diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc')
-rw-r--r-- | security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc new file mode 100644 index 0000000000..9aa6542d66 --- /dev/null +++ b/security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc @@ -0,0 +1,414 @@ +/* -*- 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 "pk11pub.h" +#include "ssl.h" +#include "sslerr.h" +#include "sslproto.h" + +extern "C" { +// This is not something that should make you happy. +#include "libssl_internals.h" +} + +#include "gtest_utils.h" +#include "tls_connect.h" +#include "tls_filter.h" + +namespace nss_test { + +// Replaces the client hello with an SSLv2 version once. +class SSLv2ClientHelloFilter : public PacketFilter { + public: + SSLv2ClientHelloFilter(const std::shared_ptr<TlsAgent>& client, + uint16_t version) + : replaced_(false), + client_(client), + version_(version), + pad_len_(0), + reported_pad_len_(0), + client_random_len_(16), + ciphers_(0), + send_escape_(false) {} + + void SetVersion(uint16_t version) { version_ = version; } + + void SetCipherSuites(const std::vector<uint16_t>& ciphers) { + ciphers_ = ciphers; + } + + // Set a padding length and announce it correctly. + void SetPadding(uint8_t pad_len) { SetPadding(pad_len, pad_len); } + + // Set a padding length and allow to lie about its length. + void SetPadding(uint8_t pad_len, uint8_t reported_pad_len) { + pad_len_ = pad_len; + reported_pad_len_ = reported_pad_len; + } + + void SetClientRandomLength(uint16_t client_random_len) { + client_random_len_ = client_random_len; + } + + void SetSendEscape(bool send_escape) { send_escape_ = send_escape; } + + protected: + virtual PacketFilter::Action Filter(const DataBuffer& input, + DataBuffer* output) { + if (replaced_) { + return KEEP; + } + + // Replace only the very first packet. + replaced_ = true; + + // The SSLv2 client hello size. + size_t packet_len = SSL_HL_CLIENT_HELLO_HBYTES + (ciphers_.size() * 3) + + client_random_len_ + pad_len_; + + size_t idx = 0; + *output = input; + output->Allocate(packet_len); + output->Truncate(packet_len); + + // Write record length. + if (pad_len_ > 0) { + size_t masked_len = 0x3fff & packet_len; + if (send_escape_) { + masked_len |= 0x4000; + } + + idx = output->Write(idx, masked_len, 2); + idx = output->Write(idx, reported_pad_len_, 1); + } else { + PR_ASSERT(!send_escape_); + idx = output->Write(idx, 0x8000 | packet_len, 2); + } + + // Remember header length. + size_t hdr_len = idx; + + // Write client hello. + idx = output->Write(idx, SSL_MT_CLIENT_HELLO, 1); + idx = output->Write(idx, version_, 2); + + // Cipher list length. + idx = output->Write(idx, (ciphers_.size() * 3), 2); + + // Session ID length. + idx = output->Write(idx, static_cast<uint32_t>(0), 2); + + // ClientRandom length. + idx = output->Write(idx, client_random_len_, 2); + + // Cipher suites. + for (auto cipher : ciphers_) { + idx = output->Write(idx, static_cast<uint32_t>(cipher), 3); + } + + // Challenge. + std::vector<uint8_t> challenge(client_random_len_); + PK11_GenerateRandom(challenge.data(), challenge.size()); + idx = output->Write(idx, challenge.data(), challenge.size()); + + // Add padding if any. + if (pad_len_ > 0) { + std::vector<uint8_t> pad(pad_len_); + idx = output->Write(idx, pad.data(), pad.size()); + } + + // Update the client random so that the handshake succeeds. + SECStatus rv = SSLInt_UpdateSSLv2ClientRandom( + client_.lock()->ssl_fd(), challenge.data(), challenge.size(), + output->data() + hdr_len, output->len() - hdr_len); + EXPECT_EQ(SECSuccess, rv); + + return CHANGE; + } + + private: + bool replaced_; + std::weak_ptr<TlsAgent> client_; + uint16_t version_; + uint8_t pad_len_; + uint8_t reported_pad_len_; + uint16_t client_random_len_; + std::vector<uint16_t> ciphers_; + bool send_escape_; +}; + +class SSLv2ClientHelloTestF : public TlsConnectTestBase { + public: + SSLv2ClientHelloTestF() + : TlsConnectTestBase(ssl_variant_stream, 0), filter_(nullptr) {} + + SSLv2ClientHelloTestF(SSLProtocolVariant variant, uint16_t version) + : TlsConnectTestBase(variant, version), filter_(nullptr) {} + + void SetUp() override { + TlsConnectTestBase::SetUp(); + filter_ = MakeTlsFilter<SSLv2ClientHelloFilter>(client_, version_); + server_->SetOption(SSL_ENABLE_V2_COMPATIBLE_HELLO, PR_TRUE); + } + + void SetExpectedVersion(uint16_t version) { + TlsConnectTestBase::SetExpectedVersion(version); + filter_->SetVersion(version); + } + + void SetAvailableCipherSuite(uint16_t cipher) { + filter_->SetCipherSuites(std::vector<uint16_t>(1, cipher)); + } + + void SetAvailableCipherSuites(const std::vector<uint16_t>& ciphers) { + filter_->SetCipherSuites(ciphers); + } + + void SetPadding(uint8_t pad_len) { filter_->SetPadding(pad_len); } + + void SetPadding(uint8_t pad_len, uint8_t reported_pad_len) { + filter_->SetPadding(pad_len, reported_pad_len); + } + + void SetClientRandomLength(uint16_t client_random_len) { + filter_->SetClientRandomLength(client_random_len); + } + + void SetSendEscape(bool send_escape) { filter_->SetSendEscape(send_escape); } + + private: + std::shared_ptr<SSLv2ClientHelloFilter> filter_; +}; + +// Parameterized version of SSLv2ClientHelloTestF we can +// use with TEST_P to test multiple TLS versions easily. +class SSLv2ClientHelloTest : public SSLv2ClientHelloTestF, + public ::testing::WithParamInterface<uint16_t> { + public: + SSLv2ClientHelloTest() + : SSLv2ClientHelloTestF(ssl_variant_stream, GetParam()) {} +}; + +// Test negotiating TLS 1.0 - 1.2. +TEST_P(SSLv2ClientHelloTest, Connect) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + Connect(); +} + +TEST_P(SSLv2ClientHelloTest, ConnectDisabled) { + server_->SetOption(SSL_ENABLE_V2_COMPATIBLE_HELLO, PR_FALSE); + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + StartConnect(); + client_->Handshake(); // Send the modified ClientHello. + server_->Handshake(); // Read some. + // The problem here is that the v2 ClientHello puts the version where the v3 + // ClientHello puts a version number. So the version number (0x0301+) appears + // to be a length and server blocks waiting for that much data. + EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError()); + + // This is usually what happens with v2-compatible: the server hangs. + // But to be certain, feed in more data to see if an error comes out. + uint8_t zeros[SSL_LIBRARY_VERSION_TLS_1_2] = {0}; + client_->SendDirect(DataBuffer(zeros, sizeof(zeros))); + ExpectAlert(server_, kTlsAlertUnexpectedMessage); + server_->Handshake(); + client_->Handshake(); +} + +// Sending a v2 ClientHello after a no-op v3 record must fail. +TEST_P(SSLv2ClientHelloTest, ConnectAfterEmptyV3Record) { + DataBuffer buffer; + + size_t idx = 0; + idx = buffer.Write(idx, 0x16, 1); // handshake + idx = buffer.Write(idx, 0x0301, 2); // record_version + (void)buffer.Write(idx, 0U, 2); // length=0 + + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + EnsureTlsSetup(); + client_->SendDirect(buffer); + + // Need padding so the connection doesn't just time out. With a v2 + // ClientHello parsed as a v3 record we will use the record version + // as the record length. + SetPadding(255); + + ConnectExpectAlert(server_, kTlsAlertUnexpectedMessage); + EXPECT_EQ(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE, server_->error_code()); +} + +// Test negotiating TLS 1.3. +TEST_F(SSLv2ClientHelloTestF, Connect13) { + EnsureTlsSetup(); + SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_3); + ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3); + + std::vector<uint16_t> cipher_suites = {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}; + SetAvailableCipherSuites(cipher_suites); + + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, server_->error_code()); +} + +// Test negotiating an EC suite. +TEST_P(SSLv2ClientHelloTest, NegotiateECSuite) { + SetAvailableCipherSuite(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA); + Connect(); +} + +// Test negotiating TLS 1.0 - 1.2 with a padded client hello. +TEST_P(SSLv2ClientHelloTest, AddPadding) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + SetPadding(255); + Connect(); +} + +// Test that sending a security escape fails the handshake. +TEST_P(SSLv2ClientHelloTest, SendSecurityEscape) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + // Send a security escape. + SetSendEscape(true); + + // Set a big padding so that the server fails instead of timing out. + SetPadding(255); + + ConnectExpectAlert(server_, kTlsAlertUnexpectedMessage); +} + +// Invalid SSLv2 client hello padding must fail the handshake. +TEST_P(SSLv2ClientHelloTest, AddErroneousPadding) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + // Append 5 bytes of padding but say it's only 4. + SetPadding(5, 4); + + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, server_->error_code()); +} + +// Invalid SSLv2 client hello padding must fail the handshake. +TEST_P(SSLv2ClientHelloTest, AddErroneousPadding2) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + // Append 5 bytes of padding but say it's 6. + SetPadding(5, 6); + + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, server_->error_code()); +} + +// Wrong amount of bytes for the ClientRandom must fail the handshake. +TEST_P(SSLv2ClientHelloTest, SmallClientRandom) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + // Send a ClientRandom that's too small. + SetClientRandomLength(15); + + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, server_->error_code()); +} + +// Test sending the maximum accepted number of ClientRandom bytes. +TEST_P(SSLv2ClientHelloTest, MaxClientRandom) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + SetClientRandomLength(32); + Connect(); +} + +// Wrong amount of bytes for the ClientRandom must fail the handshake. +TEST_P(SSLv2ClientHelloTest, BigClientRandom) { + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + // Send a ClientRandom that's too big. + SetClientRandomLength(33); + + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, server_->error_code()); +} + +// Connection must fail if we require safe renegotiation but the client doesn't +// include TLS_EMPTY_RENEGOTIATION_INFO_SCSV in the list of cipher suites. +TEST_P(SSLv2ClientHelloTest, RequireSafeRenegotiation) { + server_->SetOption(SSL_REQUIRE_SAFE_NEGOTIATION, PR_TRUE); + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + ConnectExpectAlert(server_, kTlsAlertHandshakeFailure); + EXPECT_EQ(SSL_ERROR_UNSAFE_NEGOTIATION, server_->error_code()); +} + +// Connection must succeed when requiring safe renegotiation and the client +// includes TLS_EMPTY_RENEGOTIATION_INFO_SCSV in the list of cipher suites. +TEST_P(SSLv2ClientHelloTest, RequireSafeRenegotiationWithSCSV) { + server_->SetOption(SSL_REQUIRE_SAFE_NEGOTIATION, PR_TRUE); + std::vector<uint16_t> cipher_suites = {TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_EMPTY_RENEGOTIATION_INFO_SCSV}; + SetAvailableCipherSuites(cipher_suites); + Connect(); +} + +TEST_P(SSLv2ClientHelloTest, CheckServerRandom) { + ConfigureSessionCache(RESUME_NONE, RESUME_NONE); + SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA); + + static const size_t random_len = 32; + uint8_t srandom1[random_len]; + uint8_t z[random_len] = {0}; + + auto sh = MakeTlsFilter<TlsHandshakeRecorder>(server_, ssl_hs_server_hello); + Connect(); + ASSERT_TRUE(sh->buffer().len() > (random_len + 2)); + memcpy(srandom1, sh->buffer().data() + 2, random_len); + EXPECT_NE(0, memcmp(srandom1, z, random_len)); + + Reset(); + sh = MakeTlsFilter<TlsHandshakeRecorder>(server_, ssl_hs_server_hello); + Connect(); + ASSERT_TRUE(sh->buffer().len() > (random_len + 2)); + const uint8_t* srandom2 = sh->buffer().data() + 2; + + EXPECT_NE(0, memcmp(srandom2, z, random_len)); + EXPECT_NE(0, memcmp(srandom1, srandom2, random_len)); +} + +// Connect to the server with TLS 1.1, signalling that this is a fallback from +// a higher version. As the server doesn't support anything higher than TLS 1.1 +// it must accept the connection. +TEST_F(SSLv2ClientHelloTestF, FallbackSCSV) { + EnsureTlsSetup(); + SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_1); + ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_1); + + std::vector<uint16_t> cipher_suites = {TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_FALLBACK_SCSV}; + SetAvailableCipherSuites(cipher_suites); + Connect(); +} + +// Connect to the server with TLS 1.1, signalling that this is a fallback from +// a higher version. As the server supports TLS 1.2 though it must reject the +// connection due to a possible downgrade attack. +TEST_F(SSLv2ClientHelloTestF, InappropriateFallbackSCSV) { + SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_1); + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_1, + SSL_LIBRARY_VERSION_TLS_1_1); + server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_1, + SSL_LIBRARY_VERSION_TLS_1_2); + + std::vector<uint16_t> cipher_suites = {TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_FALLBACK_SCSV}; + SetAvailableCipherSuites(cipher_suites); + + ConnectExpectAlert(server_, kTlsAlertInappropriateFallback); + EXPECT_EQ(SSL_ERROR_INAPPROPRIATE_FALLBACK_ALERT, server_->error_code()); +} + +INSTANTIATE_TEST_SUITE_P(VersionsStream10Pre13, SSLv2ClientHelloTest, + TlsConnectTestBase::kTlsV10); +INSTANTIATE_TEST_SUITE_P(VersionsStreamPre13, SSLv2ClientHelloTest, + TlsConnectTestBase::kTlsV11V12); + +} // namespace nss_test |