зеркало из https://github.com/mozilla/gecko-dev.git
469 строки
16 KiB
C++
469 строки
16 KiB
C++
/* -*- 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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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(ssl_ct_application_data,
|
|
client_records->record(1).header.content_type());
|
|
|
|
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(ssl_ct_application_data,
|
|
server_records->record(i).header.content_type());
|
|
}
|
|
}
|
|
|
|
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(ssl_ct_application_data,
|
|
server_records->record(i).header.content_type());
|
|
}
|
|
|
|
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
|