blob: 4ed169e51718a8289654cacb584194c9b2d58515 [file] [log] [blame] [edit]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <cstdint>
#include <memory>
#include <ostream>
#include <vector>
#include "third_party/boringssl/src/include/openssl/sha.h"
#include "net/third_party/quiche/src/quic/core/crypto/cert_compressor.h"
#include "net/third_party/quiche/src/quic/core/crypto/common_cert_set.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_utils.h"
#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
#include "net/third_party/quiche/src/quic/core/proto/crypto_server_config.proto.h"
#include "net/third_party/quiche/src/quic/core/quic_socket_address_coder.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/failing_proof_source.h"
#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
#include "net/third_party/quiche/src/quic/test_tools/mock_random.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_crypto_server_config_peer.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
namespace quic {
namespace test {
namespace {
class DummyProofVerifierCallback : public ProofVerifierCallback {
public:
DummyProofVerifierCallback() {}
~DummyProofVerifierCallback() override {}
void Run(bool ok,
const QuicString& error_details,
std::unique_ptr<ProofVerifyDetails>* details) override {
DCHECK(false);
}
};
const char kOldConfigId[] = "old-config-id";
} // namespace
struct TestParams {
TestParams(bool enable_stateless_rejects,
bool use_stateless_rejects,
ParsedQuicVersionVector supported_versions)
: enable_stateless_rejects(enable_stateless_rejects),
use_stateless_rejects(use_stateless_rejects),
supported_versions(std::move(supported_versions)) {}
friend std::ostream& operator<<(std::ostream& os, const TestParams& p) {
os << " enable_stateless_rejects: " << p.enable_stateless_rejects
<< std::endl;
os << " use_stateless_rejects: " << p.use_stateless_rejects << std::endl;
os << " versions: "
<< ParsedQuicVersionVectorToString(p.supported_versions) << " }";
return os;
}
// This only enables the stateless reject feature via the feature-flag.
// It does not force the crypto server to emit stateless rejects.
bool enable_stateless_rejects;
// If true, this forces the server to send a stateless reject when
// rejecting messages. This should be a no-op if
// enable_stateless_rejects is false.
bool use_stateless_rejects;
// Versions supported by client and server.
ParsedQuicVersionVector supported_versions;
};
// Constructs various test permutations.
std::vector<TestParams> GetTestParams() {
std::vector<TestParams> params;
static const bool kTrueFalse[] = {true, false};
for (bool enable_stateless_rejects : kTrueFalse) {
for (bool use_stateless_rejects : kTrueFalse) {
// Start with all versions, remove highest on each iteration.
ParsedQuicVersionVector supported_versions = AllSupportedVersions();
while (!supported_versions.empty()) {
params.push_back(TestParams(enable_stateless_rejects,
use_stateless_rejects, supported_versions));
supported_versions.erase(supported_versions.begin());
}
}
}
return params;
}
class CryptoServerTest : public QuicTestWithParam<TestParams> {
public:
CryptoServerTest()
: rand_(QuicRandom::GetInstance()),
client_address_(QuicIpAddress::Loopback4(), 1234),
client_version_(UnsupportedQuicVersion()),
config_(QuicCryptoServerConfig::TESTING,
rand_,
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default(),
TlsServerHandshaker::CreateSslCtx()),
peer_(&config_),
compressed_certs_cache_(
QuicCompressedCertsCache::kQuicCompressedCertsCacheSize),
params_(new QuicCryptoNegotiatedParameters),
signed_config_(new QuicSignedServerConfig),
chlo_packet_size_(kDefaultMaxPacketSize) {
supported_versions_ = GetParam().supported_versions;
config_.set_enable_serving_sct(true);
client_version_ = supported_versions_.front();
client_version_string_ = ParsedQuicVersionToString(client_version_);
SetQuicReloadableFlag(enable_quic_stateless_reject_support,
GetParam().enable_stateless_rejects);
use_stateless_rejects_ = GetParam().use_stateless_rejects;
}
void SetUp() override {
QuicCryptoServerConfig::ConfigOptions old_config_options;
old_config_options.id = kOldConfigId;
delete config_.AddDefaultConfig(rand_, &clock_, old_config_options);
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1000));
std::unique_ptr<QuicServerConfigProtobuf> primary_config(
config_.GenerateConfig(rand_, &clock_, config_options_));
primary_config->set_primary_time(clock_.WallNow().ToUNIXSeconds());
std::unique_ptr<CryptoHandshakeMessage> msg(
config_.AddConfig(std::move(primary_config), clock_.WallNow()));
QuicStringPiece orbit;
CHECK(msg->GetStringPiece(kORBT, &orbit));
CHECK_EQ(sizeof(orbit_), orbit.size());
memcpy(orbit_, orbit.data(), orbit.size());
char public_value[32];
memset(public_value, 42, sizeof(public_value));
nonce_hex_ = "#" + QuicTextUtils::HexEncode(GenerateNonce());
pub_hex_ =
"#" + QuicTextUtils::HexEncode(public_value, sizeof(public_value));
CryptoHandshakeMessage client_hello =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"CSCT", ""},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(client_hello);
// The message should be rejected because the source-address token is
// missing.
CheckRejectTag();
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
CheckForServerDesignatedConnectionId();
QuicStringPiece srct;
ASSERT_TRUE(out_.GetStringPiece(kSourceAddressTokenTag, &srct));
srct_hex_ = "#" + QuicTextUtils::HexEncode(srct);
QuicStringPiece scfg;
ASSERT_TRUE(out_.GetStringPiece(kSCFG, &scfg));
server_config_ = CryptoFramer::ParseMessage(scfg);
QuicStringPiece scid;
ASSERT_TRUE(server_config_->GetStringPiece(kSCID, &scid));
scid_hex_ = "#" + QuicTextUtils::HexEncode(scid);
signed_config_ = QuicReferenceCountedPointer<QuicSignedServerConfig>(
new QuicSignedServerConfig());
DCHECK(signed_config_->chain.get() == nullptr);
}
// Helper used to accept the result of ValidateClientHello and pass
// it on to ProcessClientHello.
class ValidateCallback : public ValidateClientHelloResultCallback {
public:
ValidateCallback(CryptoServerTest* test,
bool should_succeed,
const char* error_substr,
bool* called)
: test_(test),
should_succeed_(should_succeed),
error_substr_(error_substr),
called_(called) {
*called_ = false;
}
void Run(QuicReferenceCountedPointer<Result> result,
std::unique_ptr<ProofSource::Details> /* details */) override {
ASSERT_FALSE(*called_);
test_->ProcessValidationResult(std::move(result), should_succeed_,
error_substr_);
*called_ = true;
}
private:
CryptoServerTest* test_;
const bool should_succeed_;
const char* const error_substr_;
bool* called_;
};
void CheckServerHello(const CryptoHandshakeMessage& server_hello) {
QuicVersionLabelVector versions;
server_hello.GetVersionLabelList(kVER, &versions);
ASSERT_EQ(supported_versions_.size(), versions.size());
for (size_t i = 0; i < versions.size(); ++i) {
EXPECT_EQ(CreateQuicVersionLabel(supported_versions_[i]), versions[i]);
}
QuicStringPiece address;
ASSERT_TRUE(server_hello.GetStringPiece(kCADR, &address));
QuicSocketAddressCoder decoder;
ASSERT_TRUE(decoder.Decode(address.data(), address.size()));
EXPECT_EQ(client_address_.host(), decoder.ip());
EXPECT_EQ(client_address_.port(), decoder.port());
}
void ShouldSucceed(const CryptoHandshakeMessage& message) {
bool called = false;
QuicSocketAddress server_address(QuicIpAddress::Any4(), 5);
config_.ValidateClientHello(
message, client_address_.host(), server_address,
supported_versions_.front().transport_version, &clock_, signed_config_,
QuicMakeUnique<ValidateCallback>(this, true, "", &called));
EXPECT_TRUE(called);
}
void ShouldFailMentioning(const char* error_substr,
const CryptoHandshakeMessage& message) {
bool called = false;
ShouldFailMentioning(error_substr, message, &called);
EXPECT_TRUE(called);
}
void ShouldFailMentioning(const char* error_substr,
const CryptoHandshakeMessage& message,
bool* called) {
QuicSocketAddress server_address(QuicIpAddress::Any4(), 5);
config_.ValidateClientHello(
message, client_address_.host(), server_address,
supported_versions_.front().transport_version, &clock_, signed_config_,
QuicMakeUnique<ValidateCallback>(this, false, error_substr, called));
}
class ProcessCallback : public ProcessClientHelloResultCallback {
public:
ProcessCallback(
QuicReferenceCountedPointer<ValidateCallback::Result> result,
bool should_succeed,
const char* error_substr,
bool* called,
CryptoHandshakeMessage* out)
: result_(std::move(result)),
should_succeed_(should_succeed),
error_substr_(error_substr),
called_(called),
out_(out) {
*called_ = false;
}
void Run(
QuicErrorCode error,
const QuicString& error_details,
std::unique_ptr<CryptoHandshakeMessage> message,
std::unique_ptr<DiversificationNonce> diversification_nonce,
std::unique_ptr<ProofSource::Details> proof_source_details) override {
if (should_succeed_) {
ASSERT_EQ(error, QUIC_NO_ERROR)
<< "Message failed with error " << error_details << ": "
<< result_->client_hello.DebugString();
} else {
ASSERT_NE(error, QUIC_NO_ERROR)
<< "Message didn't fail: " << result_->client_hello.DebugString();
EXPECT_TRUE(error_details.find(error_substr_) != QuicString::npos)
<< error_substr_ << " not in " << error_details;
}
if (message != nullptr) {
*out_ = *message;
}
*called_ = true;
}
private:
const QuicReferenceCountedPointer<ValidateCallback::Result> result_;
const bool should_succeed_;
const char* const error_substr_;
bool* called_;
CryptoHandshakeMessage* out_;
};
void ProcessValidationResult(
QuicReferenceCountedPointer<ValidateCallback::Result> result,
bool should_succeed,
const char* error_substr) {
QuicSocketAddress server_address(QuicIpAddress::Any4(), 5);
QuicConnectionId server_designated_connection_id =
TestConnectionId(rand_for_id_generation_.RandUint64());
bool called;
config_.ProcessClientHello(
result, /*reject_only=*/false,
/*connection_id=*/TestConnectionId(1), server_address, client_address_,
supported_versions_.front(), supported_versions_,
use_stateless_rejects_, server_designated_connection_id, &clock_, rand_,
&compressed_certs_cache_, params_, signed_config_,
/*total_framing_overhead=*/50, chlo_packet_size_,
QuicMakeUnique<ProcessCallback>(result, should_succeed, error_substr,
&called, &out_));
EXPECT_TRUE(called);
}
QuicString GenerateNonce() {
QuicString nonce;
CryptoUtils::GenerateNonce(
clock_.WallNow(), rand_,
QuicStringPiece(reinterpret_cast<const char*>(orbit_), sizeof(orbit_)),
&nonce);
return nonce;
}
void CheckRejectReasons(
const HandshakeFailureReason* expected_handshake_failures,
size_t expected_count) {
QuicTagVector reject_reasons;
static_assert(sizeof(QuicTag) == sizeof(uint32_t), "header out of sync");
QuicErrorCode error_code = out_.GetTaglist(kRREJ, &reject_reasons);
ASSERT_EQ(QUIC_NO_ERROR, error_code);
EXPECT_EQ(expected_count, reject_reasons.size());
for (size_t i = 0; i < reject_reasons.size(); ++i) {
EXPECT_EQ(static_cast<QuicTag>(expected_handshake_failures[i]),
reject_reasons[i]);
}
}
// If the server is rejecting statelessly, make sure it contains a
// server-designated connection id. Once the check is complete,
// allow the random id-generator to move to the next value.
void CheckForServerDesignatedConnectionId() {
uint64_t server_designated_connection_id;
if (!RejectsAreStateless()) {
EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND,
out_.GetUint64(kRCID, &server_designated_connection_id));
} else {
ASSERT_EQ(QUIC_NO_ERROR,
out_.GetUint64(kRCID, &server_designated_connection_id));
server_designated_connection_id =
QuicEndian::NetToHost64(server_designated_connection_id);
EXPECT_EQ(rand_for_id_generation_.RandUint64(),
server_designated_connection_id);
}
rand_for_id_generation_.ChangeValue();
}
void CheckRejectTag() {
if (RejectsAreStateless()) {
ASSERT_EQ(kSREJ, out_.tag()) << QuicTagToString(out_.tag());
} else {
ASSERT_EQ(kREJ, out_.tag()) << QuicTagToString(out_.tag());
}
}
bool RejectsAreStateless() {
return GetParam().enable_stateless_rejects &&
GetParam().use_stateless_rejects;
}
QuicString XlctHexString() {
uint64_t xlct = crypto_test_utils::LeafCertHashForTesting();
return "#" + QuicTextUtils::HexEncode(reinterpret_cast<char*>(&xlct),
sizeof(xlct));
}
protected:
QuicRandom* const rand_;
MockRandom rand_for_id_generation_;
MockClock clock_;
QuicSocketAddress client_address_;
ParsedQuicVersionVector supported_versions_;
ParsedQuicVersion client_version_;
QuicString client_version_string_;
QuicCryptoServerConfig config_;
QuicCryptoServerConfigPeer peer_;
QuicCompressedCertsCache compressed_certs_cache_;
QuicCryptoServerConfig::ConfigOptions config_options_;
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
CryptoHandshakeMessage out_;
uint8_t orbit_[kOrbitSize];
bool use_stateless_rejects_;
size_t chlo_packet_size_;
// These strings contain hex escaped values from the server suitable for using
// when constructing client hello messages.
QuicString nonce_hex_, pub_hex_, srct_hex_, scid_hex_;
std::unique_ptr<CryptoHandshakeMessage> server_config_;
};
INSTANTIATE_TEST_SUITE_P(CryptoServerTests, CryptoServerTest,
::testing::ValuesIn(GetTestParams()));
TEST_P(CryptoServerTest, BadSNI) {
// clang-format off
static const char* const kBadSNIs[] = {
"",
"foo",
"#00",
"#ff00",
"127.0.0.1",
"ffee::1",
};
// clang-format on
for (size_t i = 0; i < QUIC_ARRAYSIZE(kBadSNIs); i++) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"SNI", kBadSNIs[i]},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldFailMentioning("SNI", msg);
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
}
TEST_P(CryptoServerTest, DefaultCert) {
// Check that the server replies with a default certificate when no SNI is
// specified. The CHLO is constructed to generate a REJ with certs, so must
// not contain a valid STK, and must include PDMD.
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"PDMD", "X509"},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
QuicStringPiece cert, proof, cert_sct;
EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert));
EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof));
EXPECT_TRUE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct));
EXPECT_NE(0u, cert.size());
EXPECT_NE(0u, proof.size());
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
EXPECT_LT(0u, cert_sct.size());
}
TEST_P(CryptoServerTest, RejectTooLarge) {
// Check that the server replies with no certificate when a CHLO is
// constructed with a PDMD but no SKT when the REJ would be too large.
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"PDMD", "X509"},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
// The REJ will be larger than the CHLO so no PROF or CRT will be sent.
config_.set_chlo_multiplier(1);
ShouldSucceed(msg);
QuicStringPiece cert, proof, cert_sct;
EXPECT_FALSE(out_.GetStringPiece(kCertificateTag, &cert));
EXPECT_FALSE(out_.GetStringPiece(kPROF, &proof));
EXPECT_FALSE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct));
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, RejectNotTooLarge) {
// When the CHLO packet is large enough, ensure that a full REJ is sent.
chlo_packet_size_ *= 2;
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"PDMD", "X509"},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
// The REJ will be larger than the CHLO so no PROF or CRT will be sent.
config_.set_chlo_multiplier(1);
ShouldSucceed(msg);
QuicStringPiece cert, proof, cert_sct;
EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert));
EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof));
EXPECT_TRUE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct));
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, RejectTooLargeButValidSTK) {
// Check that the server replies with no certificate when a CHLO is
// constructed with a PDMD but no SKT when the REJ would be too large.
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"#004b5453", srct_hex_},
{"PDMD", "X509"},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
// The REJ will be larger than the CHLO so no PROF or CRT will be sent.
config_.set_chlo_multiplier(1);
ShouldSucceed(msg);
QuicStringPiece cert, proof, cert_sct;
EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert));
EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof));
EXPECT_TRUE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct));
EXPECT_NE(0u, cert.size());
EXPECT_NE(0u, proof.size());
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, TooSmall) {
ShouldFailMentioning(
"too small",
crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"}, {"VER\0", client_version_string_.c_str()}}));
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, BadSourceAddressToken) {
// Invalid source-address tokens should be ignored.
// clang-format off
static const char* const kBadSourceAddressTokens[] = {
"",
"foo",
"#0000",
"#0000000000000000000000000000000000000000",
};
// clang-format on
for (size_t i = 0; i < QUIC_ARRAYSIZE(kBadSourceAddressTokens); i++) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"STK", kBadSourceAddressTokens[i]},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
}
TEST_P(CryptoServerTest, BadClientNonce) {
// clang-format off
static const char* const kBadNonces[] = {
"",
"#0000",
"#0000000000000000000000000000000000000000",
};
// clang-format on
for (size_t i = 0; i < QUIC_ARRAYSIZE(kBadNonces); i++) {
// Invalid nonces should be ignored, in an inchoate CHLO.
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"NONC", kBadNonces[i]},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
// Invalid nonces should result in CLIENT_NONCE_INVALID_FAILURE.
CryptoHandshakeMessage msg1 =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", kBadNonces[i]},
{"NONP", kBadNonces[i]},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg1);
CheckRejectTag();
const HandshakeFailureReason kRejectReasons1[] = {
CLIENT_NONCE_INVALID_FAILURE};
CheckRejectReasons(kRejectReasons1, QUIC_ARRAYSIZE(kRejectReasons1));
}
}
TEST_P(CryptoServerTest, NoClientNonce) {
// No client nonces should result in INCHOATE_HELLO_FAILURE.
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"}, {"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
CryptoHandshakeMessage msg1 =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg1);
CheckRejectTag();
const HandshakeFailureReason kRejectReasons1[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons1, QUIC_ARRAYSIZE(kRejectReasons1));
}
TEST_P(CryptoServerTest, DowngradeAttack) {
if (supported_versions_.size() == 1) {
// No downgrade attack is possible if the server only supports one version.
return;
}
// Set the client's preferred version to a supported version that
// is not the "current" version (supported_versions_.front()).
QuicString bad_version =
ParsedQuicVersionToString(supported_versions_.back());
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"}, {"VER\0", bad_version}}, kClientHelloMinimumSize);
ShouldFailMentioning("Downgrade", msg);
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, CorruptServerConfig) {
// This tests corrupted server config.
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", (QuicString(1, 'X') + scid_hex_)},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
CheckRejectTag();
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, CorruptSourceAddressToken) {
// This tests corrupted source address token.
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", (QuicString(1, 'X') + srct_hex_)},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
CheckRejectTag();
const HandshakeFailureReason kRejectReasons[] = {
SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, CorruptSourceAddressTokenIsStillAccepted) {
// This tests corrupted source address token.
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", (QuicString(1, 'X') + srct_hex_)},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
config_.set_validate_source_address_token(false);
ShouldSucceed(msg);
EXPECT_EQ(kSHLO, out_.tag());
}
TEST_P(CryptoServerTest, CorruptClientNonceAndSourceAddressToken) {
// This test corrupts client nonce and source address token.
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", (QuicString(1, 'X') + srct_hex_)},
{"PUBS", pub_hex_},
{"NONC", (QuicString(1, 'X') + nonce_hex_)},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
CheckRejectTag();
const HandshakeFailureReason kRejectReasons[] = {
SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, CLIENT_NONCE_INVALID_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, CorruptMultipleTags) {
// This test corrupts client nonce, server nonce and source address token.
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", (QuicString(1, 'X') + srct_hex_)},
{"PUBS", pub_hex_},
{"NONC", (QuicString(1, 'X') + nonce_hex_)},
{"NONP", (QuicString(1, 'X') + nonce_hex_)},
{"SNO\0", (QuicString(1, 'X') + nonce_hex_)},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
CheckRejectTag();
const HandshakeFailureReason kRejectReasons[] = {
SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, CLIENT_NONCE_INVALID_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, NoServerNonce) {
// When no server nonce is present and no strike register is configured,
// the CHLO should be rejected.
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"NONP", nonce_hex_},
{"XLCT", XlctHexString()},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
// Even without a server nonce, this ClientHello should be accepted in
// version 33.
ASSERT_EQ(kSHLO, out_.tag());
CheckServerHello(out_);
}
TEST_P(CryptoServerTest, ProofForSuppliedServerConfig) {
client_address_ = QuicSocketAddress(QuicIpAddress::Loopback6(), 1234);
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PDMD", "X509"},
{"SCID", kOldConfigId},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"NONP", "123456789012345678901234567890"},
{"VER\0", client_version_string_},
{"XLCT", XlctHexString()}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
// The message should be rejected because the source-address token is no
// longer valid.
CheckRejectTag();
const HandshakeFailureReason kRejectReasons[] = {
SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
QuicStringPiece cert, proof, scfg_str;
EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert));
EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof));
EXPECT_TRUE(out_.GetStringPiece(kSCFG, &scfg_str));
std::unique_ptr<CryptoHandshakeMessage> scfg(
CryptoFramer::ParseMessage(scfg_str));
QuicStringPiece scid;
EXPECT_TRUE(scfg->GetStringPiece(kSCID, &scid));
EXPECT_NE(scid, kOldConfigId);
// Get certs from compressed certs.
const CommonCertSets* common_cert_sets(CommonCertSets::GetInstanceQUIC());
std::vector<QuicString> cached_certs;
std::vector<QuicString> certs;
ASSERT_TRUE(CertCompressor::DecompressChain(cert, cached_certs,
common_cert_sets, &certs));
// Check that the proof in the REJ message is valid.
std::unique_ptr<ProofVerifier> proof_verifier(
crypto_test_utils::ProofVerifierForTesting());
std::unique_ptr<ProofVerifyContext> verify_context(
crypto_test_utils::ProofVerifyContextForTesting());
std::unique_ptr<ProofVerifyDetails> details;
QuicString error_details;
std::unique_ptr<ProofVerifierCallback> callback(
new DummyProofVerifierCallback());
QuicString chlo_hash;
CryptoUtils::HashHandshakeMessage(msg, &chlo_hash, Perspective::IS_SERVER);
EXPECT_EQ(QUIC_SUCCESS,
proof_verifier->VerifyProof(
"test.example.com", 443, (QuicString(scfg_str)),
client_version_.transport_version, chlo_hash, certs, "",
(QuicString(proof)), verify_context.get(), &error_details,
&details, std::move(callback)));
}
TEST_P(CryptoServerTest, RejectInvalidXlct) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"VER\0", client_version_string_},
{"XLCT", "#0102030405060708"}},
kClientHelloMinimumSize);
// If replay protection isn't disabled, then
// QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false
// and cause ProcessClientHello to exit early (and generate a REJ message).
config_.set_replay_protection(false);
ShouldSucceed(msg);
const HandshakeFailureReason kRejectReasons[] = {
INVALID_EXPECTED_LEAF_CERTIFICATE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
TEST_P(CryptoServerTest, ValidXlct) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"VER\0", client_version_string_},
{"XLCT", XlctHexString()}},
kClientHelloMinimumSize);
// If replay protection isn't disabled, then
// QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false
// and cause ProcessClientHello to exit early (and generate a REJ message).
config_.set_replay_protection(false);
ShouldSucceed(msg);
EXPECT_EQ(kSHLO, out_.tag());
}
TEST_P(CryptoServerTest, NonceInSHLO) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"VER\0", client_version_string_},
{"XLCT", XlctHexString()}},
kClientHelloMinimumSize);
// If replay protection isn't disabled, then
// QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false
// and cause ProcessClientHello to exit early (and generate a REJ message).
config_.set_replay_protection(false);
ShouldSucceed(msg);
EXPECT_EQ(kSHLO, out_.tag());
QuicStringPiece nonce;
EXPECT_TRUE(out_.GetStringPiece(kServerNonceTag, &nonce));
}
TEST_P(CryptoServerTest, ProofSourceFailure) {
// Install a ProofSource which will unconditionally fail
peer_.ResetProofSource(std::unique_ptr<ProofSource>(new FailingProofSource));
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"PDMD", "X509"},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
// Just ensure that we don't crash as occurred in b/33916924.
ShouldFailMentioning("", msg);
}
// Regression test for crbug.com/723604
// For 2RTT, if the first CHLO from the client contains hashes of cached
// certs (stored in CCRT tag) but the second CHLO does not, then the second REJ
// from the server should not contain hashes of cached certs.
TEST_P(CryptoServerTest, TwoRttServerDropCachedCerts) {
// Send inchoate CHLO to get cert chain from server. This CHLO is only for
// the purpose of getting the server's certs; it is not part of the 2RTT
// handshake.
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"}, {"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldSucceed(msg);
// Decompress cert chain from server to individual certs.
QuicStringPiece certs_compressed;
ASSERT_TRUE(out_.GetStringPiece(kCertificateTag, &certs_compressed));
ASSERT_NE(0u, certs_compressed.size());
std::vector<QuicString> certs;
ASSERT_TRUE(CertCompressor::DecompressChain(
certs_compressed, /*cached_certs=*/{}, /*common_sets=*/nullptr, &certs));
// Start 2-RTT. Client sends CHLO with bad source-address token and hashes of
// the certs, which tells the server that the client has cached those certs.
config_.set_chlo_multiplier(1);
const char kBadSourceAddressToken[] = "";
msg.SetStringPiece(kSourceAddressTokenTag, kBadSourceAddressToken);
std::vector<uint64_t> hashes(certs.size());
for (size_t i = 0; i < certs.size(); ++i) {
hashes[i] = QuicUtils::QuicUtils::FNV1a_64_Hash(certs[i]);
}
msg.SetVector(kCCRT, hashes);
ShouldSucceed(msg);
// Server responds with inchoate REJ containing valid source-address token.
QuicStringPiece srct;
ASSERT_TRUE(out_.GetStringPiece(kSourceAddressTokenTag, &srct));
// Client now drops cached certs; sends CHLO with updated source-address
// token but no hashes of certs.
msg.SetStringPiece(kSourceAddressTokenTag, srct);
msg.Erase(kCCRT);
ShouldSucceed(msg);
// Server response's cert chain should not contain hashes of
// previously-cached certs.
ASSERT_TRUE(out_.GetStringPiece(kCertificateTag, &certs_compressed));
ASSERT_NE(0u, certs_compressed.size());
ASSERT_TRUE(CertCompressor::DecompressChain(
certs_compressed, /*cached_certs=*/{}, /*common_sets=*/nullptr, &certs));
}
class CryptoServerConfigGenerationTest : public QuicTest {};
TEST_F(CryptoServerConfigGenerationTest, Determinism) {
// Test that using a deterministic PRNG causes the server-config to be
// deterministic.
MockRandom rand_a, rand_b;
const QuicCryptoServerConfig::ConfigOptions options;
MockClock clock;
QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a,
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default(),
TlsServerHandshaker::CreateSslCtx());
QuicCryptoServerConfig b(QuicCryptoServerConfig::TESTING, &rand_b,
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default(),
TlsServerHandshaker::CreateSslCtx());
std::unique_ptr<CryptoHandshakeMessage> scfg_a(
a.AddDefaultConfig(&rand_a, &clock, options));
std::unique_ptr<CryptoHandshakeMessage> scfg_b(
b.AddDefaultConfig(&rand_b, &clock, options));
ASSERT_EQ(scfg_a->DebugString(), scfg_b->DebugString());
}
TEST_F(CryptoServerConfigGenerationTest, SCIDVaries) {
// This test ensures that the server config ID varies for different server
// configs.
MockRandom rand_a, rand_b;
const QuicCryptoServerConfig::ConfigOptions options;
MockClock clock;
QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a,
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default(),
TlsServerHandshaker::CreateSslCtx());
rand_b.ChangeValue();
QuicCryptoServerConfig b(QuicCryptoServerConfig::TESTING, &rand_b,
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default(),
TlsServerHandshaker::CreateSslCtx());
std::unique_ptr<CryptoHandshakeMessage> scfg_a(
a.AddDefaultConfig(&rand_a, &clock, options));
std::unique_ptr<CryptoHandshakeMessage> scfg_b(
b.AddDefaultConfig(&rand_b, &clock, options));
QuicStringPiece scid_a, scid_b;
EXPECT_TRUE(scfg_a->GetStringPiece(kSCID, &scid_a));
EXPECT_TRUE(scfg_b->GetStringPiece(kSCID, &scid_b));
EXPECT_NE(scid_a, scid_b);
}
TEST_F(CryptoServerConfigGenerationTest, SCIDIsHashOfServerConfig) {
MockRandom rand_a;
const QuicCryptoServerConfig::ConfigOptions options;
MockClock clock;
QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a,
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default(),
TlsServerHandshaker::CreateSslCtx());
std::unique_ptr<CryptoHandshakeMessage> scfg(
a.AddDefaultConfig(&rand_a, &clock, options));
QuicStringPiece scid;
EXPECT_TRUE(scfg->GetStringPiece(kSCID, &scid));
// Need to take a copy of |scid| has we're about to call |Erase|.
const QuicString scid_str(scid);
scfg->Erase(kSCID);
scfg->MarkDirty();
const QuicData& serialized(scfg->GetSerialized());
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const uint8_t*>(serialized.data()),
serialized.length(), digest);
// scid is a SHA-256 hash, truncated to 16 bytes.
ASSERT_EQ(scid.size(), 16u);
EXPECT_EQ(0, memcmp(digest, scid_str.c_str(), scid.size()));
}
class CryptoServerTestNoConfig : public CryptoServerTest {
public:
void SetUp() override {
// Deliberately don't add a config so that we can test this situation.
}
};
TEST_P(CryptoServerTestNoConfig, DontCrash) {
CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO(
{{"PDMD", "X509"}, {"VER\0", client_version_string_}},
kClientHelloMinimumSize);
ShouldFailMentioning("No config", msg);
const HandshakeFailureReason kRejectReasons[] = {
SERVER_CONFIG_INCHOATE_HELLO_FAILURE};
CheckRejectReasons(kRejectReasons, QUIC_ARRAYSIZE(kRejectReasons));
}
class CryptoServerTestOldVersion : public CryptoServerTest {
public:
void SetUp() override {
client_version_ = supported_versions_.back();
client_version_string_ = ParsedQuicVersionToString(client_version_);
CryptoServerTest::SetUp();
}
};
TEST_P(CryptoServerTestOldVersion, ServerIgnoresXlct) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"VER\0", client_version_string_},
{"XLCT", "#0100000000000000"}},
kClientHelloMinimumSize);
// If replay protection isn't disabled, then
// QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false
// and cause ProcessClientHello to exit early (and generate a REJ message).
config_.set_replay_protection(false);
ShouldSucceed(msg);
EXPECT_EQ(kSHLO, out_.tag());
}
TEST_P(CryptoServerTestOldVersion, XlctNotRequired) {
CryptoHandshakeMessage msg =
crypto_test_utils::CreateCHLO({{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"SCID", scid_hex_},
{"#004b5453", srct_hex_},
{"PUBS", pub_hex_},
{"NONC", nonce_hex_},
{"VER\0", client_version_string_}},
kClientHelloMinimumSize);
// If replay protection isn't disabled, then
// QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false
// and cause ProcessClientHello to exit early (and generate a REJ message).
config_.set_replay_protection(false);
ShouldSucceed(msg);
EXPECT_EQ(kSHLO, out_.tag());
}
} // namespace test
} // namespace quic