summaryrefslogtreecommitdiffstats
path: root/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc')
-rw-r--r--security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc1522
1 files changed, 1522 insertions, 0 deletions
diff --git a/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
new file mode 100644
index 0000000000..2e23fc096a
--- /dev/null
+++ b/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
@@ -0,0 +1,1522 @@
+/* -*- 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 <functional>
+#include <memory>
+#include "secerr.h"
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslexp.h"
+#include "sslproto.h"
+
+extern "C" {
+// This is not something that should make you happy.
+#include "libssl_internals.h"
+}
+
+#include "gtest_utils.h"
+#include "nss_scoped_ptrs.h"
+#include "scoped_ptrs_ssl.h"
+#include "tls_connect.h"
+#include "tls_filter.h"
+#include "tls_parser.h"
+#include "tls_protect.h"
+
+namespace nss_test {
+
+class TlsServerKeyExchangeEcdhe {
+ public:
+ bool Parse(const DataBuffer& buffer) {
+ TlsParser parser(buffer);
+
+ uint8_t curve_type;
+ if (!parser.Read(&curve_type)) {
+ return false;
+ }
+
+ if (curve_type != 3) { // named_curve
+ return false;
+ }
+
+ uint32_t named_curve;
+ if (!parser.Read(&named_curve, 2)) {
+ return false;
+ }
+
+ return parser.ReadVariable(&public_key_, 1);
+ }
+
+ DataBuffer public_key_;
+};
+
+TEST_P(TlsConnectGenericPre13, ConnectResumed) {
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_SESSIONID);
+ Connect();
+
+ Reset();
+ ExpectResumption(RESUME_SESSIONID);
+ Connect();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectClientCacheDisabled) {
+ ConfigureSessionCache(RESUME_NONE, RESUME_SESSIONID);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectServerCacheDisabled) {
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_NONE);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectSessionCacheDisabled) {
+ ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectResumeSupportBoth) {
+ // This prefers tickets.
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectResumeClientTicketServerBoth) {
+ // This causes no resumption because the client needs the
+ // session cache to resume even with tickets.
+ ConfigureSessionCache(RESUME_TICKET, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_TICKET, RESUME_BOTH);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectResumeClientBothTicketServerTicket) {
+ // This causes a ticket resumption.
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectResumeClientServerTicketOnly) {
+ // This causes no resumption because the client needs the
+ // session cache to resume even with tickets.
+ ConfigureSessionCache(RESUME_TICKET, RESUME_TICKET);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_TICKET, RESUME_TICKET);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectResumeClientBothServerNone) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_NONE);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_NONE);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumption, ConnectResumeClientNoneServerBoth) {
+ ConfigureSessionCache(RESUME_NONE, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_NONE, RESUME_BOTH);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericPre13, ResumeWithHigherVersionTls13) {
+ uint16_t lower_version = version_;
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ SendReceive();
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ EnsureTlsSetup();
+ auto psk_ext = std::make_shared<TlsExtensionCapture>(
+ client_, ssl_tls13_pre_shared_key_xtn);
+ auto ticket_ext =
+ std::make_shared<TlsExtensionCapture>(client_, ssl_session_ticket_xtn);
+ client_->SetFilter(std::make_shared<ChainedPacketFilter>(
+ ChainedPacketFilterInit({psk_ext, ticket_ext})));
+ SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ client_->SetVersionRange(lower_version, SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(lower_version, SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+
+ // The client shouldn't have sent a PSK, though it will send a ticket.
+ EXPECT_FALSE(psk_ext->captured());
+ EXPECT_TRUE(ticket_ext->captured());
+}
+
+class CaptureSessionId : public TlsHandshakeFilter {
+ public:
+ CaptureSessionId(const std::shared_ptr<TlsAgent>& a)
+ : TlsHandshakeFilter(
+ a, {kTlsHandshakeClientHello, kTlsHandshakeServerHello}),
+ sid_() {}
+
+ const DataBuffer& sid() const { return sid_; }
+
+ protected:
+ PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
+ const DataBuffer& input,
+ DataBuffer* output) override {
+ // The session_id is in the same place in both Hello messages:
+ size_t offset = 2 + 32; // Version(2) + Random(32)
+ uint32_t len = 0;
+ EXPECT_TRUE(input.Read(offset, 1, &len));
+ offset++;
+ if (input.len() < offset + len) {
+ ADD_FAILURE() << "session_id overflows the Hello message";
+ return KEEP;
+ }
+ sid_.Assign(input.data() + offset, len);
+ return KEEP;
+ }
+
+ private:
+ DataBuffer sid_;
+};
+
+// Attempting to resume from TLS 1.2 when 1.3 is possible should not result in
+// resumption, though it will appear to be TLS 1.3 compatibility mode if the
+// server uses a session ID.
+TEST_P(TlsConnectGenericPre13, ResumeWithHigherVersionTls13SessionId) {
+ uint16_t lower_version = version_;
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_SESSIONID);
+ auto original_sid = MakeTlsFilter<CaptureSessionId>(server_);
+ Connect();
+ CheckKeys();
+ EXPECT_EQ(32U, original_sid->sid().len());
+
+ // The client should now attempt to resume with the session ID from the last
+ // connection. This looks like compatibility mode, we just want to ensure
+ // that we get TLS 1.3 rather than 1.2 (and no resumption).
+ Reset();
+ auto client_sid = MakeTlsFilter<CaptureSessionId>(client_);
+ auto server_sid = MakeTlsFilter<CaptureSessionId>(server_);
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_SESSIONID);
+ SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ client_->SetVersionRange(lower_version, SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(lower_version, SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_NONE);
+
+ Connect();
+ SendReceive();
+
+ EXPECT_EQ(client_sid->sid(), original_sid->sid());
+ if (variant_ == ssl_variant_stream) {
+ EXPECT_EQ(client_sid->sid(), server_sid->sid());
+ } else {
+ // DTLS servers don't echo the session ID.
+ EXPECT_EQ(0U, server_sid->sid().len());
+ }
+}
+
+TEST_P(TlsConnectPre12, ResumeWithHigherVersionTls12) {
+ uint16_t lower_version = version_;
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ EnsureTlsSetup();
+ SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ client_->SetVersionRange(lower_version, SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(lower_version, SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+}
+
+TEST_P(TlsConnectGenericPre13, ResumeWithLowerVersionFromTls13) {
+ uint16_t original_version = version_;
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+ SendReceive();
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ConfigureVersion(original_version);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectPre12, ResumeWithLowerVersionFromTls12) {
+ uint16_t original_version = version_;
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_2);
+ Connect();
+ SendReceive();
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ConfigureVersion(original_version);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+TEST_P(TlsConnectGeneric, ConnectResumeClientBothTicketServerTicketForget) {
+ // This causes a ticket resumption.
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ClearServerCache();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+// Tickets last two days maximum; this is a time longer than that.
+static const PRTime kLongerThanTicketLifetime =
+ 3LL * 24 * 60 * 60 * PR_USEC_PER_SEC;
+
+TEST_P(TlsConnectGenericResumption, ConnectWithExpiredTicketAtClient) {
+ // This causes a ticket resumption.
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive();
+
+ AdvanceTime(kLongerThanTicketLifetime);
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_NONE);
+
+ // TLS 1.3 uses the pre-shared key extension instead.
+ SSLExtensionType xtn = (version_ >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? ssl_tls13_pre_shared_key_xtn
+ : ssl_session_ticket_xtn;
+ auto capture = MakeTlsFilter<TlsExtensionCapture>(client_, xtn);
+ Connect();
+
+ if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ EXPECT_FALSE(capture->captured());
+ } else {
+ EXPECT_TRUE(capture->captured());
+ EXPECT_EQ(0U, capture->extension().len());
+ }
+}
+
+TEST_P(TlsConnectGeneric, ConnectWithExpiredTicketAtServer) {
+ // This causes a ticket resumption.
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_NONE);
+
+ SSLExtensionType xtn = (version_ >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? ssl_tls13_pre_shared_key_xtn
+ : ssl_session_ticket_xtn;
+ auto capture = MakeTlsFilter<TlsExtensionCapture>(client_, xtn);
+ StartConnect();
+ client_->Handshake();
+ EXPECT_TRUE(capture->captured());
+ EXPECT_LT(0U, capture->extension().len());
+
+ AdvanceTime(kLongerThanTicketLifetime);
+
+ Handshake();
+ CheckConnected();
+}
+
+TEST_P(TlsConnectGeneric, ConnectResumeCorruptTicket) {
+ // This causes a ticket resumption.
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive();
+
+ Reset();
+ static const uint8_t kHmacKey1Buf[32] = {0};
+ static const DataBuffer kHmacKey1(kHmacKey1Buf, sizeof(kHmacKey1Buf));
+
+ SECItem key_item = {siBuffer, const_cast<uint8_t*>(kHmacKey1Buf),
+ sizeof(kHmacKey1Buf)};
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ PK11SymKey* hmac_key =
+ PK11_ImportSymKey(slot.get(), CKM_SHA256_HMAC, PK11_OriginUnwrap,
+ CKA_SIGN, &key_item, nullptr);
+ ASSERT_NE(nullptr, hmac_key);
+ SSLInt_SetSelfEncryptMacKey(hmac_key);
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ } else {
+ ConnectExpectAlert(server_, illegal_parameter);
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
+ }
+}
+
+// This callback switches out the "server" cert used on the server with
+// the "client" certificate, which should be the same type.
+static int32_t SwitchCertificates(TlsAgent* agent, const SECItem* srvNameArr,
+ uint32_t srvNameArrSize) {
+ bool ok = agent->ConfigServerCert("client");
+ if (!ok) return SSL_SNI_SEND_ALERT;
+
+ return 0; // first config
+};
+
+TEST_P(TlsConnectGeneric, ServerSNICertSwitch) {
+ Connect();
+ ScopedCERTCertificate cert1(SSL_PeerCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert1.get());
+
+ Reset();
+ ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
+
+ server_->SetSniCallback(SwitchCertificates);
+
+ Connect();
+ ScopedCERTCertificate cert2(SSL_PeerCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert2.get());
+ CheckKeys();
+ EXPECT_FALSE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert));
+}
+
+TEST_P(TlsConnectGeneric, ServerSNICertTypeSwitch) {
+ Reset(TlsAgent::kServerEcdsa256);
+ Connect();
+ ScopedCERTCertificate cert1(SSL_PeerCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert1.get());
+
+ Reset();
+ ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
+
+ // Because we configure an RSA certificate here, it only adds a second, unused
+ // certificate, which has no effect on what the server uses.
+ server_->SetSniCallback(SwitchCertificates);
+
+ Connect();
+ ScopedCERTCertificate cert2(SSL_PeerCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert2.get());
+ CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa);
+ EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert));
+}
+
+TEST_P(TlsConnectGenericPre13, ConnectEcdheTwiceReuseKey) {
+ auto filter = MakeTlsFilter<TlsHandshakeRecorder>(
+ server_, kTlsHandshakeServerKeyExchange);
+ EnableECDHEServerKeyReuse();
+ Connect();
+ CheckKeys();
+ TlsServerKeyExchangeEcdhe dhe1;
+ EXPECT_TRUE(dhe1.Parse(filter->buffer()));
+
+ // Restart
+ Reset();
+ EnableECDHEServerKeyReuse();
+ auto filter2 = MakeTlsFilter<TlsHandshakeRecorder>(
+ server_, kTlsHandshakeServerKeyExchange);
+ ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
+ Connect();
+ CheckKeys();
+
+ TlsServerKeyExchangeEcdhe dhe2;
+ EXPECT_TRUE(dhe2.Parse(filter2->buffer()));
+
+ // Make sure they are the same.
+ EXPECT_EQ(dhe1.public_key_.len(), dhe2.public_key_.len());
+ EXPECT_TRUE(!memcmp(dhe1.public_key_.data(), dhe2.public_key_.data(),
+ dhe1.public_key_.len()));
+}
+
+// This test parses the ServerKeyExchange, which isn't in 1.3
+TEST_P(TlsConnectGenericPre13, ConnectEcdheTwiceNewKey) {
+ auto filter = MakeTlsFilter<TlsHandshakeRecorder>(
+ server_, kTlsHandshakeServerKeyExchange);
+ Connect();
+ CheckKeys();
+ TlsServerKeyExchangeEcdhe dhe1;
+ EXPECT_TRUE(dhe1.Parse(filter->buffer()));
+
+ // Restart
+ Reset();
+ auto filter2 = MakeTlsFilter<TlsHandshakeRecorder>(
+ server_, kTlsHandshakeServerKeyExchange);
+ ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
+ Connect();
+ CheckKeys();
+
+ TlsServerKeyExchangeEcdhe dhe2;
+ EXPECT_TRUE(dhe2.Parse(filter2->buffer()));
+
+ // Make sure they are different.
+ EXPECT_FALSE((dhe1.public_key_.len() == dhe2.public_key_.len()) &&
+ (!memcmp(dhe1.public_key_.data(), dhe2.public_key_.data(),
+ dhe1.public_key_.len())));
+}
+
+// Verify that TLS 1.3 reports an accurate group on resumption.
+TEST_P(TlsConnectTls13, TestTls13ResumeDifferentGroup) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive(); // Need to read so that we absorb the session ticket.
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_TICKET);
+ client_->ConfigNamedGroups(kFFDHEGroups);
+ server_->ConfigNamedGroups(kFFDHEGroups);
+ Connect();
+ CheckKeys(ssl_kea_dh, ssl_grp_ffdhe_2048, ssl_auth_rsa_sign,
+ ssl_sig_rsa_pss_rsae_sha256);
+}
+
+// Verify that TLS 1.3 server doesn't request certificate in the main
+// handshake, after resumption.
+TEST_P(TlsConnectTls13, TestTls13ResumeNoCertificateRequest) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ client_->SetupClientAuth();
+ server_->RequestClientAuth(true);
+ Connect();
+ SendReceive(); // Need to read so that we absorb the session ticket.
+ ScopedCERTCertificate cert1(SSL_LocalCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert1.get());
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_TICKET);
+ server_->RequestClientAuth(false);
+ auto cr_capture =
+ MakeTlsFilter<TlsHandshakeRecorder>(server_, ssl_hs_certificate_request);
+ cr_capture->EnableDecryption();
+ Connect();
+ SendReceive();
+ EXPECT_EQ(0U, cr_capture->buffer().len()) << "expect nothing captured yet";
+
+ // Sanity check whether the client certificate matches the one
+ // decrypted from ticket.
+ ScopedCERTCertificate cert2(SSL_PeerCertificate(server_->ssl_fd()));
+ ASSERT_NE(nullptr, cert2.get());
+ EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert));
+}
+
+// Here we test that 0.5 RTT is available at the server when resuming, even if
+// configured to request a client certificate. The resumed handshake relies on
+// the authentication from the original handshake, so no certificate is
+// requested this time around. The server can write before the handshake
+// completes because the PSK binder is sufficient authentication for the client.
+TEST_P(TlsConnectTls13, WriteBeforeHandshakeCompleteOnResumption) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ client_->SetupClientAuth();
+ server_->RequestClientAuth(true);
+ Connect();
+ SendReceive(); // Absorb the session ticket.
+ ScopedCERTCertificate cert1(SSL_LocalCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert1.get());
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_TICKET);
+ server_->RequestClientAuth(false);
+ StartConnect();
+ client_->Handshake(); // ClientHello
+ server_->Handshake(); // ServerHello
+
+ server_->SendData(10);
+ client_->ReadBytes(10); // Client should emit the Finished as a side-effect.
+ server_->Handshake(); // Server consumes the Finished.
+ CheckConnected();
+
+ // Check whether the client certificate matches the one from the ticket.
+ ScopedCERTCertificate cert2(SSL_PeerCertificate(server_->ssl_fd()));
+ ASSERT_NE(nullptr, cert2.get());
+ EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert));
+}
+
+// We need to enable different cipher suites at different times in the following
+// tests. Those cipher suites need to be suited to the version.
+static uint16_t ChooseOneCipher(uint16_t version) {
+ if (version >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ return TLS_AES_128_GCM_SHA256;
+ }
+ return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
+}
+
+static uint16_t ChooseIncompatibleCipher(uint16_t version) {
+ if (version >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ return TLS_AES_256_GCM_SHA384;
+ }
+ return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA;
+}
+
+// Test that we don't resume when we can't negotiate the same cipher. Note that
+// for TLS 1.3, resumption is allowed between compatible ciphers, that is those
+// with the same KDF hash, but we choose an incompatible one here.
+TEST_P(TlsConnectGenericResumption, ResumeClientIncompatibleCipher) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ client_->EnableSingleCipher(ChooseOneCipher(version_));
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign);
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_NONE);
+ client_->EnableSingleCipher(ChooseIncompatibleCipher(version_));
+ uint16_t ticket_extension;
+ if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ ticket_extension = ssl_tls13_pre_shared_key_xtn;
+ } else {
+ ticket_extension = ssl_session_ticket_xtn;
+ }
+ auto ticket_capture =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ticket_extension);
+ Connect();
+ CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign);
+ EXPECT_EQ(0U, ticket_capture->extension().len());
+}
+
+// Test that we don't resume when we can't negotiate the same cipher.
+TEST_P(TlsConnectGenericResumption, ResumeServerIncompatibleCipher) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ server_->EnableSingleCipher(ChooseOneCipher(version_));
+ Connect();
+ SendReceive(); // Absorb the session ticket.
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_NONE);
+ server_->EnableSingleCipher(ChooseIncompatibleCipher(version_));
+ Connect();
+ CheckKeys();
+}
+
+// Test that the client doesn't tolerate the server picking a different cipher
+// suite for resumption.
+TEST_P(TlsConnectStream, ResumptionOverrideCipher) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ server_->EnableSingleCipher(ChooseOneCipher(version_));
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign);
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ MakeTlsFilter<SelectedCipherSuiteReplacer>(
+ server_, ChooseIncompatibleCipher(version_));
+
+ if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ client_->ExpectSendAlert(kTlsAlertIllegalParameter);
+ server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+ } else {
+ ExpectAlert(client_, kTlsAlertHandshakeFailure);
+ }
+ ConnectExpectFail();
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_SERVER_HELLO);
+ if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ // The reason this test is stream only: the server is unable to decrypt
+ // the alert that the client sends, see bug 1304603.
+ server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE);
+ } else {
+ server_->CheckErrorCode(SSL_ERROR_HANDSHAKE_FAILURE_ALERT);
+ }
+}
+
+// In TLS 1.3, it is possible to resume with a different cipher if it has the
+// same hash.
+TEST_P(TlsConnectTls13, ResumeClientCompatibleCipher) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ client_->EnableSingleCipher(TLS_AES_128_GCM_SHA256);
+ Connect();
+ SendReceive(); // Absorb the session ticket.
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_TICKET);
+ client_->EnableSingleCipher(TLS_CHACHA20_POLY1305_SHA256);
+ Connect();
+ CheckKeys();
+}
+
+TEST_P(TlsConnectTls13, ResumeServerCompatibleCipher) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ server_->EnableSingleCipher(TLS_AES_128_GCM_SHA256);
+ Connect();
+ SendReceive(); // Absorb the session ticket.
+ CheckKeys();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ExpectResumption(RESUME_TICKET);
+ server_->EnableSingleCipher(TLS_CHACHA20_POLY1305_SHA256);
+ Connect();
+ CheckKeys();
+}
+
+class SelectedVersionReplacer : public TlsHandshakeFilter {
+ public:
+ SelectedVersionReplacer(const std::shared_ptr<TlsAgent>& a, uint16_t version)
+ : TlsHandshakeFilter(a, {kTlsHandshakeServerHello}), version_(version) {}
+
+ protected:
+ PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
+ const DataBuffer& input,
+ DataBuffer* output) override {
+ *output = input;
+ output->Write(0, static_cast<uint32_t>(version_), 2);
+ return CHANGE;
+ }
+
+ private:
+ uint16_t version_;
+};
+
+// Test how the client handles the case where the server picks a
+// lower version number on resumption.
+TEST_P(TlsConnectGenericPre13, TestResumptionOverrideVersion) {
+ uint16_t override_version = 0;
+ if (variant_ == ssl_variant_stream) {
+ switch (version_) {
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ GTEST_SKIP();
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ override_version = SSL_LIBRARY_VERSION_TLS_1_0;
+ break;
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ override_version = SSL_LIBRARY_VERSION_TLS_1_1;
+ break;
+ default:
+ ASSERT_TRUE(false) << "unknown version";
+ }
+ } else {
+ if (version_ == SSL_LIBRARY_VERSION_TLS_1_2) {
+ override_version = SSL_LIBRARY_VERSION_DTLS_1_0_WIRE;
+ } else {
+ ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, version_);
+ GTEST_SKIP();
+ }
+ }
+
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ // Need to use a cipher that is plausible for the lower version.
+ server_->EnableSingleCipher(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA);
+ Connect();
+ CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign);
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ // Enable the lower version on the client.
+ client_->SetVersionRange(version_ - 1, version_);
+ server_->EnableSingleCipher(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA);
+ MakeTlsFilter<SelectedVersionReplacer>(server_, override_version);
+
+ ConnectExpectAlert(client_, kTlsAlertHandshakeFailure);
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_SERVER_HELLO);
+ server_->CheckErrorCode(SSL_ERROR_HANDSHAKE_FAILURE_ALERT);
+}
+
+// Test that two TLS resumptions work and produce the same ticket.
+// This will change after bug 1257047 is fixed.
+TEST_F(TlsConnectTest, TestTls13ResumptionTwice) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ Connect();
+ SendReceive(); // Need to read so that we absorb the session ticket.
+ CheckKeys();
+ uint16_t original_suite;
+ EXPECT_TRUE(client_->cipher_suite(&original_suite));
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_TICKET);
+ auto c1 =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign,
+ ssl_sig_rsa_pss_rsae_sha256);
+ // The filter will go away when we reset, so save the captured extension.
+ DataBuffer initialTicket(c1->extension());
+ ASSERT_LT(0U, initialTicket.len());
+
+ ScopedCERTCertificate cert1(SSL_PeerCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert1.get());
+
+ Reset();
+ ClearStats();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ auto c2 =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign,
+ ssl_sig_rsa_pss_rsae_sha256);
+ ASSERT_LT(0U, c2->extension().len());
+
+ ScopedCERTCertificate cert2(SSL_PeerCertificate(client_->ssl_fd()));
+ ASSERT_NE(nullptr, cert2.get());
+
+ // Check that the cipher suite is reported the same on both sides, though in
+ // TLS 1.3 resumption actually negotiates a different cipher suite.
+ uint16_t resumed_suite;
+ EXPECT_TRUE(server_->cipher_suite(&resumed_suite));
+ EXPECT_EQ(original_suite, resumed_suite);
+ EXPECT_TRUE(client_->cipher_suite(&resumed_suite));
+ EXPECT_EQ(original_suite, resumed_suite);
+
+ ASSERT_NE(initialTicket, c2->extension());
+}
+
+// Check that resumption works after receiving two NST messages.
+TEST_F(TlsConnectTest, TestTls13ResumptionDuplicateNST) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+
+ // Clear the session ticket keys to invalidate the old ticket.
+ ClearServerCache();
+ EXPECT_EQ(SECSuccess, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0));
+
+ SendReceive(); // Need to read so that we absorb the session tickets.
+ CheckKeys();
+
+ // Resume the connection.
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ SendReceive();
+}
+
+// Check that the value captured in a NewSessionTicket message matches the value
+// captured from a pre_shared_key extension.
+void NstTicketMatchesPskIdentity(const DataBuffer& nst, const DataBuffer& psk) {
+ uint32_t len;
+
+ size_t offset = 4 + 4; // Skip ticket_lifetime and ticket_age_add.
+ ASSERT_TRUE(nst.Read(offset, 1, &len));
+ offset += 1 + len; // Skip ticket_nonce.
+
+ ASSERT_TRUE(nst.Read(offset, 2, &len));
+ offset += 2; // Skip the ticket length.
+ ASSERT_LE(offset + len, nst.len());
+ DataBuffer nst_ticket(nst.data() + offset, static_cast<size_t>(len));
+
+ offset = 2; // Skip the identities length.
+ ASSERT_TRUE(psk.Read(offset, 2, &len));
+ offset += 2; // Skip the identity length.
+ ASSERT_LE(offset + len, psk.len());
+ DataBuffer psk_ticket(psk.data() + offset, static_cast<size_t>(len));
+
+ EXPECT_EQ(nst_ticket, psk_ticket);
+}
+
+TEST_F(TlsConnectTest, TestTls13ResumptionDuplicateNSTWithToken) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ auto nst_capture =
+ MakeTlsFilter<TlsHandshakeRecorder>(server_, ssl_hs_new_session_ticket);
+ nst_capture->EnableDecryption();
+ Connect();
+
+ // Clear the session ticket keys to invalidate the old ticket.
+ ClearServerCache();
+ nst_capture->Reset();
+ uint8_t token[] = {0x20, 0x20, 0xff, 0x00};
+ EXPECT_EQ(SECSuccess,
+ SSL_SendSessionTicket(server_->ssl_fd(), token, sizeof(token)));
+
+ SendReceive(); // Need to read so that we absorb the session tickets.
+ CheckKeys();
+ EXPECT_LT(0U, nst_capture->buffer().len());
+
+ // Resume the connection.
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_TICKET);
+
+ auto psk_capture =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ Connect();
+ SendReceive();
+
+ NstTicketMatchesPskIdentity(nst_capture->buffer(), psk_capture->extension());
+}
+
+// Disable SSL_ENABLE_SESSION_TICKETS but ensure that tickets can still be sent
+// by invoking SSL_SendSessionTicket directly (and that the ticket is usable).
+TEST_F(TlsConnectTest, SendSessionTicketWithTicketsDisabled) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ server_->SetOption(SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
+
+ auto nst_capture =
+ MakeTlsFilter<TlsHandshakeRecorder>(server_, ssl_hs_new_session_ticket);
+ nst_capture->EnableDecryption();
+ Connect();
+
+ EXPECT_EQ(0U, nst_capture->buffer().len()) << "expect nothing captured yet";
+
+ EXPECT_EQ(SECSuccess, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0));
+ EXPECT_LT(0U, nst_capture->buffer().len()) << "should capture now";
+
+ SendReceive(); // Ensure that the client reads the ticket.
+
+ // Resume the connection.
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_TICKET);
+
+ auto psk_capture =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ Connect();
+ SendReceive();
+
+ NstTicketMatchesPskIdentity(nst_capture->buffer(), psk_capture->extension());
+}
+
+// Successfully send a session ticket after resuming and then use it.
+TEST_F(TlsConnectTest, SendTicketAfterResumption) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+
+ SendReceive(); // Need to read so that we absorb the session tickets.
+ CheckKeys();
+
+ // Resume the connection.
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_TICKET);
+
+ // We need to capture just one ticket, so
+ // disable automatic sending of tickets at the server.
+ // ConfigureSessionCache enables this option, so revert that.
+ server_->SetOption(SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
+ auto nst_capture =
+ MakeTlsFilter<TlsHandshakeRecorder>(server_, ssl_hs_new_session_ticket);
+ nst_capture->EnableDecryption();
+ Connect();
+
+ ClearServerCache();
+ EXPECT_EQ(SECSuccess, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0));
+ SendReceive();
+
+ // Reset stats so that the counters for resumptions match up.
+ ClearStats();
+ // Resume again and ensure that we get the same ticket.
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ExpectResumption(RESUME_TICKET);
+
+ auto psk_capture =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ Connect();
+ SendReceive();
+
+ NstTicketMatchesPskIdentity(nst_capture->buffer(), psk_capture->extension());
+}
+
+// Test calling SSL_SendSessionTicket in inappropriate conditions.
+TEST_F(TlsConnectTest, SendSessionTicketInappropriate) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_2);
+
+ EXPECT_EQ(SECFailure, SSL_SendSessionTicket(client_->ssl_fd(), NULL, 0))
+ << "clients can't send tickets";
+ EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+ StartConnect();
+
+ EXPECT_EQ(SECFailure, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0))
+ << "no ticket before the handshake has started";
+ EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+ Handshake();
+ EXPECT_EQ(SECFailure, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0))
+ << "no special tickets in TLS 1.2";
+ EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+}
+
+TEST_F(TlsConnectTest, SendSessionTicketMassiveToken) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+ // It should be safe to set length with a NULL token because the length should
+ // be checked before reading token.
+ EXPECT_EQ(SECFailure, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0x1ffff))
+ << "this is clearly too big";
+ EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+ static const uint8_t big_token[0xffff] = {1};
+ EXPECT_EQ(SECFailure, SSL_SendSessionTicket(server_->ssl_fd(), big_token,
+ sizeof(big_token)))
+ << "this is too big, but that's not immediately obvious";
+ EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+}
+
+TEST_F(TlsConnectDatagram13, SendSessionTicketDtls) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+ EXPECT_EQ(SECFailure, SSL_SendSessionTicket(server_->ssl_fd(), NULL, 0))
+ << "no extra tickets in DTLS until we have Ack support";
+ EXPECT_EQ(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION, PORT_GetError());
+}
+
+TEST_F(TlsConnectStreamTls13, ExternalResumptionUseSecondTicket) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ struct ResumptionTicketState {
+ std::vector<uint8_t> ticket;
+ size_t invoked = 0;
+ } ticket_state;
+ auto cb = [](PRFileDesc* fd, const PRUint8* ticket, unsigned int ticket_len,
+ void* arg) -> SECStatus {
+ auto state = reinterpret_cast<ResumptionTicketState*>(arg);
+ state->ticket.assign(ticket, ticket + ticket_len);
+ state->invoked++;
+ return SECSuccess;
+ };
+ EXPECT_EQ(SECSuccess, SSL_SetResumptionTokenCallback(client_->ssl_fd(), cb,
+ &ticket_state));
+
+ Connect();
+ EXPECT_EQ(SECSuccess, SSL_SendSessionTicket(server_->ssl_fd(), nullptr, 0));
+ SendReceive();
+ EXPECT_EQ(2U, ticket_state.invoked);
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ client_->SetResumptionToken(ticket_state.ticket);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(TlsConnectTest, TestTls13ResumptionDowngrade) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+
+ SendReceive(); // Need to read so that we absorb the session tickets.
+ CheckKeys();
+
+ // Try resuming the connection. This will fail resuming the 1.3 session
+ // from before, but will successfully establish a 1.2 connection.
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ 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);
+ Connect();
+
+ // Renegotiate to ensure we don't carryover any state
+ // from the 1.3 resumption attempt.
+ client_->SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_2);
+ client_->PrepareForRenegotiate();
+ server_->StartRenegotiate();
+ Handshake();
+
+ SendReceive();
+ CheckKeys();
+}
+
+TEST_F(TlsConnectTest, TestTls13ResumptionForcedDowngrade) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+
+ SendReceive(); // Need to read so that we absorb the session tickets.
+ CheckKeys();
+
+ // Try resuming the connection.
+ Reset();
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ // Enable the lower version on the client.
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+
+ // Add filters that set downgrade SH.version to 1.2 and the cipher suite
+ // to one that works with 1.2, so that we don't run into early sanity checks.
+ // We will eventually fail the (sid.version == SH.version) check.
+ std::vector<std::shared_ptr<PacketFilter>> filters;
+ filters.push_back(std::make_shared<SelectedCipherSuiteReplacer>(
+ server_, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256));
+ filters.push_back(std::make_shared<SelectedVersionReplacer>(
+ server_, SSL_LIBRARY_VERSION_TLS_1_2));
+
+ // Drop a bunch of extensions so that we get past the SH processing. The
+ // version extension says TLS 1.3, which is counter to our goal, the others
+ // are not permitted in TLS 1.2 handshakes.
+ filters.push_back(std::make_shared<TlsExtensionDropper>(
+ server_, ssl_tls13_supported_versions_xtn));
+ filters.push_back(
+ std::make_shared<TlsExtensionDropper>(server_, ssl_tls13_key_share_xtn));
+ filters.push_back(std::make_shared<TlsExtensionDropper>(
+ server_, ssl_tls13_pre_shared_key_xtn));
+ server_->SetFilter(std::make_shared<ChainedPacketFilter>(filters));
+
+ // The client here generates an unexpected_message alert when it receives an
+ // encrypted handshake message from the server (EncryptedExtension). The
+ // client expects to receive an unencrypted TLS 1.2 Certificate message.
+ // The server can't decrypt the alert.
+ client_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+ server_->ExpectSendAlert(kTlsAlertUnexpectedMessage); // Server can't read
+ ConnectExpectFail();
+ client_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA);
+ server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE);
+}
+
+TEST_P(TlsConnectGenericResumption, ReConnectTicket) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ server_->EnableSingleCipher(ChooseOneCipher(version_));
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign,
+ ssl_sig_rsa_pss_rsae_sha256);
+ // Resume
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ // Only the client knows this.
+ CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519,
+ ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256);
+}
+
+TEST_P(TlsConnectGenericPre13, ReConnectCache) {
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_SESSIONID);
+ server_->EnableSingleCipher(ChooseOneCipher(version_));
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign,
+ ssl_sig_rsa_pss_rsae_sha256);
+ // Resume
+ Reset();
+ ExpectResumption(RESUME_SESSIONID);
+ Connect();
+ CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519,
+ ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256);
+}
+
+TEST_P(TlsConnectGenericResumption, ReConnectAgainTicket) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ server_->EnableSingleCipher(ChooseOneCipher(version_));
+ Connect();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign,
+ ssl_sig_rsa_pss_rsae_sha256);
+ // Resume
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ // Only the client knows this.
+ CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519,
+ ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256);
+ // Resume connection again
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET, 2);
+ Connect();
+ // Only the client knows this.
+ CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519,
+ ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256);
+}
+
+void CheckGetInfoResult(PRTime now, uint32_t alpnSize, uint32_t earlyDataSize,
+ ScopedCERTCertificate& cert,
+ ScopedSSLResumptionTokenInfo& token) {
+ ASSERT_TRUE(cert);
+ ASSERT_TRUE(token->peerCert);
+
+ // Check that the server cert is the correct one.
+ ASSERT_EQ(cert->derCert.len, token->peerCert->derCert.len);
+ EXPECT_EQ(0, memcmp(cert->derCert.data, token->peerCert->derCert.data,
+ cert->derCert.len));
+
+ ASSERT_EQ(alpnSize, token->alpnSelectionLen);
+ EXPECT_EQ(0, memcmp("a", token->alpnSelection, token->alpnSelectionLen));
+
+ ASSERT_EQ(earlyDataSize, token->maxEarlyDataSize);
+
+ ASSERT_LT(now, token->expirationTime);
+}
+
+// The client should generate a new, randomized session_id
+// when resuming using an external token.
+TEST_P(TlsConnectGenericResumptionToken, CheckSessionId) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ auto original_sid = MakeTlsFilter<CaptureSessionId>(client_);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+
+ StartConnect();
+ ASSERT_TRUE(client_->MaybeSetResumptionToken());
+ auto resumed_sid = MakeTlsFilter<CaptureSessionId>(client_);
+
+ Handshake();
+ CheckConnected();
+ SendReceive();
+
+ if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
+ EXPECT_NE(resumed_sid->sid(), original_sid->sid());
+ EXPECT_EQ(32U, resumed_sid->sid().len());
+ } else {
+ EXPECT_EQ(0U, resumed_sid->sid().len());
+ }
+}
+
+TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfo) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+
+ StartConnect();
+ ASSERT_TRUE(client_->MaybeSetResumptionToken());
+
+ // Get resumption token infos
+ SSLResumptionTokenInfo tokenInfo = {0};
+ ScopedSSLResumptionTokenInfo token(&tokenInfo);
+ client_->GetTokenInfo(token);
+ ScopedCERTCertificate cert(
+ PK11_FindCertFromNickname(server_->name().c_str(), nullptr));
+ ASSERT_NE(nullptr, cert.get());
+
+ CheckGetInfoResult(now(), 0, 0, cert, token);
+
+ Handshake();
+ CheckConnected();
+
+ SendReceive();
+}
+
+TEST_P(TlsConnectGenericResumptionToken, RefuseExpiredTicketClient) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ // Move the clock to the expiration time of the ticket.
+ SSLResumptionTokenInfo tokenInfo = {0};
+ ScopedSSLResumptionTokenInfo token(&tokenInfo);
+ client_->GetTokenInfo(token);
+ AdvanceTime(token->expirationTime - now());
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+
+ StartConnect();
+ ASSERT_EQ(SECFailure,
+ SSL_SetResumptionToken(client_->ssl_fd(),
+ client_->GetResumptionToken().data(),
+ client_->GetResumptionToken().size()));
+ EXPECT_EQ(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR, PORT_GetError());
+}
+
+TEST_P(TlsConnectGenericResumptionToken, RefuseExpiredTicketServer) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_NONE);
+
+ // Start the handshake and send the ClientHello.
+ StartConnect();
+ ASSERT_EQ(SECSuccess,
+ SSL_SetResumptionToken(client_->ssl_fd(),
+ client_->GetResumptionToken().data(),
+ client_->GetResumptionToken().size()));
+ client_->Handshake();
+
+ // Move the clock to the expiration time of the ticket.
+ SSLResumptionTokenInfo tokenInfo = {0};
+ ScopedSSLResumptionTokenInfo token(&tokenInfo);
+ client_->GetTokenInfo(token);
+ AdvanceTime(token->expirationTime - now());
+
+ Handshake();
+ CheckConnected();
+}
+
+TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfoAlpn) {
+ EnableAlpn();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ CheckAlpn("a");
+ SendReceive();
+
+ Reset();
+ EnableAlpn();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+
+ StartConnect();
+ ASSERT_TRUE(client_->MaybeSetResumptionToken());
+
+ // Get resumption token infos
+ SSLResumptionTokenInfo tokenInfo = {0};
+ ScopedSSLResumptionTokenInfo token(&tokenInfo);
+ client_->GetTokenInfo(token);
+ ScopedCERTCertificate cert(
+ PK11_FindCertFromNickname(server_->name().c_str(), nullptr));
+ ASSERT_NE(nullptr, cert.get());
+
+ CheckGetInfoResult(now(), 1, 0, cert, token);
+
+ Handshake();
+ CheckConnected();
+ CheckAlpn("a");
+
+ SendReceive();
+}
+
+TEST_P(TlsConnectTls13ResumptionToken, ConnectResumeGetInfoZeroRtt) {
+ EnableAlpn();
+ RolloverAntiReplay();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ server_->Set0RttEnabled(true);
+ Connect();
+ CheckAlpn("a");
+ SendReceive();
+
+ Reset();
+ EnableAlpn();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+
+ StartConnect();
+ server_->Set0RttEnabled(true);
+ client_->Set0RttEnabled(true);
+ ASSERT_TRUE(client_->MaybeSetResumptionToken());
+
+ // Get resumption token infos
+ SSLResumptionTokenInfo tokenInfo = {0};
+ ScopedSSLResumptionTokenInfo token(&tokenInfo);
+ client_->GetTokenInfo(token);
+ ScopedCERTCertificate cert(
+ PK11_FindCertFromNickname(server_->name().c_str(), nullptr));
+ ASSERT_NE(nullptr, cert.get());
+ CheckGetInfoResult(now(), 1, 1024, cert, token);
+
+ ZeroRttSendReceive(true, true);
+ Handshake();
+ ExpectEarlyDataAccepted(true);
+ CheckConnected();
+ CheckAlpn("a");
+
+ SendReceive();
+}
+
+// Resumption on sessions with client authentication only works with internal
+// caching.
+TEST_P(TlsConnectGenericResumption, ConnectResumeClientAuth) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ client_->SetupClientAuth();
+ server_->RequestClientAuth(true);
+ Connect();
+ SendReceive();
+ EXPECT_FALSE(client_->resumption_callback_called());
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ if (use_external_cache()) {
+ ExpectResumption(RESUME_NONE);
+ } else {
+ ExpectResumption(RESUME_TICKET);
+ }
+ Connect();
+ SendReceive();
+}
+
+// Check that resumption is blocked if the server requires client auth.
+TEST_P(TlsConnectGenericResumption, ClientAuthRequiredOnResumption) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ server_->RequestClientAuth(false);
+ Connect();
+ SendReceive();
+
+ Reset();
+ client_->SetupClientAuth();
+ server_->RequestClientAuth(true);
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_NONE);
+ Connect();
+ SendReceive();
+}
+
+// Check that resumption is blocked if the server requires client auth and
+// the client fails to provide a certificate.
+TEST_P(TlsConnectGenericResumption, ClientAuthRequiredOnResumptionNoCert) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ server_->RequestClientAuth(false);
+ Connect();
+ SendReceive();
+
+ Reset();
+ server_->RequestClientAuth(true);
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ // Drive handshake manually because TLS 1.3 needs it.
+ StartConnect();
+ client_->Handshake(); // CH
+ server_->Handshake(); // SH.. (no resumption)
+ client_->Handshake(); // ...
+ if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ // In TLS 1.3, the client thinks that everything is OK here.
+ ASSERT_EQ(TlsAgent::STATE_CONNECTED, client_->state());
+ ExpectAlert(server_, kTlsAlertCertificateRequired);
+ server_->Handshake(); // Alert
+ client_->Handshake(); // Receive Alert
+ client_->CheckErrorCode(SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT);
+ } else {
+ ExpectAlert(server_, kTlsAlertBadCertificate);
+ server_->Handshake(); // Alert
+ client_->Handshake(); // Receive Alert
+ client_->CheckErrorCode(SSL_ERROR_BAD_CERT_ALERT);
+ }
+ server_->CheckErrorCode(SSL_ERROR_NO_CERTIFICATE);
+}
+
+TEST_F(TlsConnectStreamTls13, ExternalTokenAfterHrr) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ExpectResumption(RESUME_TICKET);
+
+ static const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1,
+ ssl_grp_ec_secp521r1};
+ server_->ConfigNamedGroups(groups);
+
+ StartConnect();
+ ASSERT_TRUE(client_->MaybeSetResumptionToken());
+
+ client_->Handshake(); // Send ClientHello.
+ server_->Handshake(); // Process ClientHello, send HelloRetryRequest.
+
+ auto& token = client_->GetResumptionToken();
+ SECStatus rv =
+ SSL_SetResumptionToken(client_->ssl_fd(), token.data(), token.size());
+ ASSERT_EQ(SECFailure, rv);
+ ASSERT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+ Handshake();
+ CheckConnected();
+ SendReceive();
+}
+
+TEST_F(TlsConnectStreamTls13, ExternalTokenWithPeerId) {
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ EXPECT_EQ(SECSuccess, SSL_SetSockPeerID(client_->ssl_fd(), "testPeerId"));
+ std::vector<uint8_t> ticket_state;
+ auto cb = [](PRFileDesc* fd, const PRUint8* ticket, unsigned int ticket_len,
+ void* arg) -> SECStatus {
+ EXPECT_NE(0U, ticket_len);
+ EXPECT_NE(nullptr, ticket);
+ auto ticket_state_ = reinterpret_cast<std::vector<uint8_t>*>(arg);
+ ticket_state_->assign(ticket, ticket + ticket_len);
+ return SECSuccess;
+ };
+ EXPECT_EQ(SECSuccess, SSL_SetResumptionTokenCallback(client_->ssl_fd(), cb,
+ &ticket_state));
+
+ Connect();
+ SendReceive();
+ EXPECT_NE(0U, ticket_state.size());
+
+ Reset();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH);
+ EXPECT_EQ(SECSuccess, SSL_SetSockPeerID(client_->ssl_fd(), "testPeerId"));
+ client_->SetResumptionToken(ticket_state);
+ ASSERT_TRUE(client_->MaybeSetResumptionToken());
+ ExpectResumption(RESUME_TICKET);
+ Connect();
+ SendReceive();
+}
+
+} // namespace nss_test