blob: 4df4f8f3cb2a4bb44f2cdca96ec04a7c32f236c6 [file] [log] [blame]
// Copyright (c) 2012 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 "quiche/quic/test_tools/crypto_test_utils.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "openssl/bn.h"
#include "openssl/ec.h"
#include "openssl/ecdsa.h"
#include "openssl/nid.h"
#include "openssl/sha.h"
#include "quiche/quic/core/crypto/certificate_view.h"
#include "quiche/quic/core/crypto/channel_id.h"
#include "quiche/quic/core/crypto/crypto_handshake.h"
#include "quiche/quic/core/crypto/crypto_utils.h"
#include "quiche/quic/core/crypto/proof_source_x509.h"
#include "quiche/quic/core/crypto/quic_crypto_server_config.h"
#include "quiche/quic/core/crypto/quic_decrypter.h"
#include "quiche/quic/core/crypto/quic_encrypter.h"
#include "quiche/quic/core/crypto/quic_random.h"
#include "quiche/quic/core/proto/crypto_server_config_proto.h"
#include "quiche/quic/core/quic_clock.h"
#include "quiche/quic/core/quic_crypto_client_stream.h"
#include "quiche/quic/core/quic_crypto_server_stream_base.h"
#include "quiche/quic/core/quic_crypto_stream.h"
#include "quiche/quic/core/quic_server_id.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/core/quic_versions.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
#include "quiche/quic/platform/api/quic_hostname_utils.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/quic_connection_peer.h"
#include "quiche/quic/test_tools/quic_framer_peer.h"
#include "quiche/quic/test_tools/quic_stream_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
#include "quiche/quic/test_tools/simple_quic_framer.h"
#include "quiche/quic/test_tools/test_certificates.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
namespace quic {
namespace test {
namespace crypto_test_utils {
namespace {
using testing::_;
// CryptoFramerVisitor is a framer visitor that records handshake messages.
class CryptoFramerVisitor : public CryptoFramerVisitorInterface {
public:
CryptoFramerVisitor() : error_(false) {}
void OnError(CryptoFramer* /*framer*/) override { error_ = true; }
void OnHandshakeMessage(const CryptoHandshakeMessage& message) override {
messages_.push_back(message);
}
bool error() const { return error_; }
const std::vector<CryptoHandshakeMessage>& messages() const {
return messages_;
}
private:
bool error_;
std::vector<CryptoHandshakeMessage> messages_;
};
// HexChar parses |c| as a hex character. If valid, it sets |*value| to the
// value of the hex character and returns true. Otherwise it returns false.
bool HexChar(char c, uint8_t* value) {
if (c >= '0' && c <= '9') {
*value = c - '0';
return true;
}
if (c >= 'a' && c <= 'f') {
*value = c - 'a' + 10;
return true;
}
if (c >= 'A' && c <= 'F') {
*value = c - 'A' + 10;
return true;
}
return false;
}
} // anonymous namespace
FakeClientOptions::FakeClientOptions() {}
FakeClientOptions::~FakeClientOptions() {}
namespace {
// This class is used by GenerateFullCHLO() to extract SCID and STK from
// REJ and to construct a full CHLO with these fields and given inchoate
// CHLO.
class FullChloGenerator {
public:
FullChloGenerator(
QuicCryptoServerConfig* crypto_config, QuicSocketAddress server_addr,
QuicSocketAddress client_addr, const QuicClock* clock,
ParsedQuicVersion version,
quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig>
signed_config,
QuicCompressedCertsCache* compressed_certs_cache,
CryptoHandshakeMessage* out)
: crypto_config_(crypto_config),
server_addr_(server_addr),
client_addr_(client_addr),
clock_(clock),
version_(version),
signed_config_(signed_config),
compressed_certs_cache_(compressed_certs_cache),
out_(out),
params_(new QuicCryptoNegotiatedParameters) {}
class ValidateClientHelloCallback : public ValidateClientHelloResultCallback {
public:
explicit ValidateClientHelloCallback(FullChloGenerator* generator)
: generator_(generator) {}
void Run(quiche::QuicheReferenceCountedPointer<
ValidateClientHelloResultCallback::Result>
result,
std::unique_ptr<ProofSource::Details> /* details */) override {
generator_->ValidateClientHelloDone(std::move(result));
}
private:
FullChloGenerator* generator_;
};
std::unique_ptr<ValidateClientHelloCallback>
GetValidateClientHelloCallback() {
return std::make_unique<ValidateClientHelloCallback>(this);
}
private:
void ValidateClientHelloDone(quiche::QuicheReferenceCountedPointer<
ValidateClientHelloResultCallback::Result>
result) {
result_ = result;
crypto_config_->ProcessClientHello(
result_, /*reject_only=*/false, TestConnectionId(1), server_addr_,
client_addr_, version_, {version_}, clock_, QuicRandom::GetInstance(),
compressed_certs_cache_, params_, signed_config_,
/*total_framing_overhead=*/50, kDefaultMaxPacketSize,
GetProcessClientHelloCallback());
}
class ProcessClientHelloCallback : public ProcessClientHelloResultCallback {
public:
explicit ProcessClientHelloCallback(FullChloGenerator* generator)
: generator_(generator) {}
void Run(QuicErrorCode error, const std::string& error_details,
std::unique_ptr<CryptoHandshakeMessage> message,
std::unique_ptr<DiversificationNonce> /*diversification_nonce*/,
std::unique_ptr<ProofSource::Details> /*proof_source_details*/)
override {
ASSERT_TRUE(message) << QuicErrorCodeToString(error) << " "
<< error_details;
generator_->ProcessClientHelloDone(std::move(message));
}
private:
FullChloGenerator* generator_;
};
std::unique_ptr<ProcessClientHelloCallback> GetProcessClientHelloCallback() {
return std::make_unique<ProcessClientHelloCallback>(this);
}
void ProcessClientHelloDone(std::unique_ptr<CryptoHandshakeMessage> rej) {
// Verify output is a REJ.
EXPECT_THAT(rej->tag(), testing::Eq(kREJ));
QUIC_VLOG(1) << "Extract valid STK and SCID from\n" << rej->DebugString();
absl::string_view srct;
ASSERT_TRUE(rej->GetStringPiece(kSourceAddressTokenTag, &srct));
absl::string_view scfg;
ASSERT_TRUE(rej->GetStringPiece(kSCFG, &scfg));
std::unique_ptr<CryptoHandshakeMessage> server_config(
CryptoFramer::ParseMessage(scfg));
absl::string_view scid;
ASSERT_TRUE(server_config->GetStringPiece(kSCID, &scid));
*out_ = result_->client_hello;
out_->SetStringPiece(kSCID, scid);
out_->SetStringPiece(kSourceAddressTokenTag, srct);
uint64_t xlct = LeafCertHashForTesting();
out_->SetValue(kXLCT, xlct);
}
protected:
QuicCryptoServerConfig* crypto_config_;
QuicSocketAddress server_addr_;
QuicSocketAddress client_addr_;
const QuicClock* clock_;
ParsedQuicVersion version_;
quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
QuicCompressedCertsCache* compressed_certs_cache_;
CryptoHandshakeMessage* out_;
quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
quiche::QuicheReferenceCountedPointer<
ValidateClientHelloResultCallback::Result>
result_;
};
} // namespace
std::unique_ptr<QuicCryptoServerConfig> CryptoServerConfigForTesting() {
return std::make_unique<QuicCryptoServerConfig>(
QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(),
ProofSourceForTesting(), KeyExchangeSource::Default());
}
int HandshakeWithFakeServer(QuicConfig* server_quic_config,
QuicCryptoServerConfig* crypto_config,
MockQuicConnectionHelper* helper,
MockAlarmFactory* alarm_factory,
PacketSavingConnection* client_conn,
QuicCryptoClientStreamBase* client,
std::string alpn) {
auto* server_conn = new testing::NiceMock<PacketSavingConnection>(
helper, alarm_factory, Perspective::IS_SERVER,
ParsedVersionOfIndex(client_conn->supported_versions(), 0));
QuicCompressedCertsCache compressed_certs_cache(
QuicCompressedCertsCache::kQuicCompressedCertsCacheSize);
SetupCryptoServerConfigForTest(
server_conn->clock(), server_conn->random_generator(), crypto_config);
TestQuicSpdyServerSession server_session(
server_conn, *server_quic_config, client_conn->supported_versions(),
crypto_config, &compressed_certs_cache);
// Call SetServerApplicationStateForResumption so that the fake server
// supports 0-RTT in TLS.
server_session.Initialize();
server_session.GetMutableCryptoStream()
->SetServerApplicationStateForResumption(
std::make_unique<ApplicationState>());
EXPECT_CALL(*server_session.helper(),
CanAcceptClientHello(testing::_, testing::_, testing::_,
testing::_, testing::_))
.Times(testing::AnyNumber());
EXPECT_CALL(*server_conn, OnCanWrite()).Times(testing::AnyNumber());
EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber());
EXPECT_CALL(*server_conn, SendCryptoData(_, _, _))
.Times(testing::AnyNumber());
EXPECT_CALL(server_session, SelectAlpn(_))
.WillRepeatedly([alpn](const std::vector<absl::string_view>& alpns) {
return std::find(alpns.cbegin(), alpns.cend(), alpn);
});
// The client's handshake must have been started already.
QUICHE_CHECK_NE(0u, client_conn->encrypted_packets_.size());
CommunicateHandshakeMessages(client_conn, client, server_conn,
server_session.GetMutableCryptoStream());
if (client_conn->connected() && server_conn->connected()) {
CompareClientAndServerKeys(client, server_session.GetMutableCryptoStream());
}
return client->num_sent_client_hellos();
}
int HandshakeWithFakeClient(MockQuicConnectionHelper* helper,
MockAlarmFactory* alarm_factory,
PacketSavingConnection* server_conn,
QuicCryptoServerStreamBase* server,
const QuicServerId& server_id,
const FakeClientOptions& options,
std::string alpn) {
// This function does not do version negotiation; read the supported versions
// directly from the server connection instead.
ParsedQuicVersionVector supported_versions =
server_conn->supported_versions();
if (options.only_tls_versions) {
supported_versions.erase(
std::remove_if(supported_versions.begin(), supported_versions.end(),
[](const ParsedQuicVersion& version) {
return version.handshake_protocol != PROTOCOL_TLS1_3;
}),
supported_versions.end());
QUICHE_CHECK(!options.only_quic_crypto_versions);
} else if (options.only_quic_crypto_versions) {
supported_versions.erase(
std::remove_if(supported_versions.begin(), supported_versions.end(),
[](const ParsedQuicVersion& version) {
return version.handshake_protocol !=
PROTOCOL_QUIC_CRYPTO;
}),
supported_versions.end());
}
PacketSavingConnection* client_conn = new PacketSavingConnection(
helper, alarm_factory, Perspective::IS_CLIENT, supported_versions);
// Advance the time, because timers do not like uninitialized times.
client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(1));
QuicCryptoClientConfig crypto_config(ProofVerifierForTesting());
TestQuicSpdyClientSession client_session(client_conn, DefaultQuicConfig(),
supported_versions, server_id,
&crypto_config);
EXPECT_CALL(client_session, OnProofValid(testing::_))
.Times(testing::AnyNumber());
EXPECT_CALL(client_session, OnProofVerifyDetailsAvailable(testing::_))
.Times(testing::AnyNumber());
EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber());
if (!alpn.empty()) {
EXPECT_CALL(client_session, GetAlpnsToOffer())
.WillRepeatedly(testing::Return(std::vector<std::string>({alpn})));
} else {
EXPECT_CALL(client_session, GetAlpnsToOffer())
.WillRepeatedly(testing::Return(std::vector<std::string>(
{AlpnForVersion(client_conn->version())})));
}
client_session.GetMutableCryptoStream()->CryptoConnect();
QUICHE_CHECK_EQ(1u, client_conn->encrypted_packets_.size());
CommunicateHandshakeMessages(client_conn,
client_session.GetMutableCryptoStream(),
server_conn, server);
if (server->one_rtt_keys_available() && server->encryption_established()) {
CompareClientAndServerKeys(client_session.GetMutableCryptoStream(), server);
}
return client_session.GetCryptoStream()->num_sent_client_hellos();
}
void SetupCryptoServerConfigForTest(const QuicClock* clock, QuicRandom* rand,
QuicCryptoServerConfig* crypto_config) {
QuicCryptoServerConfig::ConfigOptions options;
options.channel_id_enabled = true;
std::unique_ptr<CryptoHandshakeMessage> scfg =
crypto_config->AddDefaultConfig(rand, clock, options);
}
void SendHandshakeMessageToStream(QuicCryptoStream* stream,
const CryptoHandshakeMessage& message,
Perspective /*perspective*/) {
const QuicData& data = message.GetSerialized();
QuicSession* session = QuicStreamPeer::session(stream);
if (!QuicVersionUsesCryptoFrames(session->transport_version())) {
QuicStreamFrame frame(
QuicUtils::GetCryptoStreamId(session->transport_version()), false,
stream->crypto_bytes_read(), data.AsStringPiece());
stream->OnStreamFrame(frame);
} else {
EncryptionLevel level = session->connection()->last_decrypted_level();
QuicCryptoFrame frame(level, stream->BytesReadOnLevel(level),
data.AsStringPiece());
stream->OnCryptoFrame(frame);
}
}
void CommunicateHandshakeMessages(PacketSavingConnection* client_conn,
QuicCryptoStream* client,
PacketSavingConnection* server_conn,
QuicCryptoStream* server) {
size_t client_i = 0, server_i = 0;
while (client_conn->connected() && server_conn->connected() &&
(!client->one_rtt_keys_available() ||
!server->one_rtt_keys_available())) {
ASSERT_GT(client_conn->encrypted_packets_.size(), client_i);
QUIC_LOG(INFO) << "Processing "
<< client_conn->encrypted_packets_.size() - client_i
<< " packets client->server";
MovePackets(client_conn, &client_i, server, server_conn,
Perspective::IS_SERVER, /*process_stream_data=*/false);
if (client->one_rtt_keys_available() && server->one_rtt_keys_available() &&
server_conn->encrypted_packets_.size() == server_i) {
break;
}
ASSERT_GT(server_conn->encrypted_packets_.size(), server_i);
QUIC_LOG(INFO) << "Processing "
<< server_conn->encrypted_packets_.size() - server_i
<< " packets server->client";
MovePackets(server_conn, &server_i, client, client_conn,
Perspective::IS_CLIENT, /*process_stream_data=*/false);
}
}
bool CommunicateHandshakeMessagesUntil(PacketSavingConnection* client_conn,
QuicCryptoStream* client,
std::function<bool()> client_condition,
PacketSavingConnection* server_conn,
QuicCryptoStream* server,
std::function<bool()> server_condition,
bool process_stream_data) {
size_t client_next_packet_to_deliver =
client_conn->number_of_packets_delivered_;
size_t server_next_packet_to_deliver =
server_conn->number_of_packets_delivered_;
while (
client_conn->connected() && server_conn->connected() &&
(!client_condition() || !server_condition()) &&
(client_conn->encrypted_packets_.size() > client_next_packet_to_deliver ||
server_conn->encrypted_packets_.size() >
server_next_packet_to_deliver)) {
if (!server_condition()) {
QUIC_LOG(INFO) << "Processing "
<< client_conn->encrypted_packets_.size() -
client_next_packet_to_deliver
<< " packets client->server";
MovePackets(client_conn, &client_next_packet_to_deliver, server,
server_conn, Perspective::IS_SERVER, process_stream_data);
}
if (!client_condition()) {
QUIC_LOG(INFO) << "Processing "
<< server_conn->encrypted_packets_.size() -
server_next_packet_to_deliver
<< " packets server->client";
MovePackets(server_conn, &server_next_packet_to_deliver, client,
client_conn, Perspective::IS_CLIENT, process_stream_data);
}
}
client_conn->number_of_packets_delivered_ = client_next_packet_to_deliver;
server_conn->number_of_packets_delivered_ = server_next_packet_to_deliver;
bool result = client_condition() && server_condition();
if (!result) {
QUIC_LOG(INFO) << "CommunicateHandshakeMessagesUnti failed with state: "
"client connected? "
<< client_conn->connected() << " server connected? "
<< server_conn->connected() << " client condition met? "
<< client_condition() << " server condition met? "
<< server_condition();
}
return result;
}
std::pair<size_t, size_t> AdvanceHandshake(PacketSavingConnection* client_conn,
QuicCryptoStream* client,
size_t client_i,
PacketSavingConnection* server_conn,
QuicCryptoStream* server,
size_t server_i) {
if (client_conn->encrypted_packets_.size() != client_i) {
QUIC_LOG(INFO) << "Processing "
<< client_conn->encrypted_packets_.size() - client_i
<< " packets client->server";
MovePackets(client_conn, &client_i, server, server_conn,
Perspective::IS_SERVER, /*process_stream_data=*/false);
}
if (server_conn->encrypted_packets_.size() != server_i) {
QUIC_LOG(INFO) << "Processing "
<< server_conn->encrypted_packets_.size() - server_i
<< " packets server->client";
MovePackets(server_conn, &server_i, client, client_conn,
Perspective::IS_CLIENT, /*process_stream_data=*/false);
}
return std::make_pair(client_i, server_i);
}
std::string GetValueForTag(const CryptoHandshakeMessage& message, QuicTag tag) {
auto it = message.tag_value_map().find(tag);
if (it == message.tag_value_map().end()) {
return std::string();
}
return it->second;
}
uint64_t LeafCertHashForTesting() {
quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain;
QuicSocketAddress server_address(QuicIpAddress::Any4(), 42);
QuicSocketAddress client_address(QuicIpAddress::Any4(), 43);
QuicCryptoProof proof;
std::unique_ptr<ProofSource> proof_source(ProofSourceForTesting());
class Callback : public ProofSource::Callback {
public:
Callback(bool* ok,
quiche::QuicheReferenceCountedPointer<ProofSource::Chain>* chain)
: ok_(ok), chain_(chain) {}
void Run(
bool ok,
const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain,
const QuicCryptoProof& /* proof */,
std::unique_ptr<ProofSource::Details> /* details */) override {
*ok_ = ok;
*chain_ = chain;
}
private:
bool* ok_;
quiche::QuicheReferenceCountedPointer<ProofSource::Chain>* chain_;
};
// Note: relies on the callback being invoked synchronously
bool ok = false;
proof_source->GetProof(
server_address, client_address, "", "",
AllSupportedVersionsWithQuicCrypto().front().transport_version, "",
std::unique_ptr<ProofSource::Callback>(new Callback(&ok, &chain)));
if (!ok || chain->certs.empty()) {
QUICHE_DCHECK(false) << "Proof generation failed";
return 0;
}
return QuicUtils::FNV1a_64_Hash(chain->certs.at(0));
}
void FillInDummyReject(CryptoHandshakeMessage* rej) {
rej->set_tag(kREJ);
// Minimum SCFG that passes config validation checks.
// clang-format off
unsigned char scfg[] = {
// SCFG
0x53, 0x43, 0x46, 0x47,
// num entries
0x01, 0x00,
// padding
0x00, 0x00,
// EXPY
0x45, 0x58, 0x50, 0x59,
// EXPY end offset
0x08, 0x00, 0x00, 0x00,
// Value
'1', '2', '3', '4',
'5', '6', '7', '8'
};
// clang-format on
rej->SetValue(kSCFG, scfg);
rej->SetStringPiece(kServerNonceTag, "SERVER_NONCE");
int64_t ttl = 2 * 24 * 60 * 60;
rej->SetValue(kSTTL, ttl);
std::vector<QuicTag> reject_reasons;
reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE);
rej->SetVector(kRREJ, reject_reasons);
}
namespace {
#define RETURN_STRING_LITERAL(x) \
case x: \
return #x
std::string EncryptionLevelString(EncryptionLevel level) {
switch (level) {
RETURN_STRING_LITERAL(ENCRYPTION_INITIAL);
RETURN_STRING_LITERAL(ENCRYPTION_HANDSHAKE);
RETURN_STRING_LITERAL(ENCRYPTION_ZERO_RTT);
RETURN_STRING_LITERAL(ENCRYPTION_FORWARD_SECURE);
default:
return "";
}
}
void CompareCrypters(const QuicEncrypter* encrypter,
const QuicDecrypter* decrypter, std::string label) {
if (encrypter == nullptr || decrypter == nullptr) {
ADD_FAILURE() << "Expected non-null crypters; have " << encrypter << " and "
<< decrypter << " for " << label;
return;
}
absl::string_view encrypter_key = encrypter->GetKey();
absl::string_view encrypter_iv = encrypter->GetNoncePrefix();
absl::string_view decrypter_key = decrypter->GetKey();
absl::string_view decrypter_iv = decrypter->GetNoncePrefix();
quiche::test::CompareCharArraysWithHexError(
label + " key", encrypter_key.data(), encrypter_key.length(),
decrypter_key.data(), decrypter_key.length());
quiche::test::CompareCharArraysWithHexError(
label + " iv", encrypter_iv.data(), encrypter_iv.length(),
decrypter_iv.data(), decrypter_iv.length());
}
} // namespace
void CompareClientAndServerKeys(QuicCryptoClientStreamBase* client,
QuicCryptoServerStreamBase* server) {
QuicFramer* client_framer = QuicConnectionPeer::GetFramer(
QuicStreamPeer::session(client)->connection());
QuicFramer* server_framer = QuicConnectionPeer::GetFramer(
QuicStreamPeer::session(server)->connection());
for (EncryptionLevel level :
{ENCRYPTION_HANDSHAKE, ENCRYPTION_ZERO_RTT, ENCRYPTION_FORWARD_SECURE}) {
SCOPED_TRACE(EncryptionLevelString(level));
const QuicEncrypter* client_encrypter(
QuicFramerPeer::GetEncrypter(client_framer, level));
const QuicDecrypter* server_decrypter(
QuicFramerPeer::GetDecrypter(server_framer, level));
if (level == ENCRYPTION_FORWARD_SECURE ||
!((level == ENCRYPTION_HANDSHAKE || level == ENCRYPTION_ZERO_RTT ||
client_encrypter == nullptr) &&
(level == ENCRYPTION_ZERO_RTT || server_decrypter == nullptr))) {
CompareCrypters(client_encrypter, server_decrypter,
"client " + EncryptionLevelString(level) + " write");
}
const QuicEncrypter* server_encrypter(
QuicFramerPeer::GetEncrypter(server_framer, level));
const QuicDecrypter* client_decrypter(
QuicFramerPeer::GetDecrypter(client_framer, level));
if (level == ENCRYPTION_FORWARD_SECURE ||
!(server_encrypter == nullptr &&
(level == ENCRYPTION_HANDSHAKE || level == ENCRYPTION_ZERO_RTT ||
client_decrypter == nullptr))) {
CompareCrypters(server_encrypter, client_decrypter,
"server " + EncryptionLevelString(level) + " write");
}
}
absl::string_view client_subkey_secret =
client->crypto_negotiated_params().subkey_secret;
absl::string_view server_subkey_secret =
server->crypto_negotiated_params().subkey_secret;
quiche::test::CompareCharArraysWithHexError(
"subkey secret", client_subkey_secret.data(),
client_subkey_secret.length(), server_subkey_secret.data(),
server_subkey_secret.length());
}
QuicTag ParseTag(const char* tagstr) {
const size_t len = strlen(tagstr);
QUICHE_CHECK_NE(0u, len);
QuicTag tag = 0;
if (tagstr[0] == '#') {
QUICHE_CHECK_EQ(static_cast<size_t>(1 + 2 * 4), len);
tagstr++;
for (size_t i = 0; i < 8; i++) {
tag <<= 4;
uint8_t v = 0;
QUICHE_CHECK(HexChar(tagstr[i], &v));
tag |= v;
}
return tag;
}
QUICHE_CHECK_LE(len, 4u);
for (size_t i = 0; i < 4; i++) {
tag >>= 8;
if (i < len) {
tag |= static_cast<uint32_t>(tagstr[i]) << 24;
}
}
return tag;
}
CryptoHandshakeMessage CreateCHLO(
std::vector<std::pair<std::string, std::string>> tags_and_values) {
return CreateCHLO(tags_and_values, -1);
}
CryptoHandshakeMessage CreateCHLO(
std::vector<std::pair<std::string, std::string>> tags_and_values,
int minimum_size_bytes) {
CryptoHandshakeMessage msg;
msg.set_tag(MakeQuicTag('C', 'H', 'L', 'O'));
if (minimum_size_bytes > 0) {
msg.set_minimum_size(minimum_size_bytes);
}
for (const auto& tag_and_value : tags_and_values) {
const std::string& tag = tag_and_value.first;
const std::string& value = tag_and_value.second;
const QuicTag quic_tag = ParseTag(tag.c_str());
size_t value_len = value.length();
if (value_len > 0 && value[0] == '#') {
// This is ascii encoded hex.
std::string hex_value =
absl::HexStringToBytes(absl::string_view(&value[1]));
msg.SetStringPiece(quic_tag, hex_value);
continue;
}
msg.SetStringPiece(quic_tag, value);
}
// The CryptoHandshakeMessage needs to be serialized and parsed to ensure
// that any padding is included.
std::unique_ptr<QuicData> bytes =
CryptoFramer::ConstructHandshakeMessage(msg);
std::unique_ptr<CryptoHandshakeMessage> parsed(
CryptoFramer::ParseMessage(bytes->AsStringPiece()));
QUICHE_CHECK(parsed);
return *parsed;
}
void MovePackets(PacketSavingConnection* source_conn,
size_t* inout_packet_index, QuicCryptoStream* dest_stream,
PacketSavingConnection* dest_conn,
Perspective dest_perspective, bool process_stream_data) {
SimpleQuicFramer framer(source_conn->supported_versions(), dest_perspective);
QuicFramerPeer::SetLastSerializedServerConnectionId(framer.framer(),
TestConnectionId());
SimpleQuicFramer null_encryption_framer(source_conn->supported_versions(),
dest_perspective);
QuicFramerPeer::SetLastSerializedServerConnectionId(
null_encryption_framer.framer(), TestConnectionId());
size_t index = *inout_packet_index;
for (; index < source_conn->encrypted_packets_.size(); index++) {
if (!dest_conn->connected()) {
QUIC_LOG(INFO)
<< "Destination connection disconnected. Skipping packet at index "
<< index;
continue;
}
// In order to properly test the code we need to perform encryption and
// decryption so that the crypters latch when expected. The crypters are in
// |dest_conn|, but we don't want to try and use them there. Instead we swap
// them into |framer|, perform the decryption with them, and then swap ther
// back.
QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
QuicConnectionPeer::AddBytesReceived(
dest_conn, source_conn->encrypted_packets_[index]->length());
if (!framer.ProcessPacket(*source_conn->encrypted_packets_[index])) {
// The framer will be unable to decrypt zero-rtt packets sent during
// handshake or forward-secure packets sent after the handshake is
// complete. Don't treat them as handshake packets.
QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
continue;
}
QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
// Install a packet flusher such that the packets generated by |dest_conn|
// in response to this packet are more likely to be coalesced and/or batched
// in the writer.
QuicConnection::ScopedPacketFlusher flusher(dest_conn);
dest_conn->OnDecryptedPacket(
source_conn->encrypted_packets_[index]->length(),
framer.last_decrypted_level());
if (dest_stream->handshake_protocol() == PROTOCOL_TLS1_3) {
// Try to process the packet with a framer that only has the NullDecrypter
// for decryption. If ProcessPacket succeeds, that means the packet was
// encrypted with the NullEncrypter. With the TLS handshaker in use, no
// packets should ever be encrypted with the NullEncrypter, instead
// they're encrypted with an obfuscation cipher based on QUIC version and
// connection ID.
QUIC_LOG(INFO) << "Attempting to decrypt with NullDecrypter: "
"expect a decryption failure on the next log line.";
ASSERT_FALSE(null_encryption_framer.ProcessPacket(
*source_conn->encrypted_packets_[index]))
<< "No TLS packets should be encrypted with the NullEncrypter";
}
// Since we're using QuicFramers separate from the connections to move
// packets, the QuicConnection never gets notified about what level the last
// packet was decrypted at. This is needed by TLS to know what encryption
// level was used for the data it's receiving, so we plumb this information
// from the SimpleQuicFramer back into the connection.
dest_conn->OnDecryptedPacket(
source_conn->encrypted_packets_[index]->length(),
framer.last_decrypted_level());
QuicConnectionPeer::SetCurrentPacket(
dest_conn, source_conn->encrypted_packets_[index]->AsStringPiece());
for (const auto& stream_frame : framer.stream_frames()) {
if (process_stream_data &&
dest_stream->handshake_protocol() == PROTOCOL_TLS1_3) {
// Deliver STREAM_FRAME such that application state is available and can
// be stored along with resumption ticket in session cache,
dest_conn->OnStreamFrame(*stream_frame);
} else {
// Ignore stream frames that are sent on other streams in the crypto
// event.
if (stream_frame->stream_id == dest_stream->id()) {
dest_stream->OnStreamFrame(*stream_frame);
}
}
}
for (const auto& crypto_frame : framer.crypto_frames()) {
dest_stream->OnCryptoFrame(*crypto_frame);
}
if (!framer.connection_close_frames().empty() && dest_conn->connected()) {
dest_conn->OnConnectionCloseFrame(framer.connection_close_frames()[0]);
}
}
*inout_packet_index = index;
QuicConnectionPeer::SetCurrentPacket(dest_conn,
absl::string_view(nullptr, 0));
}
CryptoHandshakeMessage GenerateDefaultInchoateCHLO(
const QuicClock* clock, QuicTransportVersion version,
QuicCryptoServerConfig* crypto_config) {
// clang-format off
return CreateCHLO(
{{"PDMD", "X509"},
{"AEAD", "AESG"},
{"KEXS", "C255"},
{"PUBS", GenerateClientPublicValuesHex().c_str()},
{"NONC", GenerateClientNonceHex(clock, crypto_config).c_str()},
{"VER\0", QuicVersionLabelToString(
CreateQuicVersionLabel(
ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, version))).c_str()}},
kClientHelloMinimumSize);
// clang-format on
}
std::string GenerateClientNonceHex(const QuicClock* clock,
QuicCryptoServerConfig* crypto_config) {
QuicCryptoServerConfig::ConfigOptions old_config_options;
QuicCryptoServerConfig::ConfigOptions new_config_options;
old_config_options.id = "old-config-id";
crypto_config->AddDefaultConfig(QuicRandom::GetInstance(), clock,
old_config_options);
QuicServerConfigProtobuf primary_config = crypto_config->GenerateConfig(
QuicRandom::GetInstance(), clock, new_config_options);
primary_config.set_primary_time(clock->WallNow().ToUNIXSeconds());
std::unique_ptr<CryptoHandshakeMessage> msg =
crypto_config->AddConfig(primary_config, clock->WallNow());
absl::string_view orbit;
QUICHE_CHECK(msg->GetStringPiece(kORBT, &orbit));
std::string nonce;
CryptoUtils::GenerateNonce(clock->WallNow(), QuicRandom::GetInstance(), orbit,
&nonce);
return ("#" + absl::BytesToHexString(nonce));
}
std::string GenerateClientPublicValuesHex() {
char public_value[32];
memset(public_value, 42, sizeof(public_value));
return ("#" + absl::BytesToHexString(
absl::string_view(public_value, sizeof(public_value))));
}
void GenerateFullCHLO(
const CryptoHandshakeMessage& inchoate_chlo,
QuicCryptoServerConfig* crypto_config, QuicSocketAddress server_addr,
QuicSocketAddress client_addr, QuicTransportVersion transport_version,
const QuicClock* clock,
quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config,
QuicCompressedCertsCache* compressed_certs_cache,
CryptoHandshakeMessage* out) {
// Pass a inchoate CHLO.
FullChloGenerator generator(
crypto_config, server_addr, client_addr, clock,
ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, transport_version), signed_config,
compressed_certs_cache, out);
crypto_config->ValidateClientHello(
inchoate_chlo, client_addr, server_addr, transport_version, clock,
signed_config, generator.GetValidateClientHelloCallback());
}
namespace {
constexpr char kTestProofHostname[] = "test.example.com";
class TestProofSource : public ProofSourceX509 {
public:
TestProofSource()
: ProofSourceX509(
quiche::QuicheReferenceCountedPointer<ProofSource::Chain>(
new ProofSource::Chain(
std::vector<std::string>{std::string(kTestCertificate)})),
std::move(*CertificatePrivateKey::LoadFromDer(
kTestCertificatePrivateKey))) {
QUICHE_DCHECK(valid());
}
protected:
void MaybeAddSctsForHostname(absl::string_view /*hostname*/,
std::string& leaf_cert_scts) override {
leaf_cert_scts = "Certificate Transparency is really nice";
}
};
class TestProofVerifier : public ProofVerifier {
public:
TestProofVerifier()
: certificate_(std::move(
*CertificateView::ParseSingleCertificate(kTestCertificate))) {}
class Details : public ProofVerifyDetails {
public:
ProofVerifyDetails* Clone() const override { return new Details(*this); }
};
QuicAsyncStatus VerifyProof(
const std::string& hostname, const uint16_t port,
const std::string& server_config,
QuicTransportVersion /*transport_version*/, absl::string_view chlo_hash,
const std::vector<std::string>& certs, const std::string& cert_sct,
const std::string& signature, const ProofVerifyContext* context,
std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details,
std::unique_ptr<ProofVerifierCallback> callback) override {
absl::optional<std::string> payload =
CryptoUtils::GenerateProofPayloadToBeSigned(chlo_hash, server_config);
if (!payload.has_value()) {
*error_details = "Failed to serialize signed payload";
return QUIC_FAILURE;
}
if (!certificate_.VerifySignature(*payload, signature,
SSL_SIGN_RSA_PSS_RSAE_SHA256)) {
*error_details = "Invalid signature";
return QUIC_FAILURE;
}
uint8_t out_alert;
return VerifyCertChain(hostname, port, certs, /*ocsp_response=*/"",
cert_sct, context, error_details, details,
&out_alert, std::move(callback));
}
QuicAsyncStatus VerifyCertChain(
const std::string& hostname, const uint16_t /*port*/,
const std::vector<std::string>& certs,
const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/,
const ProofVerifyContext* /*context*/, std::string* error_details,
std::unique_ptr<ProofVerifyDetails>* details, uint8_t* /*out_alert*/,
std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
std::string normalized_hostname =
QuicHostnameUtils::NormalizeHostname(hostname);
if (normalized_hostname != kTestProofHostname) {
*error_details = absl::StrCat("Invalid hostname, expected ",
kTestProofHostname, " got ", hostname);
return QUIC_FAILURE;
}
if (certs.empty() || certs.front() != kTestCertificate) {
*error_details = "Received certificate different from the expected";
return QUIC_FAILURE;
}
*details = std::make_unique<Details>();
return QUIC_SUCCESS;
}
std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
return nullptr;
}
private:
CertificateView certificate_;
};
} // namespace
std::unique_ptr<ProofSource> ProofSourceForTesting() {
return std::make_unique<TestProofSource>();
}
std::unique_ptr<ProofVerifier> ProofVerifierForTesting() {
return std::make_unique<TestProofVerifier>();
}
std::string CertificateHostnameForTesting() { return kTestProofHostname; }
std::unique_ptr<ProofVerifyContext> ProofVerifyContextForTesting() {
return nullptr;
}
} // namespace crypto_test_utils
} // namespace test
} // namespace quic