summaryrefslogtreecommitdiffstats
path: root/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc')
-rw-r--r--security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc431
1 files changed, 431 insertions, 0 deletions
diff --git a/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc
new file mode 100644
index 000000000..00651aec5
--- /dev/null
+++ b/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc
@@ -0,0 +1,431 @@
+/* -*- 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 "secerr.h"
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+#include "gtest_utils.h"
+#include "scoped_ptrs.h"
+#include "tls_connect.h"
+#include "tls_filter.h"
+#include "tls_parser.h"
+
+namespace nss_test {
+
+// This class tracks the maximum size of record that was sent, both cleartext
+// and plain. It only tracks records that have an outer type of
+// application_data. In TLS 1.3, this includes handshake messages.
+class TlsRecordMaximum : public TlsRecordFilter {
+ public:
+ TlsRecordMaximum(const std::shared_ptr<TlsAgent>& a)
+ : TlsRecordFilter(a), max_ciphertext_(0), max_plaintext_(0) {}
+
+ size_t max_ciphertext() const { return max_ciphertext_; }
+ size_t max_plaintext() const { return max_plaintext_; }
+
+ protected:
+ PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& record, size_t* offset,
+ DataBuffer* output) override {
+ std::cerr << "max: " << record << std::endl;
+ // Ignore unprotected packets.
+ if (header.content_type() != kTlsApplicationDataType) {
+ return KEEP;
+ }
+
+ max_ciphertext_ = (std::max)(max_ciphertext_, record.len());
+ return TlsRecordFilter::FilterRecord(header, record, offset, output);
+ }
+
+ PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& data,
+ DataBuffer* changed) override {
+ max_plaintext_ = (std::max)(max_plaintext_, data.len());
+ return KEEP;
+ }
+
+ private:
+ size_t max_ciphertext_;
+ size_t max_plaintext_;
+};
+
+void CheckRecordSizes(const std::shared_ptr<TlsAgent>& agent,
+ const std::shared_ptr<TlsRecordMaximum>& record_max,
+ size_t config) {
+ uint16_t cipher_suite;
+ ASSERT_TRUE(agent->cipher_suite(&cipher_suite));
+
+ size_t expansion;
+ size_t iv;
+ switch (cipher_suite) {
+ case TLS_AES_128_GCM_SHA256:
+ case TLS_AES_256_GCM_SHA384:
+ case TLS_CHACHA20_POLY1305_SHA256:
+ expansion = 16;
+ iv = 0;
+ break;
+
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ expansion = 16;
+ iv = 8;
+ break;
+
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ // Expansion is 20 for the MAC. Maximum block padding is 16. Maximum
+ // padding is added when the input plus the MAC is an exact multiple of
+ // the block size.
+ expansion = 20 + 16 - ((config + 20) % 16);
+ iv = 16;
+ break;
+
+ default:
+ ADD_FAILURE() << "No expansion set for ciphersuite "
+ << agent->cipher_suite_name();
+ return;
+ }
+
+ switch (agent->version()) {
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ EXPECT_EQ(0U, iv) << "No IV for TLS 1.3";
+ // We only have decryption in TLS 1.3.
+ EXPECT_EQ(config - 1, record_max->max_plaintext())
+ << "bad plaintext length for " << agent->role_str();
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ expansion += iv;
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ break;
+
+ default:
+ ADD_FAILURE() << "Unexpected version " << agent->version();
+ return;
+ }
+
+ EXPECT_EQ(config + expansion, record_max->max_ciphertext())
+ << "bad ciphertext length for " << agent->role_str();
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeMaximum) {
+ uint16_t max_record_size =
+ (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) ? 16385 : 16384;
+ size_t send_size = (version_ >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? max_record_size
+ : max_record_size + 1;
+
+ EnsureTlsSetup();
+ auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
+ client_max->EnableDecryption();
+ auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
+ server_max->EnableDecryption();
+
+ Connect();
+ client_->SendData(send_size, send_size);
+ server_->SendData(send_size, send_size);
+ server_->ReadBytes(send_size);
+ client_->ReadBytes(send_size);
+
+ CheckRecordSizes(client_, client_max, max_record_size);
+ CheckRecordSizes(server_, server_max, max_record_size);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) {
+ EnsureTlsSetup();
+ auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
+ server_max->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+ SendReceive(127); // Big enough for one record, allowing for 1+N splitting.
+
+ CheckRecordSizes(server_, server_max, 64);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) {
+ EnsureTlsSetup();
+ auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
+ client_max->EnableDecryption();
+
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+ SendReceive(127);
+
+ CheckRecordSizes(client_, client_max, 64);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeAsymmetric) {
+ EnsureTlsSetup();
+ auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
+ client_max->EnableDecryption();
+ auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
+ server_max->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 100);
+ Connect();
+ SendReceive(127);
+
+ CheckRecordSizes(client_, client_max, 100);
+ CheckRecordSizes(server_, server_max, 64);
+}
+
+// This just modifies the encrypted payload so to include a few extra zeros.
+class TlsRecordExpander : public TlsRecordFilter {
+ public:
+ TlsRecordExpander(const std::shared_ptr<TlsAgent>& a, size_t expansion)
+ : TlsRecordFilter(a), expansion_(expansion) {}
+
+ protected:
+ virtual PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& data,
+ DataBuffer* changed) {
+ if (header.content_type() != kTlsApplicationDataType) {
+ return KEEP;
+ }
+ changed->Allocate(data.len() + expansion_);
+ changed->Write(0, data.data(), data.len());
+ return CHANGE;
+ }
+
+ private:
+ size_t expansion_;
+};
+
+// Tweak the plaintext of server records so that they exceed the client's limit.
+TEST_P(TlsConnectTls13, RecordSizePlaintextExceed) {
+ EnsureTlsSetup();
+ auto server_expand = MakeTlsFilter<TlsRecordExpander>(server_, 1);
+ server_expand->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+
+ server_->SendData(100);
+
+ client_->ExpectReadWriteError();
+ ExpectAlert(client_, kTlsAlertRecordOverflow);
+ client_->ReadBytes(100);
+ EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, client_->error_code());
+
+ // Consume the alert at the server.
+ server_->Handshake();
+ server_->CheckErrorCode(SSL_ERROR_RECORD_OVERFLOW_ALERT);
+}
+
+// Tweak the ciphertext of server records so that they greatly exceed the limit.
+// This requires a much larger expansion than for plaintext to trigger the
+// guard, which runs before decryption (current allowance is 304 octets).
+TEST_P(TlsConnectTls13, RecordSizeCiphertextExceed) {
+ EnsureTlsSetup();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+
+ auto server_expand = MakeTlsFilter<TlsRecordExpander>(server_, 320);
+ server_->SendData(100);
+
+ client_->ExpectReadWriteError();
+ ExpectAlert(client_, kTlsAlertRecordOverflow);
+ client_->ReadBytes(100);
+ EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, client_->error_code());
+
+ // Consume the alert at the server.
+ server_->Handshake();
+ server_->CheckErrorCode(SSL_ERROR_RECORD_OVERFLOW_ALERT);
+}
+
+// This indiscriminately adds padding to application data records.
+class TlsRecordPadder : public TlsRecordFilter {
+ public:
+ TlsRecordPadder(const std::shared_ptr<TlsAgent>& a, size_t padding)
+ : TlsRecordFilter(a), padding_(padding) {}
+
+ protected:
+ PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& record, size_t* offset,
+ DataBuffer* output) override {
+ if (header.content_type() != kTlsApplicationDataType) {
+ return KEEP;
+ }
+
+ uint8_t inner_content_type;
+ DataBuffer plaintext;
+ if (!Unprotect(header, record, &inner_content_type, &plaintext)) {
+ return KEEP;
+ }
+
+ if (inner_content_type != kTlsApplicationDataType) {
+ return KEEP;
+ }
+
+ DataBuffer ciphertext;
+ bool ok =
+ Protect(header, inner_content_type, plaintext, &ciphertext, padding_);
+ EXPECT_TRUE(ok);
+ if (!ok) {
+ return KEEP;
+ }
+ *offset = header.Write(output, *offset, ciphertext);
+ return CHANGE;
+ }
+
+ private:
+ size_t padding_;
+};
+
+TEST_P(TlsConnectTls13, RecordSizeExceedPad) {
+ EnsureTlsSetup();
+ auto server_max = std::make_shared<TlsRecordMaximum>(server_);
+ auto server_expand = std::make_shared<TlsRecordPadder>(server_, 1);
+ server_->SetFilter(std::make_shared<ChainedPacketFilter>(
+ ChainedPacketFilterInit({server_max, server_expand})));
+ server_expand->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+
+ server_->SendData(100);
+
+ client_->ExpectReadWriteError();
+ ExpectAlert(client_, kTlsAlertRecordOverflow);
+ client_->ReadBytes(100);
+ EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, client_->error_code());
+
+ // Consume the alert at the server.
+ server_->Handshake();
+ server_->CheckErrorCode(SSL_ERROR_RECORD_OVERFLOW_ALERT);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeBadValues) {
+ EnsureTlsSetup();
+ EXPECT_EQ(SECFailure,
+ SSL_OptionSet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, 63));
+ EXPECT_EQ(SECFailure,
+ SSL_OptionSet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, -1));
+ EXPECT_EQ(SECFailure,
+ SSL_OptionSet(server_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, 16386));
+ Connect();
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeGetValues) {
+ EnsureTlsSetup();
+ int v;
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionGet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(16385, v);
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 300);
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionGet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(300, v);
+ Connect();
+}
+
+// The value of the extension is capped by the maximum version of the client.
+TEST_P(TlsConnectGeneric, RecordSizeCapExtensionClient) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385);
+ auto capture =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_record_size_limit_xtn);
+ capture->EnableDecryption();
+ Connect();
+
+ uint64_t val = 0;
+ EXPECT_TRUE(capture->extension().Read(0, 2, &val));
+ if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
+ EXPECT_EQ(16384U, val) << "Extension should be capped";
+ } else {
+ EXPECT_EQ(16385U, val);
+ }
+}
+
+// The value of the extension is capped by the maximum version of the server.
+TEST_P(TlsConnectGeneric, RecordSizeCapExtensionServer) {
+ EnsureTlsSetup();
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385);
+ auto capture =
+ MakeTlsFilter<TlsExtensionCapture>(server_, ssl_record_size_limit_xtn);
+ capture->EnableDecryption();
+ Connect();
+
+ uint64_t val = 0;
+ EXPECT_TRUE(capture->extension().Read(0, 2, &val));
+ if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
+ EXPECT_EQ(16384U, val) << "Extension should be capped";
+ } else {
+ EXPECT_EQ(16385U, val);
+ }
+}
+
+// Damage the client extension and the handshake fails, but the server
+// doesn't generate a validation error.
+TEST_P(TlsConnectGenericPre13, RecordSizeClientExtensionInvalid) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 1000);
+ static const uint8_t v[] = {0xf4, 0x1f};
+ MakeTlsFilter<TlsExtensionReplacer>(client_, ssl_record_size_limit_xtn,
+ DataBuffer(v, sizeof(v)));
+ ConnectExpectAlert(server_, kTlsAlertDecryptError);
+}
+
+// Special handling for TLS 1.3, where the alert isn't read.
+TEST_F(TlsConnectStreamTls13, RecordSizeClientExtensionInvalid) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 1000);
+ static const uint8_t v[] = {0xf4, 0x1f};
+ MakeTlsFilter<TlsExtensionReplacer>(client_, ssl_record_size_limit_xtn,
+ DataBuffer(v, sizeof(v)));
+ client_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ ConnectExpectFail();
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeServerExtensionInvalid) {
+ EnsureTlsSetup();
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 1000);
+ static const uint8_t v[] = {0xf4, 0x1f};
+ auto replace = MakeTlsFilter<TlsExtensionReplacer>(
+ server_, ssl_record_size_limit_xtn, DataBuffer(v, sizeof(v)));
+ replace->EnableDecryption();
+ ConnectExpectAlert(client_, kTlsAlertIllegalParameter);
+}
+
+class RecordSizeDefaultsTest : public ::testing::Test {
+ public:
+ void SetUp() {
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionGetDefault(SSL_RECORD_SIZE_LIMIT, &default_));
+ }
+ void TearDown() {
+ // Make sure to restore the default value at the end.
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, default_));
+ }
+
+ private:
+ PRIntn default_ = 0;
+};
+
+TEST_F(RecordSizeDefaultsTest, RecordSizeBadValues) {
+ EXPECT_EQ(SECFailure, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, 63));
+ EXPECT_EQ(SECFailure, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, -1));
+ EXPECT_EQ(SECFailure, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, 16386));
+}
+
+TEST_F(RecordSizeDefaultsTest, RecordSizeGetValue) {
+ int v;
+ EXPECT_EQ(SECSuccess, SSL_OptionGetDefault(SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(16385, v);
+ EXPECT_EQ(SECSuccess, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, 3000));
+ EXPECT_EQ(SECSuccess, SSL_OptionGetDefault(SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(3000, v);
+}
+
+} // namespace nss_test