blob: af671664a60b54f2108c66fe9adaff70fefbeecf [file] [log] [blame]
// Copyright 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 "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
#include <algorithm>
#include <cstdlib>
#include <memory>
#include "base/macros.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "net/third_party/quiche/src/quic/core/crypto/aes_128_gcm_12_decrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/aes_128_gcm_12_encrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/cert_compressor.h"
#include "net/third_party/quiche/src/quic/core/crypto/chacha20_poly1305_encrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/channel_id.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_framer.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_utils.h"
#include "net/third_party/quiche/src/quic/core/crypto/curve25519_key_exchange.h"
#include "net/third_party/quiche/src/quic/core/crypto/key_exchange.h"
#include "net/third_party/quiche/src/quic/core/crypto/p256_key_exchange.h"
#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_decrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_hkdf.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/proto/source_address_token.proto.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_socket_address_coder.h"
#include "net/third_party/quiche/src/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_cert_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_hostname_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.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_text_utils.h"
namespace quic {
namespace {
// kMultiplier is the multiple of the CHLO message size that a REJ message
// must stay under when the client doesn't present a valid source-address
// token. This is used to protect QUIC from amplification attacks.
// TODO(rch): Reduce this to 2 again once b/25933682 is fixed.
const size_t kMultiplier = 3;
const int kMaxTokenAddresses = 4;
QuicString DeriveSourceAddressTokenKey(
QuicStringPiece source_address_token_secret) {
QuicHKDF hkdf(source_address_token_secret, QuicStringPiece() /* no salt */,
"QUIC source address token key",
CryptoSecretBoxer::GetKeySize(), 0 /* no fixed IV needed */,
0 /* no subkey secret */);
return QuicString(hkdf.server_write_key());
}
// Default source for creating KeyExchange objects.
class DefaultKeyExchangeSource : public KeyExchangeSource {
public:
DefaultKeyExchangeSource() = default;
~DefaultKeyExchangeSource() override = default;
std::unique_ptr<KeyExchange> Create(QuicString /*server_config_id*/,
QuicTag type,
QuicStringPiece private_key) override {
if (private_key.empty()) {
QUIC_LOG(WARNING) << "Server config contains key exchange method without "
"corresponding private key: "
<< type;
return nullptr;
}
std::unique_ptr<KeyExchange> ka;
switch (type) {
case kC255:
ka = Curve25519KeyExchange::New(private_key);
if (!ka) {
QUIC_LOG(WARNING) << "Server config contained an invalid curve25519"
" private key.";
return nullptr;
}
break;
case kP256:
ka = P256KeyExchange::New(private_key);
if (!ka) {
QUIC_LOG(WARNING) << "Server config contained an invalid P-256"
" private key.";
return nullptr;
}
break;
default:
QUIC_LOG(WARNING)
<< "Server config message contains unknown key exchange "
"method: "
<< type;
return nullptr;
}
return ka;
}
};
} // namespace
// static
std::unique_ptr<KeyExchangeSource> KeyExchangeSource::Default() {
return QuicMakeUnique<DefaultKeyExchangeSource>();
}
class ValidateClientHelloHelper {
public:
// Note: stores a pointer to a unique_ptr, and std::moves the unique_ptr when
// ValidationComplete is called.
ValidateClientHelloHelper(
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
result,
std::unique_ptr<ValidateClientHelloResultCallback>* done_cb)
: result_(std::move(result)), done_cb_(done_cb) {}
ValidateClientHelloHelper(const ValidateClientHelloHelper&) = delete;
ValidateClientHelloHelper& operator=(const ValidateClientHelloHelper&) =
delete;
~ValidateClientHelloHelper() {
QUIC_BUG_IF(done_cb_ != nullptr)
<< "Deleting ValidateClientHelloHelper with a pending callback.";
}
void ValidationComplete(
QuicErrorCode error_code,
const char* error_details,
std::unique_ptr<ProofSource::Details> proof_source_details) {
result_->error_code = error_code;
result_->error_details = error_details;
(*done_cb_)->Run(std::move(result_), std::move(proof_source_details));
DetachCallback();
}
void DetachCallback() {
QUIC_BUG_IF(done_cb_ == nullptr) << "Callback already detached.";
done_cb_ = nullptr;
}
private:
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
result_;
std::unique_ptr<ValidateClientHelloResultCallback>* done_cb_;
};
// static
const char QuicCryptoServerConfig::TESTING[] = "secret string for testing";
ClientHelloInfo::ClientHelloInfo(const QuicIpAddress& in_client_ip,
QuicWallTime in_now)
: client_ip(in_client_ip), now(in_now), valid_source_address_token(false) {}
ClientHelloInfo::ClientHelloInfo(const ClientHelloInfo& other) = default;
ClientHelloInfo::~ClientHelloInfo() {}
PrimaryConfigChangedCallback::PrimaryConfigChangedCallback() {}
PrimaryConfigChangedCallback::~PrimaryConfigChangedCallback() {}
ValidateClientHelloResultCallback::Result::Result(
const CryptoHandshakeMessage& in_client_hello,
QuicIpAddress in_client_ip,
QuicWallTime in_now)
: client_hello(in_client_hello),
info(in_client_ip, in_now),
error_code(QUIC_NO_ERROR) {}
ValidateClientHelloResultCallback::Result::~Result() {}
ValidateClientHelloResultCallback::ValidateClientHelloResultCallback() {}
ValidateClientHelloResultCallback::~ValidateClientHelloResultCallback() {}
ProcessClientHelloResultCallback::ProcessClientHelloResultCallback() {}
ProcessClientHelloResultCallback::~ProcessClientHelloResultCallback() {}
QuicCryptoServerConfig::ConfigOptions::ConfigOptions()
: expiry_time(QuicWallTime::Zero()),
channel_id_enabled(false),
p256(false) {}
QuicCryptoServerConfig::ConfigOptions::ConfigOptions(
const ConfigOptions& other) = default;
QuicCryptoServerConfig::ConfigOptions::~ConfigOptions() {}
QuicCryptoServerConfig::QuicCryptoServerConfig(
QuicStringPiece source_address_token_secret,
QuicRandom* server_nonce_entropy,
std::unique_ptr<ProofSource> proof_source,
std::unique_ptr<KeyExchangeSource> key_exchange_source,
bssl::UniquePtr<SSL_CTX> ssl_ctx)
: replay_protection_(true),
chlo_multiplier_(kMultiplier),
configs_lock_(),
primary_config_(nullptr),
next_config_promotion_time_(QuicWallTime::Zero()),
proof_source_(std::move(proof_source)),
key_exchange_source_(std::move(key_exchange_source)),
ssl_ctx_(std::move(ssl_ctx)),
source_address_token_future_secs_(3600),
source_address_token_lifetime_secs_(86400),
enable_serving_sct_(false),
rejection_observer_(nullptr),
pad_rej_(true),
pad_shlo_(true),
validate_chlo_size_(true),
validate_source_address_token_(true) {
DCHECK(proof_source_.get());
source_address_token_boxer_.SetKeys(
{DeriveSourceAddressTokenKey(source_address_token_secret)});
// Generate a random key and orbit for server nonces.
server_nonce_entropy->RandBytes(server_nonce_orbit_,
sizeof(server_nonce_orbit_));
const size_t key_size = server_nonce_boxer_.GetKeySize();
std::unique_ptr<uint8_t[]> key_bytes(new uint8_t[key_size]);
server_nonce_entropy->RandBytes(key_bytes.get(), key_size);
server_nonce_boxer_.SetKeys(
{QuicString(reinterpret_cast<char*>(key_bytes.get()), key_size)});
}
QuicCryptoServerConfig::~QuicCryptoServerConfig() {}
// static
std::unique_ptr<QuicServerConfigProtobuf>
QuicCryptoServerConfig::GenerateConfig(QuicRandom* rand,
const QuicClock* clock,
const ConfigOptions& options) {
CryptoHandshakeMessage msg;
const QuicString curve25519_private_key =
Curve25519KeyExchange::NewPrivateKey(rand);
std::unique_ptr<Curve25519KeyExchange> curve25519(
Curve25519KeyExchange::New(curve25519_private_key));
QuicStringPiece curve25519_public_value = curve25519->public_value();
QuicString encoded_public_values;
// First three bytes encode the length of the public value.
DCHECK_LT(curve25519_public_value.size(), (1U << 24));
encoded_public_values.push_back(
static_cast<char>(curve25519_public_value.size()));
encoded_public_values.push_back(
static_cast<char>(curve25519_public_value.size() >> 8));
encoded_public_values.push_back(
static_cast<char>(curve25519_public_value.size() >> 16));
encoded_public_values.append(curve25519_public_value.data(),
curve25519_public_value.size());
QuicString p256_private_key;
if (options.p256) {
p256_private_key = P256KeyExchange::NewPrivateKey();
std::unique_ptr<P256KeyExchange> p256(
P256KeyExchange::New(p256_private_key));
QuicStringPiece p256_public_value = p256->public_value();
DCHECK_LT(p256_public_value.size(), (1U << 24));
encoded_public_values.push_back(
static_cast<char>(p256_public_value.size()));
encoded_public_values.push_back(
static_cast<char>(p256_public_value.size() >> 8));
encoded_public_values.push_back(
static_cast<char>(p256_public_value.size() >> 16));
encoded_public_values.append(p256_public_value.data(),
p256_public_value.size());
}
msg.set_tag(kSCFG);
if (options.p256) {
msg.SetVector(kKEXS, QuicTagVector{kC255, kP256});
} else {
msg.SetVector(kKEXS, QuicTagVector{kC255});
}
msg.SetVector(kAEAD, QuicTagVector{kAESG, kCC20});
msg.SetStringPiece(kPUBS, encoded_public_values);
if (options.expiry_time.IsZero()) {
const QuicWallTime now = clock->WallNow();
const QuicWallTime expiry = now.Add(QuicTime::Delta::FromSeconds(
60 * 60 * 24 * 180 /* 180 days, ~six months */));
const uint64_t expiry_seconds = expiry.ToUNIXSeconds();
msg.SetValue(kEXPY, expiry_seconds);
} else {
msg.SetValue(kEXPY, options.expiry_time.ToUNIXSeconds());
}
char orbit_bytes[kOrbitSize];
if (options.orbit.size() == sizeof(orbit_bytes)) {
memcpy(orbit_bytes, options.orbit.data(), sizeof(orbit_bytes));
} else {
DCHECK(options.orbit.empty());
rand->RandBytes(orbit_bytes, sizeof(orbit_bytes));
}
msg.SetStringPiece(kORBT, QuicStringPiece(orbit_bytes, sizeof(orbit_bytes)));
if (options.channel_id_enabled) {
msg.SetVector(kPDMD, QuicTagVector{kCHID});
}
if (!options.token_binding_params.empty()) {
msg.SetVector(kTBKP, options.token_binding_params);
}
if (options.id.empty()) {
// We need to ensure that the SCID changes whenever the server config does
// thus we make it a hash of the rest of the server config.
std::unique_ptr<QuicData> serialized(
CryptoFramer::ConstructHandshakeMessage(msg));
uint8_t scid_bytes[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const uint8_t*>(serialized->data()),
serialized->length(), scid_bytes);
// The SCID is a truncated SHA-256 digest.
static_assert(16 <= SHA256_DIGEST_LENGTH, "SCID length too high.");
msg.SetStringPiece(
kSCID, QuicStringPiece(reinterpret_cast<const char*>(scid_bytes), 16));
} else {
msg.SetStringPiece(kSCID, options.id);
}
// Don't put new tags below this point. The SCID generation should hash over
// everything but itself and so extra tags should be added prior to the
// preceding if block.
std::unique_ptr<QuicData> serialized(
CryptoFramer::ConstructHandshakeMessage(msg));
std::unique_ptr<QuicServerConfigProtobuf> config(
new QuicServerConfigProtobuf);
config->set_config(QuicString(serialized->AsStringPiece()));
QuicServerConfigProtobuf::PrivateKey* curve25519_key = config->add_key();
curve25519_key->set_tag(kC255);
curve25519_key->set_private_key(curve25519_private_key);
if (options.p256) {
QuicServerConfigProtobuf::PrivateKey* p256_key = config->add_key();
p256_key->set_tag(kP256);
p256_key->set_private_key(p256_private_key);
}
return config;
}
CryptoHandshakeMessage* QuicCryptoServerConfig::AddConfig(
std::unique_ptr<QuicServerConfigProtobuf> protobuf,
const QuicWallTime now) {
std::unique_ptr<CryptoHandshakeMessage> msg(
CryptoFramer::ParseMessage(protobuf->config()));
if (!msg.get()) {
QUIC_LOG(WARNING) << "Failed to parse server config message";
return nullptr;
}
QuicReferenceCountedPointer<Config> config(ParseConfigProtobuf(protobuf));
if (!config.get()) {
QUIC_LOG(WARNING) << "Failed to parse server config message";
return nullptr;
}
{
QuicWriterMutexLock locked(&configs_lock_);
if (configs_.find(config->id) != configs_.end()) {
QUIC_LOG(WARNING) << "Failed to add config because another with the same "
"server config id already exists: "
<< QuicTextUtils::HexEncode(config->id);
return nullptr;
}
configs_[config->id] = config;
SelectNewPrimaryConfig(now);
DCHECK(primary_config_.get());
DCHECK_EQ(configs_.find(primary_config_->id)->second.get(),
primary_config_.get());
}
return msg.release();
}
CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig(
QuicRandom* rand,
const QuicClock* clock,
const ConfigOptions& options) {
return AddConfig(GenerateConfig(rand, clock, options), clock->WallNow());
}
bool QuicCryptoServerConfig::SetConfigs(
const std::vector<std::unique_ptr<QuicServerConfigProtobuf>>& protobufs,
const QuicWallTime now) {
std::vector<QuicReferenceCountedPointer<Config>> parsed_configs;
bool ok = true;
for (auto& protobuf : protobufs) {
QuicReferenceCountedPointer<Config> config(ParseConfigProtobuf(protobuf));
if (!config) {
ok = false;
break;
}
parsed_configs.push_back(config);
}
if (parsed_configs.empty()) {
QUIC_LOG(WARNING) << "New config list is empty.";
ok = false;
}
if (!ok) {
QUIC_LOG(WARNING) << "Rejecting QUIC configs because of above errors";
} else {
QUIC_LOG(INFO) << "Updating configs:";
QuicWriterMutexLock locked(&configs_lock_);
ConfigMap new_configs;
for (std::vector<QuicReferenceCountedPointer<Config>>::const_iterator i =
parsed_configs.begin();
i != parsed_configs.end(); ++i) {
QuicReferenceCountedPointer<Config> config = *i;
auto it = configs_.find(config->id);
if (it != configs_.end()) {
QUIC_LOG(INFO) << "Keeping scid: "
<< QuicTextUtils::HexEncode(config->id) << " orbit: "
<< QuicTextUtils::HexEncode(
reinterpret_cast<const char*>(config->orbit),
kOrbitSize)
<< " new primary_time "
<< config->primary_time.ToUNIXSeconds()
<< " old primary_time "
<< it->second->primary_time.ToUNIXSeconds()
<< " new priority " << config->priority
<< " old priority " << it->second->priority;
// Update primary_time and priority.
it->second->primary_time = config->primary_time;
it->second->priority = config->priority;
new_configs.insert(*it);
} else {
QUIC_LOG(INFO) << "Adding scid: "
<< QuicTextUtils::HexEncode(config->id) << " orbit: "
<< QuicTextUtils::HexEncode(
reinterpret_cast<const char*>(config->orbit),
kOrbitSize)
<< " primary_time "
<< config->primary_time.ToUNIXSeconds() << " priority "
<< config->priority;
new_configs.insert(std::make_pair(config->id, config));
}
}
configs_.swap(new_configs);
SelectNewPrimaryConfig(now);
DCHECK(primary_config_.get());
DCHECK_EQ(configs_.find(primary_config_->id)->second.get(),
primary_config_.get());
}
return ok;
}
void QuicCryptoServerConfig::SetSourceAddressTokenKeys(
const std::vector<QuicString>& keys) {
source_address_token_boxer_.SetKeys(keys);
}
void QuicCryptoServerConfig::GetConfigIds(
std::vector<QuicString>* scids) const {
QuicReaderMutexLock locked(&configs_lock_);
for (auto it = configs_.begin(); it != configs_.end(); ++it) {
scids->push_back(it->first);
}
}
void QuicCryptoServerConfig::ValidateClientHello(
const CryptoHandshakeMessage& client_hello,
const QuicIpAddress& client_ip,
const QuicSocketAddress& server_address,
QuicTransportVersion version,
const QuicClock* clock,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const {
const QuicWallTime now(clock->WallNow());
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result> result(
new ValidateClientHelloResultCallback::Result(client_hello, client_ip,
now));
QuicStringPiece requested_scid;
client_hello.GetStringPiece(kSCID, &requested_scid);
QuicReferenceCountedPointer<Config> requested_config;
QuicReferenceCountedPointer<Config> primary_config;
{
QuicReaderMutexLock locked(&configs_lock_);
if (!primary_config_.get()) {
result->error_code = QUIC_CRYPTO_INTERNAL_ERROR;
result->error_details = "No configurations loaded";
} else {
if (IsNextConfigReady(now)) {
configs_lock_.ReaderUnlock();
configs_lock_.WriterLock();
SelectNewPrimaryConfig(now);
DCHECK(primary_config_.get());
DCHECK_EQ(configs_.find(primary_config_->id)->second.get(),
primary_config_.get());
configs_lock_.WriterUnlock();
configs_lock_.ReaderLock();
}
}
requested_config = GetConfigWithScid(requested_scid);
primary_config = primary_config_;
signed_config->config = primary_config_;
}
if (result->error_code == QUIC_NO_ERROR) {
// QUIC requires a new proof for each CHLO so clear any existing proof.
signed_config->chain = nullptr;
signed_config->proof.signature = "";
signed_config->proof.leaf_cert_scts = "";
EvaluateClientHello(server_address, version, requested_config,
primary_config, signed_config, result,
std::move(done_cb));
} else {
done_cb->Run(result, /* details = */ nullptr);
}
}
class ProcessClientHelloHelper {
public:
explicit ProcessClientHelloHelper(
std::unique_ptr<ProcessClientHelloResultCallback>* done_cb)
: done_cb_(done_cb) {}
~ProcessClientHelloHelper() {
QUIC_BUG_IF(done_cb_ != nullptr)
<< "Deleting ProcessClientHelloHelper with a pending callback.";
}
void Fail(QuicErrorCode error, const QuicString& error_details) {
(*done_cb_)->Run(error, error_details, nullptr, nullptr, nullptr);
DetachCallback();
}
void Succeed(std::unique_ptr<CryptoHandshakeMessage> message,
std::unique_ptr<DiversificationNonce> diversification_nonce,
std::unique_ptr<ProofSource::Details> proof_source_details) {
(*done_cb_)->Run(QUIC_NO_ERROR, QuicString(), std::move(message),
std::move(diversification_nonce),
std::move(proof_source_details));
DetachCallback();
}
void DetachCallback() {
QUIC_BUG_IF(done_cb_ == nullptr) << "Callback already detached.";
done_cb_ = nullptr;
}
private:
std::unique_ptr<ProcessClientHelloResultCallback>* done_cb_;
};
class QuicCryptoServerConfig::ProcessClientHelloCallback
: public ProofSource::Callback {
public:
ProcessClientHelloCallback(
const QuicCryptoServerConfig* config,
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
validate_chlo_result,
bool reject_only,
QuicConnectionId connection_id,
const QuicSocketAddress& client_address,
ParsedQuicVersion version,
const ParsedQuicVersionVector& supported_versions,
bool use_stateless_rejects,
QuicConnectionId server_designated_connection_id,
const QuicClock* clock,
QuicRandom* rand,
QuicCompressedCertsCache* compressed_certs_cache,
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
QuicByteCount total_framing_overhead,
QuicByteCount chlo_packet_size,
const QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>&
requested_config,
const QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>&
primary_config,
std::unique_ptr<ProcessClientHelloResultCallback> done_cb)
: config_(config),
validate_chlo_result_(std::move(validate_chlo_result)),
reject_only_(reject_only),
connection_id_(connection_id),
client_address_(client_address),
version_(version),
supported_versions_(supported_versions),
use_stateless_rejects_(use_stateless_rejects),
server_designated_connection_id_(server_designated_connection_id),
clock_(clock),
rand_(rand),
compressed_certs_cache_(compressed_certs_cache),
params_(params),
signed_config_(signed_config),
total_framing_overhead_(total_framing_overhead),
chlo_packet_size_(chlo_packet_size),
requested_config_(requested_config),
primary_config_(primary_config),
done_cb_(std::move(done_cb)) {}
void Run(bool ok,
const QuicReferenceCountedPointer<ProofSource::Chain>& chain,
const QuicCryptoProof& proof,
std::unique_ptr<ProofSource::Details> details) override {
if (ok) {
signed_config_->chain = chain;
signed_config_->proof = proof;
}
config_->ProcessClientHelloAfterGetProof(
!ok, std::move(details), validate_chlo_result_, reject_only_,
connection_id_, client_address_, version_, supported_versions_,
use_stateless_rejects_, server_designated_connection_id_, clock_, rand_,
compressed_certs_cache_, params_, signed_config_,
total_framing_overhead_, chlo_packet_size_, requested_config_,
primary_config_, std::move(done_cb_));
}
private:
const QuicCryptoServerConfig* config_;
const QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
validate_chlo_result_;
const bool reject_only_;
const QuicConnectionId connection_id_;
const QuicSocketAddress client_address_;
const ParsedQuicVersion version_;
const ParsedQuicVersionVector supported_versions_;
const bool use_stateless_rejects_;
const QuicConnectionId server_designated_connection_id_;
const QuicClock* const clock_;
QuicRandom* const rand_;
QuicCompressedCertsCache* compressed_certs_cache_;
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
const QuicByteCount total_framing_overhead_;
const QuicByteCount chlo_packet_size_;
const QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
requested_config_;
const QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
primary_config_;
std::unique_ptr<ProcessClientHelloResultCallback> done_cb_;
};
class QuicCryptoServerConfig::ProcessClientHelloAfterGetProofCallback
: public KeyExchange::Callback {
public:
ProcessClientHelloAfterGetProofCallback(
const QuicCryptoServerConfig* config,
std::unique_ptr<ProofSource::Details> proof_source_details,
const KeyExchange::Factory& key_exchange_factory,
std::unique_ptr<CryptoHandshakeMessage> out,
QuicStringPiece public_value,
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
validate_chlo_result,
QuicConnectionId connection_id,
const QuicSocketAddress& client_address,
ParsedQuicVersion version,
const ParsedQuicVersionVector& supported_versions,
const QuicClock* clock,
QuicRandom* rand,
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
const QuicReferenceCountedPointer<Config>& requested_config,
const QuicReferenceCountedPointer<Config>& primary_config,
std::unique_ptr<ProcessClientHelloResultCallback> done_cb)
: config_(config),
proof_source_details_(std::move(proof_source_details)),
key_exchange_factory_(key_exchange_factory),
out_(std::move(out)),
public_value_(public_value),
validate_chlo_result_(std::move(validate_chlo_result)),
connection_id_(connection_id),
client_address_(client_address),
version_(version),
supported_versions_(supported_versions),
clock_(clock),
rand_(rand),
params_(params),
signed_config_(signed_config),
requested_config_(requested_config),
primary_config_(primary_config),
done_cb_(std::move(done_cb)) {}
void Run(bool ok) override {
config_->ProcessClientHelloAfterCalculateSharedKeys(
!ok, std::move(proof_source_details_), key_exchange_factory_,
std::move(out_), public_value_, *validate_chlo_result_, connection_id_,
client_address_, version_, supported_versions_, clock_, rand_, params_,
signed_config_, requested_config_, primary_config_,
std::move(done_cb_));
}
private:
const QuicCryptoServerConfig* config_;
std::unique_ptr<ProofSource::Details> proof_source_details_;
const KeyExchange::Factory& key_exchange_factory_;
std::unique_ptr<CryptoHandshakeMessage> out_;
QuicString public_value_;
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
validate_chlo_result_;
QuicConnectionId connection_id_;
const QuicSocketAddress client_address_;
ParsedQuicVersion version_;
const ParsedQuicVersionVector supported_versions_;
const QuicClock* clock_;
QuicRandom* rand_;
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
const QuicReferenceCountedPointer<Config> requested_config_;
const QuicReferenceCountedPointer<Config> primary_config_;
std::unique_ptr<ProcessClientHelloResultCallback> done_cb_;
};
void QuicCryptoServerConfig::ProcessClientHello(
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
validate_chlo_result,
bool reject_only,
QuicConnectionId connection_id,
const QuicSocketAddress& server_address,
const QuicSocketAddress& client_address,
ParsedQuicVersion version,
const ParsedQuicVersionVector& supported_versions,
bool use_stateless_rejects,
QuicConnectionId server_designated_connection_id,
const QuicClock* clock,
QuicRandom* rand,
QuicCompressedCertsCache* compressed_certs_cache,
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
QuicByteCount total_framing_overhead,
QuicByteCount chlo_packet_size,
std::unique_ptr<ProcessClientHelloResultCallback> done_cb) const {
DCHECK(done_cb);
ProcessClientHelloHelper helper(&done_cb);
const CryptoHandshakeMessage& client_hello =
validate_chlo_result->client_hello;
const ClientHelloInfo& info = validate_chlo_result->info;
QuicString error_details;
QuicErrorCode valid = CryptoUtils::ValidateClientHello(
client_hello, version, supported_versions, &error_details);
if (valid != QUIC_NO_ERROR) {
helper.Fail(valid, error_details);
return;
}
QuicStringPiece requested_scid;
client_hello.GetStringPiece(kSCID, &requested_scid);
const QuicWallTime now(clock->WallNow());
QuicReferenceCountedPointer<Config> requested_config;
QuicReferenceCountedPointer<Config> primary_config;
bool no_primary_config = false;
{
QuicReaderMutexLock locked(&configs_lock_);
if (!primary_config_) {
no_primary_config = true;
} else {
if (IsNextConfigReady(now)) {
configs_lock_.ReaderUnlock();
configs_lock_.WriterLock();
SelectNewPrimaryConfig(now);
DCHECK(primary_config_.get());
DCHECK_EQ(configs_.find(primary_config_->id)->second.get(),
primary_config_.get());
configs_lock_.WriterUnlock();
configs_lock_.ReaderLock();
}
// Use the config that the client requested in order to do key-agreement.
// Otherwise give it a copy of |primary_config_| to use.
primary_config = signed_config->config;
requested_config = GetConfigWithScid(requested_scid);
}
}
if (no_primary_config) {
helper.Fail(QUIC_CRYPTO_INTERNAL_ERROR, "No configurations loaded");
return;
}
if (validate_chlo_result->error_code != QUIC_NO_ERROR) {
helper.Fail(validate_chlo_result->error_code,
validate_chlo_result->error_details);
return;
}
if (!ClientDemandsX509Proof(client_hello)) {
helper.Fail(QUIC_UNSUPPORTED_PROOF_DEMAND, "Missing or invalid PDMD");
return;
}
DCHECK(proof_source_.get());
QuicString chlo_hash;
CryptoUtils::HashHandshakeMessage(client_hello, &chlo_hash,
Perspective::IS_SERVER);
// No need to get a new proof if one was already generated.
if (!signed_config->chain) {
std::unique_ptr<ProcessClientHelloCallback> cb(
new ProcessClientHelloCallback(
this, validate_chlo_result, reject_only, connection_id,
client_address, version, supported_versions, use_stateless_rejects,
server_designated_connection_id, clock, rand,
compressed_certs_cache, params, signed_config,
total_framing_overhead, chlo_packet_size, requested_config,
primary_config, std::move(done_cb)));
proof_source_->GetProof(
server_address, QuicString(info.sni), primary_config->serialized,
version.transport_version, chlo_hash, std::move(cb));
helper.DetachCallback();
return;
}
helper.DetachCallback();
ProcessClientHelloAfterGetProof(
/* found_error = */ false, /* proof_source_details = */ nullptr,
validate_chlo_result, reject_only, connection_id, client_address, version,
supported_versions, use_stateless_rejects,
server_designated_connection_id, clock, rand, compressed_certs_cache,
params, signed_config, total_framing_overhead, chlo_packet_size,
requested_config, primary_config, std::move(done_cb));
}
void QuicCryptoServerConfig::ProcessClientHelloAfterGetProof(
bool found_error,
std::unique_ptr<ProofSource::Details> proof_source_details,
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
validate_chlo_result,
bool reject_only,
QuicConnectionId connection_id,
const QuicSocketAddress& client_address,
ParsedQuicVersion version,
const ParsedQuicVersionVector& supported_versions,
bool use_stateless_rejects,
QuicConnectionId server_designated_connection_id,
const QuicClock* clock,
QuicRandom* rand,
QuicCompressedCertsCache* compressed_certs_cache,
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
QuicByteCount total_framing_overhead,
QuicByteCount chlo_packet_size,
const QuicReferenceCountedPointer<Config>& requested_config,
const QuicReferenceCountedPointer<Config>& primary_config,
std::unique_ptr<ProcessClientHelloResultCallback> done_cb) const {
QUIC_BUG_IF(!QuicUtils::IsConnectionIdValidForVersion(
connection_id, version.transport_version))
<< "ProcessClientHelloAfterGetProof: attempted to use connection ID "
<< connection_id << " which is invalid with version "
<< QuicVersionToString(version.transport_version);
ProcessClientHelloHelper helper(&done_cb);
if (found_error) {
helper.Fail(QUIC_HANDSHAKE_FAILED, "Failed to get proof");
return;
}
const CryptoHandshakeMessage& client_hello =
validate_chlo_result->client_hello;
const ClientHelloInfo& info = validate_chlo_result->info;
std::unique_ptr<DiversificationNonce> out_diversification_nonce(
new DiversificationNonce);
QuicStringPiece cert_sct;
if (client_hello.GetStringPiece(kCertificateSCTTag, &cert_sct) &&
cert_sct.empty()) {
params->sct_supported_by_client = true;
}
std::unique_ptr<CryptoHandshakeMessage> out(new CryptoHandshakeMessage);
if (!info.reject_reasons.empty() || !requested_config.get()) {
BuildRejection(version.transport_version, clock->WallNow(), *primary_config,
client_hello, info,
validate_chlo_result->cached_network_params,
use_stateless_rejects, server_designated_connection_id, rand,
compressed_certs_cache, params, *signed_config,
total_framing_overhead, chlo_packet_size, out.get());
if (rejection_observer_ != nullptr) {
rejection_observer_->OnRejectionBuilt(info.reject_reasons, out.get());
}
helper.Succeed(std::move(out), std::move(out_diversification_nonce),
std::move(proof_source_details));
return;
}
if (reject_only) {
helper.Succeed(std::move(out), std::move(out_diversification_nonce),
std::move(proof_source_details));
return;
}
QuicTagVector their_aeads;
QuicTagVector their_key_exchanges;
if (client_hello.GetTaglist(kAEAD, &their_aeads) != QUIC_NO_ERROR ||
client_hello.GetTaglist(kKEXS, &their_key_exchanges) != QUIC_NO_ERROR ||
their_aeads.size() != 1 || their_key_exchanges.size() != 1) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
"Missing or invalid AEAD or KEXS");
return;
}
size_t key_exchange_index;
if (!FindMutualQuicTag(requested_config->aead, their_aeads, &params->aead,
nullptr) ||
!FindMutualQuicTag(requested_config->kexs, their_key_exchanges,
&params->key_exchange, &key_exchange_index)) {
helper.Fail(QUIC_CRYPTO_NO_SUPPORT, "Unsupported AEAD or KEXS");
return;
}
if (!requested_config->tb_key_params.empty()) {
QuicTagVector their_tbkps;
switch (client_hello.GetTaglist(kTBKP, &their_tbkps)) {
case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
break;
case QUIC_NO_ERROR:
if (FindMutualQuicTag(requested_config->tb_key_params, their_tbkps,
&params->token_binding_key_param, nullptr)) {
break;
}
QUIC_FALLTHROUGH_INTENDED;
default:
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
"Invalid Token Binding key parameter");
return;
}
}
QuicStringPiece public_value;
if (!client_hello.GetStringPiece(kPUBS, &public_value)) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, "Missing public value");
return;
}
const KeyExchange* key_exchange =
requested_config->key_exchanges[key_exchange_index].get();
// TODO(rch): Would it be better to implement a move operator and just
// std::move(helper) instead of done_cb?
helper.DetachCallback();
auto cb = QuicMakeUnique<ProcessClientHelloAfterGetProofCallback>(
this, std::move(proof_source_details), key_exchange->GetFactory(),
std::move(out), public_value, validate_chlo_result, connection_id,
client_address, version, supported_versions, clock, rand, params,
signed_config, requested_config, primary_config, std::move(done_cb));
key_exchange->CalculateSharedKey(
public_value, &params->initial_premaster_secret, std::move(cb));
}
void QuicCryptoServerConfig::ProcessClientHelloAfterCalculateSharedKeys(
bool found_error,
std::unique_ptr<ProofSource::Details> proof_source_details,
const KeyExchange::Factory& key_exchange_factory,
std::unique_ptr<CryptoHandshakeMessage> out,
QuicStringPiece public_value,
const ValidateClientHelloResultCallback::Result& validate_chlo_result,
QuicConnectionId connection_id,
const QuicSocketAddress& client_address,
ParsedQuicVersion version,
const ParsedQuicVersionVector& supported_versions,
const QuicClock* clock,
QuicRandom* rand,
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
const QuicReferenceCountedPointer<Config>& requested_config,
const QuicReferenceCountedPointer<Config>& primary_config,
std::unique_ptr<ProcessClientHelloResultCallback> done_cb) const {
QUIC_BUG_IF(!QuicUtils::IsConnectionIdValidForVersion(
connection_id, version.transport_version))
<< "ProcessClientHelloAfterCalculateSharedKeys:"
" attempted to use connection ID "
<< connection_id << " which is invalid with version "
<< QuicVersionToString(version.transport_version);
ProcessClientHelloHelper helper(&done_cb);
if (found_error) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, "Invalid public value");
return;
}
const CryptoHandshakeMessage& client_hello =
validate_chlo_result.client_hello;
const ClientHelloInfo& info = validate_chlo_result.info;
auto out_diversification_nonce = QuicMakeUnique<DiversificationNonce>();
if (!info.sni.empty()) {
params->sni = QuicHostnameUtils::NormalizeHostname(info.sni);
}
QuicString hkdf_suffix;
const QuicData& client_hello_serialized = client_hello.GetSerialized();
hkdf_suffix.reserve(connection_id.length() +
client_hello_serialized.length() +
requested_config->serialized.size());
hkdf_suffix.append(connection_id.data(), connection_id.length());
hkdf_suffix.append(client_hello_serialized.data(),
client_hello_serialized.length());
hkdf_suffix.append(requested_config->serialized);
DCHECK(proof_source_.get());
if (signed_config->chain->certs.empty()) {
helper.Fail(QUIC_CRYPTO_INTERNAL_ERROR, "Failed to get certs");
return;
}
hkdf_suffix.append(signed_config->chain->certs.at(0));
QuicStringPiece cetv_ciphertext;
if (requested_config->channel_id_enabled &&
client_hello.GetStringPiece(kCETV, &cetv_ciphertext)) {
CryptoHandshakeMessage client_hello_copy(client_hello);
client_hello_copy.Erase(kCETV);
client_hello_copy.Erase(kPAD);
const QuicData& client_hello_copy_serialized =
client_hello_copy.GetSerialized();
QuicString hkdf_input;
hkdf_input.append(QuicCryptoConfig::kCETVLabel,
strlen(QuicCryptoConfig::kCETVLabel) + 1);
hkdf_input.append(connection_id.data(), connection_id.length());
hkdf_input.append(client_hello_copy_serialized.data(),
client_hello_copy_serialized.length());
hkdf_input.append(requested_config->serialized);
CrypterPair crypters;
if (!CryptoUtils::DeriveKeys(
params->initial_premaster_secret, params->aead, info.client_nonce,
info.server_nonce, pre_shared_key_, hkdf_input,
Perspective::IS_SERVER, CryptoUtils::Diversification::Never(),
&crypters, nullptr /* subkey secret */)) {
helper.Fail(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED,
"Symmetric key setup failed");
return;
}
char plaintext[kMaxPacketSize];
size_t plaintext_length = 0;
const bool success = crypters.decrypter->DecryptPacket(
0 /* packet number */, QuicStringPiece() /* associated data */,
cetv_ciphertext, plaintext, &plaintext_length, kMaxPacketSize);
if (!success) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
"CETV decryption failure");
return;
}
std::unique_ptr<CryptoHandshakeMessage> cetv(CryptoFramer::ParseMessage(
QuicStringPiece(plaintext, plaintext_length)));
if (!cetv.get()) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, "CETV parse error");
return;
}
QuicStringPiece key, signature;
if (cetv->GetStringPiece(kCIDK, &key) &&
cetv->GetStringPiece(kCIDS, &signature)) {
if (!ChannelIDVerifier::Verify(key, hkdf_input, signature)) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
"ChannelID signature failure");
return;
}
params->channel_id = QuicString(key);
}
}
QuicString hkdf_input;
size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1;
hkdf_input.reserve(label_len + hkdf_suffix.size());
hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len);
hkdf_input.append(hkdf_suffix);
rand->RandBytes(out_diversification_nonce->data(),
out_diversification_nonce->size());
CryptoUtils::Diversification diversification =
CryptoUtils::Diversification::Now(out_diversification_nonce.get());
if (!CryptoUtils::DeriveKeys(
params->initial_premaster_secret, params->aead, info.client_nonce,
info.server_nonce, pre_shared_key_, hkdf_input,
Perspective::IS_SERVER, diversification, &params->initial_crypters,
&params->initial_subkey_secret)) {
helper.Fail(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED,
"Symmetric key setup failed");
return;
}
QuicString forward_secure_public_value;
std::unique_ptr<KeyExchange> forward_secure_key_exchange =
key_exchange_factory.Create(rand);
forward_secure_public_value =
QuicString(forward_secure_key_exchange->public_value());
if (!forward_secure_key_exchange->CalculateSharedKey(
public_value, &params->forward_secure_premaster_secret)) {
helper.Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, "Invalid public value");
return;
}
QuicString forward_secure_hkdf_input;
label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1;
forward_secure_hkdf_input.reserve(label_len + hkdf_suffix.size());
forward_secure_hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel,
label_len);
forward_secure_hkdf_input.append(hkdf_suffix);
QuicString shlo_nonce;
shlo_nonce = NewServerNonce(rand, info.now);
out->SetStringPiece(kServerNonceTag, shlo_nonce);
if (!CryptoUtils::DeriveKeys(
params->forward_secure_premaster_secret, params->aead,
info.client_nonce,
shlo_nonce.empty() ? info.server_nonce : shlo_nonce, pre_shared_key_,
forward_secure_hkdf_input, Perspective::IS_SERVER,
CryptoUtils::Diversification::Never(),
&params->forward_secure_crypters, &params->subkey_secret)) {
helper.Fail(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED,
"Symmetric key setup failed");
return;
}
out->set_tag(kSHLO);
out->SetVersionVector(kVER, supported_versions);
out->SetStringPiece(
kSourceAddressTokenTag,
NewSourceAddressToken(*requested_config, info.source_address_tokens,
client_address.host(), rand, info.now, nullptr));
QuicSocketAddressCoder address_coder(client_address);
out->SetStringPiece(kCADR, address_coder.Encode());
out->SetStringPiece(kPUBS, forward_secure_public_value);
helper.Succeed(std::move(out), std::move(out_diversification_nonce),
std::move(proof_source_details));
}
QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
QuicCryptoServerConfig::GetConfigWithScid(
QuicStringPiece requested_scid) const {
configs_lock_.AssertReaderHeld();
if (!requested_scid.empty()) {
auto it = configs_.find((QuicString(requested_scid)));
if (it != configs_.end()) {
// We'll use the config that the client requested in order to do
// key-agreement.
return QuicReferenceCountedPointer<Config>(it->second);
}
}
return QuicReferenceCountedPointer<Config>();
}
// ConfigPrimaryTimeLessThan is a comparator that implements "less than" for
// Config's based on their primary_time.
// static
bool QuicCryptoServerConfig::ConfigPrimaryTimeLessThan(
const QuicReferenceCountedPointer<Config>& a,
const QuicReferenceCountedPointer<Config>& b) {
if (a->primary_time.IsBefore(b->primary_time) ||
b->primary_time.IsBefore(a->primary_time)) {
// Primary times differ.
return a->primary_time.IsBefore(b->primary_time);
} else if (a->priority != b->priority) {
// Primary times are equal, sort backwards by priority.
return a->priority < b->priority;
} else {
// Primary times and priorities are equal, sort by config id.
return a->id < b->id;
}
}
void QuicCryptoServerConfig::SelectNewPrimaryConfig(
const QuicWallTime now) const {
std::vector<QuicReferenceCountedPointer<Config>> configs;
configs.reserve(configs_.size());
for (auto it = configs_.begin(); it != configs_.end(); ++it) {
// TODO(avd) Exclude expired configs?
configs.push_back(it->second);
}
if (configs.empty()) {
if (primary_config_ != nullptr) {
QUIC_BUG << "No valid QUIC server config. Keeping the current config.";
} else {
QUIC_BUG << "No valid QUIC server config.";
}
return;
}
std::sort(configs.begin(), configs.end(), ConfigPrimaryTimeLessThan);
QuicReferenceCountedPointer<Config> best_candidate = configs[0];
for (size_t i = 0; i < configs.size(); ++i) {
const QuicReferenceCountedPointer<Config> config(configs[i]);
if (!config->primary_time.IsAfter(now)) {
if (config->primary_time.IsAfter(best_candidate->primary_time)) {
best_candidate = config;
}
continue;
}
// This is the first config with a primary_time in the future. Thus the
// previous Config should be the primary and this one should determine the
// next_config_promotion_time_.
QuicReferenceCountedPointer<Config> new_primary = best_candidate;
if (i == 0) {
// We need the primary_time of the next config.
if (configs.size() > 1) {
next_config_promotion_time_ = configs[1]->primary_time;
} else {
next_config_promotion_time_ = QuicWallTime::Zero();
}
} else {
next_config_promotion_time_ = config->primary_time;
}
if (primary_config_) {
primary_config_->is_primary = false;
}
primary_config_ = new_primary;
new_primary->is_primary = true;
QUIC_DLOG(INFO) << "New primary config. orbit: "
<< QuicTextUtils::HexEncode(reinterpret_cast<const char*>(
primary_config_->orbit),
kOrbitSize);
if (primary_config_changed_cb_ != nullptr) {
primary_config_changed_cb_->Run(primary_config_->id);
}
return;
}
// All config's primary times are in the past. We should make the most recent
// and highest priority candidate primary.
QuicReferenceCountedPointer<Config> new_primary = best_candidate;
if (primary_config_) {
primary_config_->is_primary = false;
}
primary_config_ = new_primary;
new_primary->is_primary = true;
QUIC_DLOG(INFO) << "New primary config. orbit: "
<< QuicTextUtils::HexEncode(
reinterpret_cast<const char*>(primary_config_->orbit),
kOrbitSize)
<< " scid: " << QuicTextUtils::HexEncode(primary_config_->id);
next_config_promotion_time_ = QuicWallTime::Zero();
if (primary_config_changed_cb_ != nullptr) {
primary_config_changed_cb_->Run(primary_config_->id);
}
}
class QuicCryptoServerConfig::EvaluateClientHelloCallback
: public ProofSource::Callback {
public:
EvaluateClientHelloCallback(
const QuicCryptoServerConfig& config,
const QuicIpAddress& server_ip,
QuicTransportVersion version,
QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
requested_config,
QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
primary_config,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
client_hello_state,
std::unique_ptr<ValidateClientHelloResultCallback> done_cb)
: config_(config),
server_ip_(server_ip),
version_(version),
requested_config_(std::move(requested_config)),
primary_config_(std::move(primary_config)),
signed_config_(signed_config),
client_hello_state_(std::move(client_hello_state)),
done_cb_(std::move(done_cb)) {}
void Run(bool ok,
const QuicReferenceCountedPointer<ProofSource::Chain>& chain,
const QuicCryptoProof& proof,
std::unique_ptr<ProofSource::Details> details) override {
if (ok) {
signed_config_->chain = chain;
signed_config_->proof = proof;
}
config_.EvaluateClientHelloAfterGetProof(
server_ip_, version_, requested_config_, primary_config_,
signed_config_, std::move(details), !ok, client_hello_state_,
std::move(done_cb_));
}
private:
const QuicCryptoServerConfig& config_;
const QuicIpAddress& server_ip_;
const QuicTransportVersion version_;
const QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
requested_config_;
const QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
primary_config_;
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
client_hello_state_;
std::unique_ptr<ValidateClientHelloResultCallback> done_cb_;
};
void QuicCryptoServerConfig::EvaluateClientHello(
const QuicSocketAddress& server_address,
QuicTransportVersion version,
QuicReferenceCountedPointer<Config> requested_config,
QuicReferenceCountedPointer<Config> primary_config,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
client_hello_state,
std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const {
DCHECK(!signed_config->chain);
ValidateClientHelloHelper helper(client_hello_state, &done_cb);
const CryptoHandshakeMessage& client_hello = client_hello_state->client_hello;
ClientHelloInfo* info = &(client_hello_state->info);
if (validate_chlo_size_ && client_hello.size() < kClientHelloMinimumSize) {
helper.ValidationComplete(QUIC_CRYPTO_INVALID_VALUE_LENGTH,
"Client hello too small", nullptr);
return;
}
if (client_hello.GetStringPiece(kSNI, &info->sni) &&
!QuicHostnameUtils::IsValidSNI(info->sni)) {
helper.ValidationComplete(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
"Invalid SNI name", nullptr);
return;
}
client_hello.GetStringPiece(kUAID, &info->user_agent_id);
HandshakeFailureReason source_address_token_error = MAX_FAILURE_REASON;
if (validate_source_address_token_) {
QuicStringPiece srct;
if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct)) {
Config& config =
requested_config != nullptr ? *requested_config : *primary_config;
source_address_token_error =
ParseSourceAddressToken(config, srct, &info->source_address_tokens);
if (source_address_token_error == HANDSHAKE_OK) {
source_address_token_error = ValidateSourceAddressTokens(
info->source_address_tokens, info->client_ip, info->now,
&client_hello_state->cached_network_params);
}
info->valid_source_address_token =
(source_address_token_error == HANDSHAKE_OK);
} else {
source_address_token_error = SOURCE_ADDRESS_TOKEN_INVALID_FAILURE;
}
} else {
source_address_token_error = HANDSHAKE_OK;
info->valid_source_address_token = true;
}
if (!requested_config.get()) {
QuicStringPiece requested_scid;
if (client_hello.GetStringPiece(kSCID, &requested_scid)) {
info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE);
} else {
info->reject_reasons.push_back(SERVER_CONFIG_INCHOATE_HELLO_FAILURE);
}
// No server config with the requested ID.
helper.ValidationComplete(QUIC_NO_ERROR, "", nullptr);
return;
}
if (!client_hello.GetStringPiece(kNONC, &info->client_nonce)) {
info->reject_reasons.push_back(SERVER_CONFIG_INCHOATE_HELLO_FAILURE);
// Report no client nonce as INCHOATE_HELLO_FAILURE.
helper.ValidationComplete(QUIC_NO_ERROR, "", nullptr);
return;
}
if (source_address_token_error != HANDSHAKE_OK) {
info->reject_reasons.push_back(source_address_token_error);
// No valid source address token.
}
QuicReferenceCountedPointer<ProofSource::Chain> chain =
proof_source_->GetCertChain(server_address, QuicString(info->sni));
if (!chain) {
info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE);
} else if (!ValidateExpectedLeafCertificate(client_hello, chain->certs)) {
info->reject_reasons.push_back(INVALID_EXPECTED_LEAF_CERTIFICATE);
}
EvaluateClientHelloAfterGetProof(
server_address.host(), version, requested_config, primary_config,
signed_config, /*proof_source_details=*/nullptr,
/*get_proof_failed=*/false, client_hello_state, std::move(done_cb));
helper.DetachCallback();
}
void QuicCryptoServerConfig::EvaluateClientHelloAfterGetProof(
const QuicIpAddress& server_ip,
QuicTransportVersion version,
QuicReferenceCountedPointer<Config> requested_config,
QuicReferenceCountedPointer<Config> primary_config,
QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config,
std::unique_ptr<ProofSource::Details> proof_source_details,
bool get_proof_failed,
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
client_hello_state,
std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const {
ValidateClientHelloHelper helper(client_hello_state, &done_cb);
const CryptoHandshakeMessage& client_hello = client_hello_state->client_hello;
ClientHelloInfo* info = &(client_hello_state->info);
if (info->client_nonce.size() != kNonceSize) {
info->reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE);
// Invalid client nonce.
QUIC_LOG_FIRST_N(ERROR, 2)
<< "Invalid client nonce: " << client_hello.DebugString();
QUIC_DLOG(INFO) << "Invalid client nonce.";
}
// Server nonce is optional, and used for key derivation if present.
client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce);
QUIC_DVLOG(1) << "No 0-RTT replay protection in QUIC_VERSION_33 and higher.";
// If the server nonce is empty and we're requiring handshake confirmation
// for DoS reasons then we must reject the CHLO.
if (GetQuicReloadableFlag(quic_require_handshake_confirmation) &&
info->server_nonce.empty()) {
info->reject_reasons.push_back(SERVER_NONCE_REQUIRED_FAILURE);
}
helper.ValidationComplete(QUIC_NO_ERROR, "", std::move(proof_source_details));
}
void QuicCryptoServerConfig::BuildServerConfigUpdateMessage(
QuicTransportVersion version,
QuicStringPiece chlo_hash,
const SourceAddressTokens& previous_source_address_tokens,
const QuicSocketAddress& server_address,
const QuicIpAddress& client_ip,
const QuicClock* clock,
QuicRandom* rand,
QuicCompressedCertsCache* compressed_certs_cache,
const QuicCryptoNegotiatedParameters& params,
const CachedNetworkParameters* cached_network_params,
std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) const {
QuicString serialized;
QuicString source_address_token;
const CommonCertSets* common_cert_sets;
{
QuicReaderMutexLock locked(&configs_lock_);
serialized = primary_config_->serialized;
common_cert_sets = primary_config_->common_cert_sets;
source_address_token = NewSourceAddressToken(
*primary_config_, previous_source_address_tokens, client_ip, rand,
clock->WallNow(), cached_network_params);
}
CryptoHandshakeMessage message;
message.set_tag(kSCUP);
message.SetStringPiece(kSCFG, serialized);
message.SetStringPiece(kSourceAddressTokenTag, source_address_token);
std::unique_ptr<BuildServerConfigUpdateMessageProofSourceCallback>
proof_source_cb(new BuildServerConfigUpdateMessageProofSourceCallback(
this, version, compressed_certs_cache, common_cert_sets, params,
std::move(message), std::move(cb)));
proof_source_->GetProof(server_address, params.sni, serialized, version,
chlo_hash, std::move(proof_source_cb));
}
QuicCryptoServerConfig::BuildServerConfigUpdateMessageProofSourceCallback::
~BuildServerConfigUpdateMessageProofSourceCallback() {}
QuicCryptoServerConfig::BuildServerConfigUpdateMessageProofSourceCallback::
BuildServerConfigUpdateMessageProofSourceCallback(
const QuicCryptoServerConfig* config,
QuicTransportVersion version,
QuicCompressedCertsCache* compressed_certs_cache,
const CommonCertSets* common_cert_sets,
const QuicCryptoNegotiatedParameters& params,
CryptoHandshakeMessage message,
std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb)
: config_(config),
version_(version),
compressed_certs_cache_(compressed_certs_cache),
common_cert_sets_(common_cert_sets),
client_common_set_hashes_(params.client_common_set_hashes),
client_cached_cert_hashes_(params.client_cached_cert_hashes),
sct_supported_by_client_(params.sct_supported_by_client),
sni_(params.sni),
message_(std::move(message)),
cb_(std::move(cb)) {}
void QuicCryptoServerConfig::BuildServerConfigUpdateMessageProofSourceCallback::
Run(bool ok,
const QuicReferenceCountedPointer<ProofSource::Chain>& chain,
const QuicCryptoProof& proof,
std::unique_ptr<ProofSource::Details> details) {
config_->FinishBuildServerConfigUpdateMessage(
version_, compressed_certs_cache_, common_cert_sets_,
client_common_set_hashes_, client_cached_cert_hashes_,
sct_supported_by_client_, sni_, ok, chain, proof.signature,
proof.leaf_cert_scts, std::move(details), std::move(message_),
std::move(cb_));
}
void QuicCryptoServerConfig::FinishBuildServerConfigUpdateMessage(
QuicTransportVersion version,
QuicCompressedCertsCache* compressed_certs_cache,
const CommonCertSets* common_cert_sets,
const QuicString& client_common_set_hashes,
const QuicString& client_cached_cert_hashes,
bool sct_supported_by_client,
const QuicString& sni,
bool ok,
const QuicReferenceCountedPointer<ProofSource::Chain>& chain,
const QuicString& signature,
const QuicString& leaf_cert_sct,
std::unique_ptr<ProofSource::Details> details,
CryptoHandshakeMessage message,
std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) const {
if (!ok) {
cb->Run(false, message);
return;
}
const QuicString compressed =
CompressChain(compressed_certs_cache, chain, client_common_set_hashes,
client_cached_cert_hashes, common_cert_sets);
message.SetStringPiece(kCertificateTag, compressed);
message.SetStringPiece(kPROF, signature);
if (sct_supported_by_client && enable_serving_sct_) {
if (leaf_cert_sct.empty()) {
QUIC_LOG_EVERY_N_SEC(WARNING, 60)
<< "SCT is expected but it is empty. SNI: " << sni;
} else {
message.SetStringPiece(kCertificateSCTTag, leaf_cert_sct);
}
}
cb->Run(true, message);
}
void QuicCryptoServerConfig::BuildRejection(
QuicTransportVersion version,
QuicWallTime now,
const Config& config,
const CryptoHandshakeMessage& client_hello,
const ClientHelloInfo& info,
const CachedNetworkParameters& cached_network_params,
bool use_stateless_rejects,
QuicConnectionId server_designated_connection_id,
QuicRandom* rand,
QuicCompressedCertsCache* compressed_certs_cache,
QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params,
const QuicSignedServerConfig& signed_config,
QuicByteCount total_framing_overhead,
QuicByteCount chlo_packet_size,
CryptoHandshakeMessage* out) const {
if (GetQuicReloadableFlag(enable_quic_stateless_reject_support) &&
use_stateless_rejects) {
QUIC_DVLOG(1) << "QUIC Crypto server config returning stateless reject "
<< "with server-designated connection ID "
<< server_designated_connection_id;
out->set_tag(kSREJ);
if (!QuicUtils::IsConnectionIdValidForVersion(
server_designated_connection_id, version)) {
QUIC_BUG << "Tried to send server designated connection ID "
<< server_designated_connection_id
<< " which is invalid with version "
<< QuicVersionToString(version);
return;
}
out->SetStringPiece(
kRCID, QuicStringPiece(server_designated_connection_id.data(),
server_designated_connection_id.length()));
} else {
out->set_tag(kREJ);
}
out->SetStringPiece(kSCFG, config.serialized);
out->SetStringPiece(
kSourceAddressTokenTag,
NewSourceAddressToken(config, info.source_address_tokens, info.client_ip,
rand, info.now, &cached_network_params));
out->SetValue(kSTTL, config.expiry_time.AbsoluteDifference(now).ToSeconds());
if (replay_protection_) {
out->SetStringPiece(kServerNonceTag, NewServerNonce(rand, info.now));
}
// Send client the reject reason for debugging purposes.
DCHECK_LT(0u, info.reject_reasons.size());
out->SetVector(kRREJ, info.reject_reasons);
// The client may have requested a certificate chain.
if (!ClientDemandsX509Proof(client_hello)) {
QUIC_BUG << "x509 certificates not supported in proof demand";
return;
}
QuicStringPiece client_common_set_hashes;
if (client_hello.GetStringPiece(kCCS, &client_common_set_hashes)) {
params->client_common_set_hashes = QuicString(client_common_set_hashes);
}
QuicStringPiece client_cached_cert_hashes;
if (client_hello.GetStringPiece(kCCRT, &client_cached_cert_hashes)) {
params->client_cached_cert_hashes = QuicString(client_cached_cert_hashes);
} else {
params->client_cached_cert_hashes.clear();
}
const QuicString compressed =
CompressChain(compressed_certs_cache, signed_config.chain,
params->client_common_set_hashes,
params->client_cached_cert_hashes, config.common_cert_sets);
DCHECK_GT(chlo_packet_size, client_hello.size());
// kREJOverheadBytes is a very rough estimate of how much of a REJ
// message is taken up by things other than the certificates.
// STK: 56 bytes
// SNO: 56 bytes
// SCFG
// SCID: 16 bytes
// PUBS: 38 bytes
const size_t kREJOverheadBytes = 166;
// max_unverified_size is the number of bytes that the certificate chain,
// signature, and (optionally) signed certificate timestamp can consume before
// we will demand a valid source-address token.
const size_t max_unverified_size =
chlo_multiplier_ * (chlo_packet_size - total_framing_overhead) -
kREJOverheadBytes;
static_assert(kClientHelloMinimumSize * kMultiplier >= kREJOverheadBytes,
"overhead calculation may underflow");
bool should_return_sct =
params->sct_supported_by_client && enable_serving_sct_;
const QuicString& cert_sct = signed_config.proof.leaf_cert_scts;
const size_t sct_size = should_return_sct ? cert_sct.size() : 0;
const size_t total_size =
signed_config.proof.signature.size() + compressed.size() + sct_size;
if (info.valid_source_address_token || total_size < max_unverified_size) {
out->SetStringPiece(kCertificateTag, compressed);
out->SetStringPiece(kPROF, signed_config.proof.signature);
if (should_return_sct) {
if (cert_sct.empty()) {
if (!GetQuicReloadableFlag(quic_log_cert_name_for_empty_sct)) {
QUIC_LOG_EVERY_N_SEC(WARNING, 60)
<< "SCT is expected but it is empty. sni :" << params->sni;
} else {
// Log SNI and subject name for the leaf cert if its SCT is empty.
// This is for debugging b/28342827.
const std::vector<quic::QuicString>& certs =
signed_config.chain->certs;
QuicStringPiece ca_subject;
if (!certs.empty()) {
QuicCertUtils::ExtractSubjectNameFromDERCert(certs[0], &ca_subject);
}
QUIC_LOG_EVERY_N_SEC(WARNING, 60)
<< "SCT is expected but it is empty. sni: '" << params->sni
<< "' cert subject: '" << ca_subject << "'";
}
} else {
out->SetStringPiece(kCertificateSCTTag, cert_sct);
}
}
} else {
QUIC_LOG_EVERY_N_SEC(WARNING, 60)
<< "Sending inchoate REJ for hostname: " << info.sni
<< " signature: " << signed_config.proof.signature.size()
<< " cert: " << compressed.size() << " sct:" << sct_size
<< " total: " << total_size << " max: " << max_unverified_size;
}
}
QuicString QuicCryptoServerConfig::CompressChain(
QuicCompressedCertsCache* compressed_certs_cache,
const QuicReferenceCountedPointer<ProofSource::Chain>& chain,
const QuicString& client_common_set_hashes,
const QuicString& client_cached_cert_hashes,
const CommonCertSets* common_sets) {
// Check whether the compressed certs is available in the cache.
DCHECK(compressed_certs_cache);
const QuicString* cached_value = compressed_certs_cache->GetCompressedCert(
chain, client_common_set_hashes, client_cached_cert_hashes);
if (cached_value) {
return *cached_value;
}
QuicString compressed =
CertCompressor::CompressChain(chain->certs, client_common_set_hashes,
client_cached_cert_hashes, common_sets);
// Insert the newly compressed cert to cache.
compressed_certs_cache->Insert(chain, client_common_set_hashes,
client_cached_cert_hashes, compressed);
return compressed;
}
QuicReferenceCountedPointer<QuicCryptoServerConfig::Config>
QuicCryptoServerConfig::ParseConfigProtobuf(
const std::unique_ptr<QuicServerConfigProtobuf>& protobuf) {
std::unique_ptr<CryptoHandshakeMessage> msg(
CryptoFramer::ParseMessage(protobuf->config()));
if (msg->tag() != kSCFG) {
QUIC_LOG(WARNING) << "Server config message has tag " << msg->tag()
<< " expected " << kSCFG;
return nullptr;
}
QuicReferenceCountedPointer<Config> config(new Config);
config->serialized = protobuf->config();
config->source_address_token_boxer = &source_address_token_boxer_;
if (protobuf->has_primary_time()) {
config->primary_time =
QuicWallTime::FromUNIXSeconds(protobuf->primary_time());
}
config->priority = protobuf->priority();
QuicStringPiece scid;
if (!msg->GetStringPiece(kSCID, &scid)) {
QUIC_LOG(WARNING) << "Server config message is missing SCID";
return nullptr;
}
config->id = QuicString(scid);
if (msg->GetTaglist(kAEAD, &config->aead) != QUIC_NO_ERROR) {
QUIC_LOG(WARNING) << "Server config message is missing AEAD";
return nullptr;
}
QuicTagVector kexs_tags;
if (msg->GetTaglist(kKEXS, &kexs_tags) != QUIC_NO_ERROR) {
QUIC_LOG(WARNING) << "Server config message is missing KEXS";
return nullptr;
}
QuicErrorCode err;
if ((err = msg->GetTaglist(kTBKP, &config->tb_key_params)) !=
QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND &&
err != QUIC_NO_ERROR) {
QUIC_LOG(WARNING) << "Server config message is missing or has invalid TBKP";
return nullptr;
}
QuicStringPiece orbit;
if (!msg->GetStringPiece(kORBT, &orbit)) {
QUIC_LOG(WARNING) << "Server config message is missing ORBT";
return nullptr;
}
if (orbit.size() != kOrbitSize) {
QUIC_LOG(WARNING) << "Orbit value in server config is the wrong length."
" Got "
<< orbit.size() << " want " << kOrbitSize;
return nullptr;
}
static_assert(sizeof(config->orbit) == kOrbitSize, "incorrect orbit size");
memcpy(config->orbit, orbit.data(), sizeof(config->orbit));
if (kexs_tags.size() != static_cast<size_t>(protobuf->key_size())) {
QUIC_LOG(WARNING) << "Server config has " << kexs_tags.size()
<< " key exchange methods configured, but "
<< protobuf->key_size() << " private keys";
return nullptr;
}
QuicTagVector proof_demand_tags;
if (msg->GetTaglist(kPDMD, &proof_demand_tags) == QUIC_NO_ERROR) {
for (QuicTag tag : proof_demand_tags) {
if (tag == kCHID) {
config->channel_id_enabled = true;
break;
}
}
}
for (size_t i = 0; i < kexs_tags.size(); i++) {
const QuicTag tag = kexs_tags[i];
QuicString private_key;
config->kexs.push_back(tag);
for (int j = 0; j < protobuf->key_size(); j++) {
const QuicServerConfigProtobuf::PrivateKey& key = protobuf->key(i);
if (key.tag() == tag) {
private_key = key.private_key();
break;
}
}
std::unique_ptr<KeyExchange> ka =
key_exchange_source_->Create(config->id, tag, private_key);
if (!ka) {
return nullptr;
}
for (const auto& key_exchange : config->key_exchanges) {
if (key_exchange->GetFactory().tag() == tag) {
QUIC_LOG(WARNING) << "Duplicate key exchange in config: " << tag;
return nullptr;
}
}
config->key_exchanges.push_back(std::move(ka));
}
uint64_t expiry_seconds;
if (msg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) {
QUIC_LOG(WARNING) << "Server config message is missing EXPY";
return nullptr;
}
config->expiry_time = QuicWallTime::FromUNIXSeconds(expiry_seconds);
return config;
}
void QuicCryptoServerConfig::set_replay_protection(bool on) {
replay_protection_ = on;
}
void QuicCryptoServerConfig::set_chlo_multiplier(size_t multiplier) {
chlo_multiplier_ = multiplier;
}
void QuicCryptoServerConfig::set_source_address_token_future_secs(
uint32_t future_secs) {
source_address_token_future_secs_ = future_secs;
}
void QuicCryptoServerConfig::set_source_address_token_lifetime_secs(
uint32_t lifetime_secs) {
source_address_token_lifetime_secs_ = lifetime_secs;
}
void QuicCryptoServerConfig::set_enable_serving_sct(bool enable_serving_sct) {
enable_serving_sct_ = enable_serving_sct;
}
void QuicCryptoServerConfig::AcquirePrimaryConfigChangedCb(
std::unique_ptr<PrimaryConfigChangedCallback> cb) {
QuicWriterMutexLock locked(&configs_lock_);
primary_config_changed_cb_ = std::move(cb);
}
QuicString QuicCryptoServerConfig::NewSourceAddressToken(
const Config& config,
const SourceAddressTokens& previous_tokens,
const QuicIpAddress& ip,
QuicRandom* rand,
QuicWallTime now,
const CachedNetworkParameters* cached_network_params) const {
SourceAddressTokens source_address_tokens;
SourceAddressToken* source_address_token = source_address_tokens.add_tokens();
source_address_token->set_ip(ip.DualStacked().ToPackedString());
source_address_token->set_timestamp(now.ToUNIXSeconds());
if (cached_network_params != nullptr) {
*(source_address_token->mutable_cached_network_parameters()) =
*cached_network_params;
}
// Append previous tokens.
for (const SourceAddressToken& token : previous_tokens.tokens()) {
if (source_address_tokens.tokens_size() > kMaxTokenAddresses) {
break;
}
if (token.ip() == source_address_token->ip()) {
// It's for the same IP address.
continue;
}
if (ValidateSourceAddressTokenTimestamp(token, now) != HANDSHAKE_OK) {
continue;
}
*(source_address_tokens.add_tokens()) = token;
}
return config.source_address_token_boxer->Box(
rand, source_address_tokens.SerializeAsString());
}
int QuicCryptoServerConfig::NumberOfConfigs() const {
QuicReaderMutexLock locked(&configs_lock_);
return configs_.size();
}
ProofSource* QuicCryptoServerConfig::proof_source() const {
return proof_source_.get();
}
SSL_CTX* QuicCryptoServerConfig::ssl_ctx() const {
return ssl_ctx_.get();
}
HandshakeFailureReason QuicCryptoServerConfig::ParseSourceAddressToken(
const Config& config,
QuicStringPiece token,
SourceAddressTokens* tokens) const {
QuicString storage;
QuicStringPiece plaintext;
if (!config.source_address_token_boxer->Unbox(token, &storage, &plaintext)) {
return SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE;
}
if (!tokens->ParseFromArray(plaintext.data(), plaintext.size())) {
// Some clients might still be using the old source token format so
// attempt to parse that format.
// TODO(rch): remove this code once the new format is ubiquitous.
SourceAddressToken token;
if (!token.ParseFromArray(plaintext.data(), plaintext.size())) {
return SOURCE_ADDRESS_TOKEN_PARSE_FAILURE;
}
*tokens->add_tokens() = token;
}
return HANDSHAKE_OK;
}
HandshakeFailureReason QuicCryptoServerConfig::ValidateSourceAddressTokens(
const SourceAddressTokens& source_address_tokens,
const QuicIpAddress& ip,
QuicWallTime now,
CachedNetworkParameters* cached_network_params) const {
HandshakeFailureReason reason =
SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE;
for (const SourceAddressToken& token : source_address_tokens.tokens()) {
reason = ValidateSingleSourceAddressToken(token, ip, now);
if (reason == HANDSHAKE_OK) {
if (token.has_cached_network_parameters()) {
*cached_network_params = token.cached_network_parameters();
}
break;
}
}
return reason;
}
HandshakeFailureReason QuicCryptoServerConfig::ValidateSingleSourceAddressToken(
const SourceAddressToken& source_address_token,
const QuicIpAddress& ip,
QuicWallTime now) const {
if (source_address_token.ip() != ip.DualStacked().ToPackedString()) {
// It's for a different IP address.
return SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE;
}
return ValidateSourceAddressTokenTimestamp(source_address_token, now);
}
HandshakeFailureReason
QuicCryptoServerConfig::ValidateSourceAddressTokenTimestamp(
const SourceAddressToken& source_address_token,
QuicWallTime now) const {
const QuicWallTime timestamp(
QuicWallTime::FromUNIXSeconds(source_address_token.timestamp()));
const QuicTime::Delta delta(now.AbsoluteDifference(timestamp));
if (now.IsBefore(timestamp) &&
delta.ToSeconds() > source_address_token_future_secs_) {
return SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE;
}
if (now.IsAfter(timestamp) &&
delta.ToSeconds() > source_address_token_lifetime_secs_) {
return SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE;
}
return HANDSHAKE_OK;
}
// kServerNoncePlaintextSize is the number of bytes in an unencrypted server
// nonce.
static const size_t kServerNoncePlaintextSize =
4 /* timestamp */ + 20 /* random bytes */;
QuicString QuicCryptoServerConfig::NewServerNonce(QuicRandom* rand,
QuicWallTime now) const {
const uint32_t timestamp = static_cast<uint32_t>(now.ToUNIXSeconds());
uint8_t server_nonce[kServerNoncePlaintextSize];
static_assert(sizeof(server_nonce) > sizeof(timestamp), "nonce too small");
server_nonce[0] = static_cast<uint8_t>(timestamp >> 24);
server_nonce[1] = static_cast<uint8_t>(timestamp >> 16);
server_nonce[2] = static_cast<uint8_t>(timestamp >> 8);
server_nonce[3] = static_cast<uint8_t>(timestamp);
rand->RandBytes(&server_nonce[sizeof(timestamp)],
sizeof(server_nonce) - sizeof(timestamp));
return server_nonce_boxer_.Box(
rand, QuicStringPiece(reinterpret_cast<char*>(server_nonce),
sizeof(server_nonce)));
}
bool QuicCryptoServerConfig::ValidateExpectedLeafCertificate(
const CryptoHandshakeMessage& client_hello,
const std::vector<QuicString>& certs) const {
if (certs.empty()) {
return false;
}
uint64_t hash_from_client;
if (client_hello.GetUint64(kXLCT, &hash_from_client) != QUIC_NO_ERROR) {
return false;
}
return CryptoUtils::ComputeLeafCertHash(certs.at(0)) == hash_from_client;
}
bool QuicCryptoServerConfig::ClientDemandsX509Proof(
const CryptoHandshakeMessage& client_hello) const {
QuicTagVector their_proof_demands;
if (client_hello.GetTaglist(kPDMD, &their_proof_demands) != QUIC_NO_ERROR) {
return false;
}
for (const QuicTag tag : their_proof_demands) {
if (tag == kX509) {
return true;
}
}
return false;
}
bool QuicCryptoServerConfig::IsNextConfigReady(QuicWallTime now) const {
if (GetQuicReloadableFlag(quic_fix_config_rotation)) {
QUIC_RELOADABLE_FLAG_COUNT(quic_fix_config_rotation);
return !next_config_promotion_time_.IsZero() &&
!next_config_promotion_time_.IsAfter(now);
}
return !next_config_promotion_time_.IsZero() &&
next_config_promotion_time_.IsAfter(now);
}
QuicCryptoServerConfig::Config::Config()
: channel_id_enabled(false),
is_primary(false),
primary_time(QuicWallTime::Zero()),
expiry_time(QuicWallTime::Zero()),
priority(0),
source_address_token_boxer(nullptr) {}
QuicCryptoServerConfig::Config::~Config() {}
QuicSignedServerConfig::QuicSignedServerConfig() {}
QuicSignedServerConfig::~QuicSignedServerConfig() {}
} // namespace quic