/* -*- 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" 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 "tls_connect.h" #include "tls_filter.h" #include "tls_parser.h" namespace nss_test { // Reproducing https://bugzilla.mozilla.org/show_bug.cgi?id=1978603 // The test causes assertion failure: timer->cb == NULL, at // lib/ssl/dtlscon.c:922 // The general problem in the bug was that each post-handshake message // was starting a new retransmit timer. // We've decided to change the timer logic such that a retransmit timer // will be restarted when a new request for a timer arrives. // This test ensures that if two different post-handshake messages are queued // for retransmission, the timer behaves correctly. When the second message is // queued, we reset the timer. This means a message never waits longer than the // minimum timer delay. The first message queued will be retransmitted a bit // more aggressively than it would otherwise, but this is unlikely to be a // problem. // We're currently supporting NewSessionTicker and KeyUpdate post-handshake // messages. // The filter will be dropping Key Update and New Session Ticket until disabled // When it's disabled, it will keep track of the New Session Ticket messages // sent by the server, and when it founds one - it will record the sequence // number This sequence number will then be provided to the second filter that // will be checking the list of ACKs seaching for this message class TLSSessionTicketAndKUDropper : public TlsRecordFilter { public: TLSSessionTicketAndKUDropper(const std::shared_ptr& a) : TlsRecordFilter(a), enabled_(true), sequenceNumberNST(0) {} void disable() { enabled_ = false; } uint64_t getSentSessionTicketSeqNum() { return sequenceNumberNST; } protected: PacketFilter::Action FilterRecord(const TlsRecordHeader& header, const DataBuffer& record, size_t* offset, DataBuffer* output) override { if (!header.is_protected()) { return KEEP; } uint16_t protection_epoch; uint8_t inner_content_type; DataBuffer plaintext; TlsRecordHeader out_header; if (!Unprotect(header, record, &protection_epoch, &inner_content_type, &plaintext, &out_header)) { return KEEP; } if (plaintext.data()[0] == ssl_hs_new_session_ticket) { if (enabled_) { return DROP; } else { sequenceNumberNST = out_header.sequence_number(); } } if (plaintext.data()[0] == ssl_hs_key_update && enabled_) { return DROP; } return KEEP; } private: bool enabled_; uint64_t sequenceNumberNST; }; class TLSACKRecorder : public TlsRecordFilter { public: TLSACKRecorder(const std::shared_ptr& a) : TlsRecordFilter(a), enabled_(false), isSeqNumFound(false), sequenceNumberToFindACKed(0) {} void EnableTLSACKCatcherWithSeqNum(uint64_t seqNum) { enabled_ = true; sequenceNumberToFindACKed = seqNum; } bool isNSTACKFound() { return isSeqNumFound; } protected: PacketFilter::Action FilterRecord(const TlsRecordHeader& header, const DataBuffer& record, size_t* offset, DataBuffer* output) override { if (!enabled_) { return KEEP; } if (!header.is_protected()) { return KEEP; } uint16_t protection_epoch; uint8_t inner_content_type; DataBuffer plaintext; TlsRecordHeader out_header; if (!Unprotect(header, record, &protection_epoch, &inner_content_type, &plaintext, &out_header)) { return KEEP; } if (plaintext.data() == NULL || plaintext.len() == 0) { return KEEP; } if (decrypting() && inner_content_type != ssl_ct_ack) { return KEEP; } uint8_t ack_message_header_len = 2; uint8_t ack_message_len_one_ACK = 16; size_t acks = plaintext.len() - ack_message_header_len; EXPECT_EQ((uint64_t)0, acks % ack_message_len_one_ACK); acks = acks / ack_message_len_one_ACK; // struct { // uint64 epoch; // uint64 sequence_number; // } RecordNumber; // sequenceNumberToFindACKed has 16 bits for epoch and 48 for seqNum uint64_t epoch = sequenceNumberToFindACKed >> 48; uint64_t seqNum = sequenceNumberToFindACKed & 0xFFFFFFFFFFFF; uint64_t lastByteEpoch = 0; uint64_t leastByteSequence = 0; for (size_t i = 0; i < acks; i++) { // Here we check that the last byte of the epoch and the last byte of the // sequence Because we just sent a couple of messages, so the values will // be less than 256. lastByteEpoch = plaintext.data()[2 + i * ack_message_len_one_ACK + 7]; leastByteSequence = plaintext.data()[2 + i * ack_message_len_one_ACK + 15]; if ((epoch % 256 == lastByteEpoch) && (seqNum % 256) == leastByteSequence) { isSeqNumFound = true; return KEEP; } } return KEEP; } private: bool enabled_; bool isSeqNumFound; uint64_t sequenceNumberToFindACKed; }; TEST_F(TlsConnectDatagram13, SendTicketThenKeyUpdate) { ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); Connect(); SendReceive(); // Need to read so that we absorb the session tickets. CheckKeys(); // Resume the connection. Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); ExpectResumption(RESUME_TICKET); auto filter = MakeTlsFilter(server_); filter->EnableDecryption(); auto ackRecorderFilter = MakeTlsFilter(client_); ackRecorderFilter->EnableDecryption(); // This should cause sending a NewSessionTicket (thus, starting the timer) // Sending the NewSessionTicket will be blocked by the filter Connect(); // Server sends Key Update // The first Key Update will also be dropped by the server EXPECT_EQ(SECSuccess, SSL_KeyUpdate(server_->ssl_fd(), false)); client_->ReadBytes(); // Check that the client indeed has not received the KU. SSLInt_SendImmediateACK(client_->ssl_fd()); server_->ReadBytes(); CheckEpochs(3, 3); // Disabling dropping the message, the effective retransmit will start filter->disable(); // So, we don't have to wait until the next retransmit happens ShiftDtlsTimers(); server_->ReadBytes(); // We get the Sequence number of the NewSessionTicket that the server has sent uint64_t sequenceNumberNST = filter->getSentSessionTicketSeqNum(); // And we check that the client has received (thus acked) the newly // retransmitted NewSessionTicket ackRecorderFilter->EnableTLSACKCatcherWithSeqNum(sequenceNumberNST); // Client Received NewSessionTicker and KU client_->ReadBytes(); SSLInt_SendImmediateACK(client_->ssl_fd()); server_->ReadBytes(); // Client and Server both received and processed KU CheckEpochs(3, 4); // Client has successfully received and sent an ACK for NST message EXPECT_EQ(true, ackRecorderFilter->isNSTACKFound()); SendReceive(50); } class TLSIthMessageSeqNumDropper : public TlsRecordFilter { public: TLSIthMessageSeqNumDropper(const std::shared_ptr& a, uint8_t messageSeqNumToDrop) : TlsRecordFilter(a), enabled_(true), messageSeqNumToDrop_(messageSeqNumToDrop), currentMessageSeqNum_(0) {} void disable() { enabled_ = false; } protected: PacketFilter::Action FilterRecord(const TlsRecordHeader& header, const DataBuffer& record, size_t* offset, DataBuffer* output) override { if (enabled_ && currentMessageSeqNum_ == messageSeqNumToDrop_) { enabled_ = false; uint16_t protection_epoch; uint8_t inner_content_type; DataBuffer plaintext; TlsRecordHeader out_header; if (!Unprotect(header, record, &protection_epoch, &inner_content_type, &plaintext, &out_header)) { return KEEP; } if (inner_content_type == ssl_ct_ack || inner_content_type == ssl_ct_application_data) { return KEEP; } return DROP; } currentMessageSeqNum_ += 1; return KEEP; } private: bool enabled_; uint8_t messageSeqNumToDrop_; uint8_t currentMessageSeqNum_; }; TEST_F(TlsConnectDatagram13, HandshakeDropIthMessageServer) { uint8_t maxServerMessageSeq = 10; for (uint8_t currMesSeqNum = 0; currMesSeqNum < maxServerMessageSeq; currMesSeqNum++) { EnsureTlsSetup(); auto filter = MakeTlsFilter(server_, currMesSeqNum); filter->EnableDecryption(); Connect(); SendReceive(); Reset(); } } TEST_F(TlsConnectDatagram13, HandshakeDropIthMessageClient) { uint8_t maxClientMessageSeq = 10; for (uint8_t currMesSeqNum = 0; currMesSeqNum < maxClientMessageSeq; currMesSeqNum++) { EnsureTlsSetup(); auto filter = MakeTlsFilter(client_, currMesSeqNum); filter->EnableDecryption(); Connect(); SendReceive(); Reset(); } } } // namespace nss_test