// 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/quic/crypto/quic_crypto_server_config.h" #include #include #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "crypto/hkdf.h" #include "crypto/secure_hash.h" #include "net/base/net_util.h" #include "net/quic/crypto/aes_128_gcm_12_decrypter.h" #include "net/quic/crypto/aes_128_gcm_12_encrypter.h" #include "net/quic/crypto/cert_compressor.h" #include "net/quic/crypto/chacha20_poly1305_encrypter.h" #include "net/quic/crypto/channel_id.h" #include "net/quic/crypto/crypto_framer.h" #include "net/quic/crypto/crypto_handshake_message.h" #include "net/quic/crypto/crypto_server_config_protobuf.h" #include "net/quic/crypto/crypto_utils.h" #include "net/quic/crypto/curve25519_key_exchange.h" #include "net/quic/crypto/ephemeral_key_source.h" #include "net/quic/crypto/key_exchange.h" #include "net/quic/crypto/local_strike_register_client.h" #include "net/quic/crypto/p256_key_exchange.h" #include "net/quic/crypto/proof_source.h" #include "net/quic/crypto/quic_decrypter.h" #include "net/quic/crypto/quic_encrypter.h" #include "net/quic/crypto/quic_random.h" #include "net/quic/crypto/strike_register.h" #include "net/quic/crypto/strike_register_client.h" #include "net/quic/proto/source_address_token.pb.h" #include "net/quic/quic_clock.h" #include "net/quic/quic_flags.h" #include "net/quic/quic_protocol.h" #include "net/quic/quic_socket_address_coder.h" #include "net/quic/quic_utils.h" using base::StringPiece; using crypto::SecureHash; using std::map; using std::sort; using std::string; using std::vector; namespace net { namespace { const int kMaxTokenAddresses = 4; string DeriveSourceAddressTokenKey(StringPiece source_address_token_secret) { crypto::HKDF hkdf(source_address_token_secret, StringPiece() /* no salt */, "QUIC source address token key", CryptoSecretBoxer::GetKeySize(), 0 /* no fixed IV needed */, 0 /* no subkey secret */); return hkdf.server_write_key().as_string(); } IPAddressNumber DualstackIPAddress(const IPAddressNumber& ip) { if (ip.size() == kIPv4AddressSize) { return ConvertIPv4NumberToIPv6Number(ip); } return ip; } } // namespace class ValidateClientHelloHelper { public: ValidateClientHelloHelper(ValidateClientHelloResultCallback::Result* result, ValidateClientHelloResultCallback* done_cb) : result_(result), done_cb_(done_cb) { } ~ValidateClientHelloHelper() { LOG_IF(DFATAL, done_cb_ != nullptr) << "Deleting ValidateClientHelloHelper with a pending callback."; } void ValidationComplete(QuicErrorCode error_code, const char* error_details) { result_->error_code = error_code; result_->error_details = error_details; done_cb_->Run(result_); DetachCallback(); } void StartedAsyncCallback() { DetachCallback(); } private: void DetachCallback() { LOG_IF(DFATAL, done_cb_ == nullptr) << "Callback already detached."; done_cb_ = nullptr; } ValidateClientHelloResultCallback::Result* result_; ValidateClientHelloResultCallback* done_cb_; DISALLOW_COPY_AND_ASSIGN(ValidateClientHelloHelper); }; class VerifyNonceIsValidAndUniqueCallback : public StrikeRegisterClient::ResultCallback { public: VerifyNonceIsValidAndUniqueCallback( ValidateClientHelloResultCallback::Result* result, ValidateClientHelloResultCallback* done_cb) : result_(result), done_cb_(done_cb) { } protected: void RunImpl(bool nonce_is_valid_and_unique, InsertStatus nonce_error) override { DVLOG(1) << "Using client nonce, unique: " << nonce_is_valid_and_unique << " nonce_error: " << nonce_error; result_->info.unique = nonce_is_valid_and_unique; if (!nonce_is_valid_and_unique) { HandshakeFailureReason client_nonce_error; switch (nonce_error) { case NONCE_INVALID_FAILURE: client_nonce_error = CLIENT_NONCE_INVALID_FAILURE; break; case NONCE_NOT_UNIQUE_FAILURE: client_nonce_error = CLIENT_NONCE_NOT_UNIQUE_FAILURE; break; case NONCE_INVALID_ORBIT_FAILURE: client_nonce_error = CLIENT_NONCE_INVALID_ORBIT_FAILURE; break; case NONCE_INVALID_TIME_FAILURE: client_nonce_error = CLIENT_NONCE_INVALID_TIME_FAILURE; break; case STRIKE_REGISTER_TIMEOUT: client_nonce_error = CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT; break; case STRIKE_REGISTER_FAILURE: client_nonce_error = CLIENT_NONCE_STRIKE_REGISTER_FAILURE; break; case NONCE_UNKNOWN_FAILURE: client_nonce_error = CLIENT_NONCE_UNKNOWN_FAILURE; break; case NONCE_OK: default: LOG(DFATAL) << "Unexpected client nonce error: " << nonce_error; client_nonce_error = CLIENT_NONCE_UNKNOWN_FAILURE; break; } result_->info.reject_reasons.push_back(client_nonce_error); } done_cb_->Run(result_); } private: ValidateClientHelloResultCallback::Result* result_; ValidateClientHelloResultCallback* done_cb_; DISALLOW_COPY_AND_ASSIGN(VerifyNonceIsValidAndUniqueCallback); }; // static const char QuicCryptoServerConfig::TESTING[] = "secret string for testing"; ClientHelloInfo::ClientHelloInfo(const IPAddressNumber& in_client_ip, QuicWallTime in_now) : client_ip(in_client_ip), now(in_now), valid_source_address_token(false), client_nonce_well_formed(false), unique(false) { } ClientHelloInfo::~ClientHelloInfo() { } PrimaryConfigChangedCallback::PrimaryConfigChangedCallback() { } PrimaryConfigChangedCallback::~PrimaryConfigChangedCallback() { } ValidateClientHelloResultCallback::Result::Result( const CryptoHandshakeMessage& in_client_hello, IPAddressNumber 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() { } void ValidateClientHelloResultCallback::Run(const Result* result) { RunImpl(result->client_hello, *result); delete result; delete this; } QuicCryptoServerConfig::ConfigOptions::ConfigOptions() : expiry_time(QuicWallTime::Zero()), channel_id_enabled(false), p256(false) {} QuicCryptoServerConfig::QuicCryptoServerConfig( StringPiece source_address_token_secret, QuicRandom* rand) : replay_protection_(true), configs_lock_(), primary_config_(nullptr), next_config_promotion_time_(QuicWallTime::Zero()), server_nonce_strike_register_lock_(), strike_register_no_startup_period_(false), strike_register_max_entries_(1 << 10), strike_register_window_secs_(600), source_address_token_future_secs_(3600), source_address_token_lifetime_secs_(86400), server_nonce_strike_register_max_entries_(1 << 10), server_nonce_strike_register_window_secs_(120) { default_source_address_token_boxer_.SetKey( DeriveSourceAddressTokenKey(source_address_token_secret)); // Generate a random key and orbit for server nonces. rand->RandBytes(server_nonce_orbit_, sizeof(server_nonce_orbit_)); const size_t key_size = server_nonce_boxer_.GetKeySize(); scoped_ptr key_bytes(new uint8[key_size]); rand->RandBytes(key_bytes.get(), key_size); server_nonce_boxer_.SetKey( StringPiece(reinterpret_cast(key_bytes.get()), key_size)); } QuicCryptoServerConfig::~QuicCryptoServerConfig() { primary_config_ = nullptr; } // static QuicServerConfigProtobuf* QuicCryptoServerConfig::GenerateConfig( QuicRandom* rand, const QuicClock* clock, const ConfigOptions& options) { CryptoHandshakeMessage msg; const string curve25519_private_key = Curve25519KeyExchange::NewPrivateKey(rand); scoped_ptr curve25519( Curve25519KeyExchange::New(curve25519_private_key)); StringPiece curve25519_public_value = curve25519->public_value(); string 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(curve25519_public_value.size())); encoded_public_values.push_back( static_cast(curve25519_public_value.size() >> 8)); encoded_public_values.push_back( static_cast(curve25519_public_value.size() >> 16)); encoded_public_values.append(curve25519_public_value.data(), curve25519_public_value.size()); string p256_private_key; if (options.p256) { p256_private_key = P256KeyExchange::NewPrivateKey(); scoped_ptr p256(P256KeyExchange::New(p256_private_key)); StringPiece p256_public_value = p256->public_value(); DCHECK_LT(p256_public_value.size(), (1U << 24)); encoded_public_values.push_back( static_cast(p256_public_value.size())); encoded_public_values.push_back( static_cast(p256_public_value.size() >> 8)); encoded_public_values.push_back( static_cast(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.SetTaglist(kKEXS, kC255, kP256, 0); } else { msg.SetTaglist(kKEXS, kC255, 0); } if (ChaCha20Poly1305Encrypter::IsSupported()) { msg.SetTaglist(kAEAD, kAESG, kCC12, 0); } else { msg.SetTaglist(kAEAD, kAESG, 0); } 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 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, StringPiece(orbit_bytes, sizeof(orbit_bytes))); if (options.channel_id_enabled) { msg.SetTaglist(kPDMD, kCHID, 0); } 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. scoped_ptr serialized( CryptoFramer::ConstructHandshakeMessage(msg)); scoped_ptr hash(SecureHash::Create(SecureHash::SHA256)); hash->Update(serialized->data(), serialized->length()); char scid_bytes[16]; hash->Finish(scid_bytes, sizeof(scid_bytes)); msg.SetStringPiece(kSCID, StringPiece(scid_bytes, sizeof(scid_bytes))); } 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 // preceeding if block. scoped_ptr serialized(CryptoFramer::ConstructHandshakeMessage(msg)); scoped_ptr config(new QuicServerConfigProtobuf); config->set_config(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.release(); } CryptoHandshakeMessage* QuicCryptoServerConfig::AddConfig( QuicServerConfigProtobuf* protobuf, const QuicWallTime now) { scoped_ptr msg( CryptoFramer::ParseMessage(protobuf->config())); if (!msg.get()) { LOG(WARNING) << "Failed to parse server config message"; return nullptr; } scoped_refptr config(ParseConfigProtobuf(protobuf)); if (!config.get()) { LOG(WARNING) << "Failed to parse server config message"; return nullptr; } { base::AutoLock locked(configs_lock_); if (configs_.find(config->id) != configs_.end()) { LOG(WARNING) << "Failed to add config because another with the same " "server config id already exists: " << base::HexEncode(config->id.data(), config->id.size()); return nullptr; } configs_[config->id] = config; SelectNewPrimaryConfig(now); DCHECK(primary_config_.get()); DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_); } return msg.release(); } CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig( QuicRandom* rand, const QuicClock* clock, const ConfigOptions& options) { scoped_ptr config( GenerateConfig(rand, clock, options)); return AddConfig(config.get(), clock->WallNow()); } bool QuicCryptoServerConfig::SetConfigs( const vector& protobufs, const QuicWallTime now) { vector > parsed_configs; bool ok = true; for (vector::const_iterator i = protobufs.begin(); i != protobufs.end(); ++i) { scoped_refptr config(ParseConfigProtobuf(*i)); if (!config.get()) { ok = false; break; } parsed_configs.push_back(config); } if (parsed_configs.empty()) { LOG(WARNING) << "New config list is empty."; ok = false; } if (!ok) { LOG(WARNING) << "Rejecting QUIC configs because of above errors"; } else { VLOG(1) << "Updating configs:"; base::AutoLock locked(configs_lock_); ConfigMap new_configs; for (vector >::const_iterator i = parsed_configs.begin(); i != parsed_configs.end(); ++i) { scoped_refptr config = *i; ConfigMap::iterator it = configs_.find(config->id); if (it != configs_.end()) { VLOG(1) << "Keeping scid: " << base::HexEncode( config->id.data(), config->id.size()) << " orbit: " << base::HexEncode( reinterpret_cast(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 { VLOG(1) << "Adding scid: " << base::HexEncode( config->id.data(), config->id.size()) << " orbit: " << base::HexEncode( reinterpret_cast(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, primary_config_); } return ok; } void QuicCryptoServerConfig::GetConfigIds(vector* scids) const { base::AutoLock locked(configs_lock_); for (ConfigMap::const_iterator it = configs_.begin(); it != configs_.end(); ++it) { scids->push_back(it->first); } } void QuicCryptoServerConfig::ValidateClientHello( const CryptoHandshakeMessage& client_hello, IPAddressNumber client_ip, const QuicClock* clock, ValidateClientHelloResultCallback* done_cb) const { const QuicWallTime now(clock->WallNow()); ValidateClientHelloResultCallback::Result* result = new ValidateClientHelloResultCallback::Result( client_hello, client_ip, now); StringPiece requested_scid; client_hello.GetStringPiece(kSCID, &requested_scid); uint8 primary_orbit[kOrbitSize]; scoped_refptr requested_config; { base::AutoLock locked(configs_lock_); if (!primary_config_.get()) { result->error_code = QUIC_CRYPTO_INTERNAL_ERROR; result->error_details = "No configurations loaded"; } else { if (!next_config_promotion_time_.IsZero() && next_config_promotion_time_.IsAfter(now)) { SelectNewPrimaryConfig(now); DCHECK(primary_config_.get()); DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_); } memcpy(primary_orbit, primary_config_->orbit, sizeof(primary_orbit)); } requested_config = GetConfigWithScid(requested_scid); } if (result->error_code == QUIC_NO_ERROR) { EvaluateClientHello(primary_orbit, requested_config, result, done_cb); } else { done_cb->Run(result); } } QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( const ValidateClientHelloResultCallback::Result& validate_chlo_result, QuicConnectionId connection_id, const IPAddressNumber& server_ip, const IPEndPoint& client_address, QuicVersion version, const QuicVersionVector& supported_versions, bool use_stateless_rejects, QuicConnectionId server_designated_connection_id, const QuicClock* clock, QuicRandom* rand, QuicCryptoNegotiatedParameters* params, CryptoHandshakeMessage* out, string* error_details) const { DCHECK(error_details); const CryptoHandshakeMessage& client_hello = validate_chlo_result.client_hello; const ClientHelloInfo& info = validate_chlo_result.info; // If the client's preferred version is not the version we are currently // speaking, then the client went through a version negotiation. In this // case, we need to make sure that we actually do not support this version // and that it wasn't a downgrade attack. QuicTag client_version_tag; if (client_hello.GetUint32(kVER, &client_version_tag) != QUIC_NO_ERROR) { *error_details = "client hello missing version list"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } QuicVersion client_version = QuicTagToQuicVersion(client_version_tag); if (client_version != version) { // Just because client_version is a valid version enum doesn't mean that // this server actually supports that version, so we check to see if // it's actually in the supported versions list. for (size_t i = 0; i < supported_versions.size(); ++i) { if (client_version == supported_versions[i]) { *error_details = "Downgrade attack detected"; return QUIC_VERSION_NEGOTIATION_MISMATCH; } } } StringPiece requested_scid; client_hello.GetStringPiece(kSCID, &requested_scid); const QuicWallTime now(clock->WallNow()); scoped_refptr requested_config; scoped_refptr primary_config; { base::AutoLock locked(configs_lock_); if (!primary_config_.get()) { *error_details = "No configurations loaded"; return QUIC_CRYPTO_INTERNAL_ERROR; } if (!next_config_promotion_time_.IsZero() && next_config_promotion_time_.IsAfter(now)) { SelectNewPrimaryConfig(now); DCHECK(primary_config_.get()); DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_); } // We'll use the config that the client requested in order to do // key-agreement. Otherwise we'll give it a copy of |primary_config_| // to use. primary_config = primary_config_; requested_config = GetConfigWithScid(requested_scid); } if (validate_chlo_result.error_code != QUIC_NO_ERROR) { *error_details = validate_chlo_result.error_details; return validate_chlo_result.error_code; } out->Clear(); if (!info.valid_source_address_token || !info.client_nonce_well_formed || !info.unique || !requested_config.get()) { BuildRejection(server_ip, *primary_config.get(), client_hello, info, validate_chlo_result.cached_network_params, use_stateless_rejects, server_designated_connection_id, rand, params, out); return QUIC_NO_ERROR; } const QuicTag* their_aeads; const QuicTag* their_key_exchanges; size_t num_their_aeads, num_their_key_exchanges; if (client_hello.GetTaglist(kAEAD, &their_aeads, &num_their_aeads) != QUIC_NO_ERROR || client_hello.GetTaglist(kKEXS, &their_key_exchanges, &num_their_key_exchanges) != QUIC_NO_ERROR || num_their_aeads != 1 || num_their_key_exchanges != 1) { *error_details = "Missing or invalid AEAD or KEXS"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } size_t key_exchange_index; if (!QuicUtils::FindMutualTag(requested_config->aead, their_aeads, num_their_aeads, QuicUtils::LOCAL_PRIORITY, ¶ms->aead, nullptr) || !QuicUtils::FindMutualTag( requested_config->kexs, their_key_exchanges, num_their_key_exchanges, QuicUtils::LOCAL_PRIORITY, ¶ms->key_exchange, &key_exchange_index)) { *error_details = "Unsupported AEAD or KEXS"; return QUIC_CRYPTO_NO_SUPPORT; } StringPiece public_value; if (!client_hello.GetStringPiece(kPUBS, &public_value)) { *error_details = "Missing public value"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } const KeyExchange* key_exchange = requested_config->key_exchanges[key_exchange_index]; if (!key_exchange->CalculateSharedKey(public_value, ¶ms->initial_premaster_secret)) { *error_details = "Invalid public value"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } if (!info.sni.empty()) { scoped_ptr sni_tmp(new char[info.sni.length() + 1]); memcpy(sni_tmp.get(), info.sni.data(), info.sni.length()); sni_tmp[info.sni.length()] = 0; params->sni = CryptoUtils::NormalizeHostname(sni_tmp.get()); } string hkdf_suffix; const QuicData& client_hello_serialized = client_hello.GetSerialized(); hkdf_suffix.reserve(sizeof(connection_id) + client_hello_serialized.length() + requested_config->serialized.size()); hkdf_suffix.append(reinterpret_cast(&connection_id), sizeof(connection_id)); hkdf_suffix.append(client_hello_serialized.data(), client_hello_serialized.length()); hkdf_suffix.append(requested_config->serialized); StringPiece 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(); string hkdf_input; hkdf_input.append(QuicCryptoConfig::kCETVLabel, strlen(QuicCryptoConfig::kCETVLabel) + 1); hkdf_input.append(reinterpret_cast(&connection_id), sizeof(connection_id)); 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, hkdf_input, Perspective::IS_SERVER, &crypters, nullptr /* subkey secret */)) { *error_details = "Symmetric key setup failed"; return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; } char plaintext[kMaxPacketSize]; size_t plaintext_length = 0; const bool success = crypters.decrypter->DecryptPacket( 0 /* sequence number */, StringPiece() /* associated data */, cetv_ciphertext, plaintext, &plaintext_length, kMaxPacketSize); if (!success) { *error_details = "CETV decryption failure"; return QUIC_PACKET_TOO_LARGE; } scoped_ptr cetv( CryptoFramer::ParseMessage(StringPiece(plaintext, plaintext_length))); if (!cetv.get()) { *error_details = "CETV parse error"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } StringPiece key, signature; if (cetv->GetStringPiece(kCIDK, &key) && cetv->GetStringPiece(kCIDS, &signature)) { if (!ChannelIDVerifier::Verify(key, hkdf_input, signature)) { *error_details = "ChannelID signature failure"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } params->channel_id = key.as_string(); } } string 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); if (!CryptoUtils::DeriveKeys( params->initial_premaster_secret, params->aead, info.client_nonce, info.server_nonce, hkdf_input, Perspective::IS_SERVER, ¶ms->initial_crypters, nullptr /* subkey secret */)) { *error_details = "Symmetric key setup failed"; return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; } string forward_secure_public_value; if (ephemeral_key_source_.get()) { params->forward_secure_premaster_secret = ephemeral_key_source_->CalculateForwardSecureKey( key_exchange, rand, clock->ApproximateNow(), public_value, &forward_secure_public_value); } else { scoped_ptr forward_secure_key_exchange( key_exchange->NewKeyPair(rand)); forward_secure_public_value = forward_secure_key_exchange->public_value().as_string(); if (!forward_secure_key_exchange->CalculateSharedKey( public_value, ¶ms->forward_secure_premaster_secret)) { *error_details = "Invalid public value"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } } string 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); if (!CryptoUtils::DeriveKeys( params->forward_secure_premaster_secret, params->aead, info.client_nonce, info.server_nonce, forward_secure_hkdf_input, Perspective::IS_SERVER, ¶ms->forward_secure_crypters, ¶ms->subkey_secret)) { *error_details = "Symmetric key setup failed"; return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; } out->set_tag(kSHLO); QuicTagVector supported_version_tags; for (size_t i = 0; i < supported_versions.size(); ++i) { supported_version_tags.push_back (QuicVersionToQuicTag(supported_versions[i])); } out->SetVector(kVER, supported_version_tags); out->SetStringPiece( kSourceAddressTokenTag, NewSourceAddressToken(*requested_config.get(), info.source_address_tokens, client_address.address(), rand, info.now, nullptr)); QuicSocketAddressCoder address_coder(client_address); out->SetStringPiece(kCADR, address_coder.Encode()); out->SetStringPiece(kPUBS, forward_secure_public_value); return QUIC_NO_ERROR; } scoped_refptr QuicCryptoServerConfig::GetConfigWithScid(StringPiece requested_scid) const { // In Chromium, we will dead lock if the lock is held by the current thread. // Chromium doesn't have AssertReaderHeld API call. // configs_lock_.AssertReaderHeld(); if (!requested_scid.empty()) { ConfigMap::const_iterator it = configs_.find(requested_scid.as_string()); if (it != configs_.end()) { // We'll use the config that the client requested in order to do // key-agreement. return scoped_refptr(it->second); } } return scoped_refptr(); } // ConfigPrimaryTimeLessThan is a comparator that implements "less than" for // Config's based on their primary_time. // static bool QuicCryptoServerConfig::ConfigPrimaryTimeLessThan( const scoped_refptr& a, const scoped_refptr& 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 { vector > configs; configs.reserve(configs_.size()); for (ConfigMap::const_iterator it = configs_.begin(); it != configs_.end(); ++it) { // TODO(avd) Exclude expired configs? configs.push_back(it->second); } if (configs.empty()) { if (primary_config_.get()) { LOG(DFATAL) << "No valid QUIC server config. Keeping the current config."; } else { LOG(DFATAL) << "No valid QUIC server config."; } return; } std::sort(configs.begin(), configs.end(), ConfigPrimaryTimeLessThan); Config* best_candidate = configs[0].get(); for (size_t i = 0; i < configs.size(); ++i) { const scoped_refptr config(configs[i]); if (!config->primary_time.IsAfter(now)) { if (config->primary_time.IsAfter(best_candidate->primary_time)) { best_candidate = config.get(); } 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_. scoped_refptr 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_.get()) { primary_config_->is_primary = false; } primary_config_ = new_primary; new_primary->is_primary = true; DVLOG(1) << "New primary config. orbit: " << base::HexEncode( reinterpret_cast(primary_config_->orbit), kOrbitSize); if (primary_config_changed_cb_.get() != 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. scoped_refptr new_primary(best_candidate); if (primary_config_.get()) { primary_config_->is_primary = false; } primary_config_ = new_primary; new_primary->is_primary = true; DVLOG(1) << "New primary config. orbit: " << base::HexEncode( reinterpret_cast(primary_config_->orbit), kOrbitSize) << " scid: " << base::HexEncode(primary_config_->id.data(), primary_config_->id.size()); next_config_promotion_time_ = QuicWallTime::Zero(); if (primary_config_changed_cb_.get() != nullptr) { primary_config_changed_cb_->Run(primary_config_->id); } } void QuicCryptoServerConfig::EvaluateClientHello( const uint8* primary_orbit, scoped_refptr requested_config, ValidateClientHelloResultCallback::Result* client_hello_state, 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 (client_hello.size() < kClientHelloMinimumSize) { helper.ValidationComplete(QUIC_CRYPTO_INVALID_VALUE_LENGTH, "Client hello too small"); return; } if (client_hello.GetStringPiece(kSNI, &info->sni) && !CryptoUtils::IsValidSNI(info->sni)) { helper.ValidationComplete(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, "Invalid SNI name"); return; } client_hello.GetStringPiece(kUAID, &info->user_agent_id); if (!requested_config.get()) { StringPiece 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, ""); return; } HandshakeFailureReason source_address_token_error; StringPiece srct; if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct)) { source_address_token_error = ParseSourceAddressToken( *requested_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; } bool found_error = false; if (source_address_token_error != HANDSHAKE_OK) { info->reject_reasons.push_back(source_address_token_error); // No valid source address token. if (FLAGS_use_early_return_when_verifying_chlo) { helper.ValidationComplete(QUIC_NO_ERROR, ""); return; } found_error = true; } if (client_hello.GetStringPiece(kNONC, &info->client_nonce) && info->client_nonce.size() == kNonceSize) { info->client_nonce_well_formed = true; } else { info->reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE); // Invalid client nonce. DVLOG(1) << "Invalid client nonce."; if (FLAGS_use_early_return_when_verifying_chlo) { helper.ValidationComplete(QUIC_NO_ERROR, ""); return; } found_error = true; } if (!replay_protection_) { if (!found_error) { info->unique = true; } DVLOG(1) << "No replay protection."; helper.ValidationComplete(QUIC_NO_ERROR, ""); return; } client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce); if (!info->server_nonce.empty()) { // If the server nonce is present, use it to establish uniqueness. HandshakeFailureReason server_nonce_error = ValidateServerNonce(info->server_nonce, info->now); if (server_nonce_error == HANDSHAKE_OK) { info->unique = true; } else { info->reject_reasons.push_back(server_nonce_error); info->unique = false; } DVLOG(1) << "Using server nonce, unique: " << info->unique; helper.ValidationComplete(QUIC_NO_ERROR, ""); return; } // If we hit this block, the server nonce was empty. If we're requiring // handshake confirmation for DoS reasons and there's no server nonce present, // reject the CHLO. if (FLAGS_quic_require_handshake_confirmation) { info->reject_reasons.push_back(SERVER_NONCE_REQUIRED_FAILURE); helper.ValidationComplete(QUIC_NO_ERROR, ""); return; } // We want to contact strike register only if there are no errors because it // is a RPC call and is expensive. if (found_error) { helper.ValidationComplete(QUIC_NO_ERROR, ""); return; } // Use the client nonce to establish uniqueness. StrikeRegisterClient* strike_register_client; { base::AutoLock locked(strike_register_client_lock_); if (strike_register_client_.get() == nullptr) { strike_register_client_.reset(new LocalStrikeRegisterClient( strike_register_max_entries_, static_cast(info->now.ToUNIXSeconds()), strike_register_window_secs_, primary_orbit, strike_register_no_startup_period_ ? StrikeRegister::NO_STARTUP_PERIOD_NEEDED : StrikeRegister::DENY_REQUESTS_AT_STARTUP)); } strike_register_client = strike_register_client_.get(); } strike_register_client->VerifyNonceIsValidAndUnique( info->client_nonce, info->now, new VerifyNonceIsValidAndUniqueCallback(client_hello_state, done_cb)); helper.StartedAsyncCallback(); } bool QuicCryptoServerConfig::BuildServerConfigUpdateMessage( const SourceAddressTokens& previous_source_address_tokens, const IPAddressNumber& server_ip, const IPAddressNumber& client_ip, const QuicClock* clock, QuicRandom* rand, const QuicCryptoNegotiatedParameters& params, const CachedNetworkParameters* cached_network_params, CryptoHandshakeMessage* out) const { base::AutoLock locked(configs_lock_); out->set_tag(kSCUP); out->SetStringPiece(kSCFG, primary_config_->serialized); out->SetStringPiece( kSourceAddressTokenTag, NewSourceAddressToken(*primary_config_.get(), previous_source_address_tokens, client_ip, rand, clock->WallNow(), cached_network_params)); if (proof_source_ == nullptr) { // Insecure QUIC, can send SCFG without proof. return true; } const vector* certs; string signature; if (!proof_source_->GetProof( server_ip, params.sni, primary_config_->serialized, params.x509_ecdsa_supported, &certs, &signature)) { DVLOG(1) << "Server: failed to get proof."; return false; } const string compressed = CertCompressor::CompressChain( *certs, params.client_common_set_hashes, params.client_cached_cert_hashes, primary_config_->common_cert_sets); out->SetStringPiece(kCertificateTag, compressed); out->SetStringPiece(kPROF, signature); return true; } void QuicCryptoServerConfig::BuildRejection( const IPAddressNumber& server_ip, 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, QuicCryptoNegotiatedParameters* params, CryptoHandshakeMessage* out) const { if (FLAGS_enable_quic_stateless_reject_support && use_stateless_rejects) { DVLOG(1) << "QUIC Crypto server config returning stateless reject " << "with server-designated connection ID " << server_designated_connection_id; out->set_tag(kSREJ); out->SetValue(kRCID, server_designated_connection_id); } 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)); 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. const QuicTag* their_proof_demands; size_t num_their_proof_demands; if (proof_source_.get() == nullptr || client_hello.GetTaglist(kPDMD, &their_proof_demands, &num_their_proof_demands) != QUIC_NO_ERROR) { return; } bool x509_supported = false; for (size_t i = 0; i < num_their_proof_demands; i++) { switch (their_proof_demands[i]) { case kX509: x509_supported = true; params->x509_ecdsa_supported = true; break; case kX59R: x509_supported = true; break; } } if (!x509_supported) { return; } const vector* certs; string signature; if (!proof_source_->GetProof(server_ip, info.sni.as_string(), config.serialized, params->x509_ecdsa_supported, &certs, &signature)) { return; } StringPiece client_common_set_hashes; if (client_hello.GetStringPiece(kCCS, &client_common_set_hashes)) { params->client_common_set_hashes = client_common_set_hashes.as_string(); } StringPiece client_cached_cert_hashes; if (client_hello.GetStringPiece(kCCRT, &client_cached_cert_hashes)) { params->client_cached_cert_hashes = client_cached_cert_hashes.as_string(); } const string compressed = CertCompressor::CompressChain( *certs, params->client_common_set_hashes, params->client_cached_cert_hashes, config.common_cert_sets); // 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; // 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. const size_t kMultiplier = 2; // max_unverified_size is the number of bytes that the certificate chain // and signature can consume before we will demand a valid source-address // token. const size_t max_unverified_size = client_hello.size() * kMultiplier - kREJOverheadBytes; static_assert(kClientHelloMinimumSize * kMultiplier >= kREJOverheadBytes, "overhead calculation may overflow"); if (info.valid_source_address_token || signature.size() + compressed.size() < max_unverified_size) { out->SetStringPiece(kCertificateTag, compressed); out->SetStringPiece(kPROF, signature); } } scoped_refptr QuicCryptoServerConfig::ParseConfigProtobuf( QuicServerConfigProtobuf* protobuf) { scoped_ptr msg( CryptoFramer::ParseMessage(protobuf->config())); if (msg->tag() != kSCFG) { LOG(WARNING) << "Server config message has tag " << msg->tag() << " expected " << kSCFG; return nullptr; } scoped_refptr config(new Config); config->serialized = protobuf->config(); if (!protobuf->has_source_address_token_secret_override()) { // Use the default boxer. config->source_address_token_boxer = &default_source_address_token_boxer_; } else { // Create override boxer instance. CryptoSecretBoxer* boxer = new CryptoSecretBoxer; boxer->SetKey(DeriveSourceAddressTokenKey( protobuf->source_address_token_secret_override())); config->source_address_token_boxer_storage.reset(boxer); config->source_address_token_boxer = boxer; } if (protobuf->has_primary_time()) { config->primary_time = QuicWallTime::FromUNIXSeconds(protobuf->primary_time()); } config->priority = protobuf->priority(); StringPiece scid; if (!msg->GetStringPiece(kSCID, &scid)) { LOG(WARNING) << "Server config message is missing SCID"; return nullptr; } config->id = scid.as_string(); const QuicTag* aead_tags; size_t aead_len; if (msg->GetTaglist(kAEAD, &aead_tags, &aead_len) != QUIC_NO_ERROR) { LOG(WARNING) << "Server config message is missing AEAD"; return nullptr; } config->aead = vector(aead_tags, aead_tags + aead_len); const QuicTag* kexs_tags; size_t kexs_len; if (msg->GetTaglist(kKEXS, &kexs_tags, &kexs_len) != QUIC_NO_ERROR) { LOG(WARNING) << "Server config message is missing KEXS"; return nullptr; } StringPiece orbit; if (!msg->GetStringPiece(kORBT, &orbit)) { LOG(WARNING) << "Server config message is missing ORBT"; return nullptr; } if (orbit.size() != kOrbitSize) { LOG(WARNING) << "Orbit value in server config is the wrong length." " Got " << orbit.size() << " want " << kOrbitSize; return nullptr; } static_assert(sizeof(config->orbit) == kOrbitSize, "orbit has incorrect size"); memcpy(config->orbit, orbit.data(), sizeof(config->orbit)); { StrikeRegisterClient* strike_register_client; { base::AutoLock locked(strike_register_client_lock_); strike_register_client = strike_register_client_.get(); } if (strike_register_client != nullptr && !strike_register_client->IsKnownOrbit(orbit)) { LOG(WARNING) << "Rejecting server config with orbit that the strike register " "client doesn't know about."; return nullptr; } } if (kexs_len != protobuf->key_size()) { LOG(WARNING) << "Server config has " << kexs_len << " key exchange methods configured, but " << protobuf->key_size() << " private keys"; return nullptr; } const QuicTag* proof_demand_tags; size_t num_proof_demand_tags; if (msg->GetTaglist(kPDMD, &proof_demand_tags, &num_proof_demand_tags) == QUIC_NO_ERROR) { for (size_t i = 0; i < num_proof_demand_tags; i++) { if (proof_demand_tags[i] == kCHID) { config->channel_id_enabled = true; break; } } } for (size_t i = 0; i < kexs_len; i++) { const QuicTag tag = kexs_tags[i]; string private_key; config->kexs.push_back(tag); for (size_t j = 0; j < protobuf->key_size(); j++) { const QuicServerConfigProtobuf::PrivateKey& key = protobuf->key(i); if (key.tag() == tag) { private_key = key.private_key(); break; } } if (private_key.empty()) { LOG(WARNING) << "Server config contains key exchange method without " "corresponding private key: " << tag; return nullptr; } scoped_ptr ka; switch (tag) { case kC255: ka.reset(Curve25519KeyExchange::New(private_key)); if (!ka.get()) { LOG(WARNING) << "Server config contained an invalid curve25519" " private key."; return nullptr; } break; case kP256: ka.reset(P256KeyExchange::New(private_key)); if (!ka.get()) { LOG(WARNING) << "Server config contained an invalid P-256" " private key."; return nullptr; } break; default: LOG(WARNING) << "Server config message contains unknown key exchange " "method: " << tag; return nullptr; } for (const KeyExchange* key_exchange : config->key_exchanges) { if (key_exchange->tag() == tag) { LOG(WARNING) << "Duplicate key exchange in config: " << tag; return nullptr; } } config->key_exchanges.push_back(ka.release()); } return config; } void QuicCryptoServerConfig::SetProofSource(ProofSource* proof_source) { proof_source_.reset(proof_source); } void QuicCryptoServerConfig::SetEphemeralKeySource( EphemeralKeySource* ephemeral_key_source) { ephemeral_key_source_.reset(ephemeral_key_source); } void QuicCryptoServerConfig::SetStrikeRegisterClient( StrikeRegisterClient* strike_register_client) { base::AutoLock locker(strike_register_client_lock_); DCHECK(!strike_register_client_.get()); strike_register_client_.reset(strike_register_client); } void QuicCryptoServerConfig::set_replay_protection(bool on) { replay_protection_ = on; } void QuicCryptoServerConfig::set_strike_register_no_startup_period() { base::AutoLock locker(strike_register_client_lock_); DCHECK(!strike_register_client_.get()); strike_register_no_startup_period_ = true; } void QuicCryptoServerConfig::set_strike_register_max_entries( uint32 max_entries) { base::AutoLock locker(strike_register_client_lock_); DCHECK(!strike_register_client_.get()); strike_register_max_entries_ = max_entries; } void QuicCryptoServerConfig::set_strike_register_window_secs( uint32 window_secs) { base::AutoLock locker(strike_register_client_lock_); DCHECK(!strike_register_client_.get()); strike_register_window_secs_ = window_secs; } void QuicCryptoServerConfig::set_source_address_token_future_secs( uint32 future_secs) { source_address_token_future_secs_ = future_secs; } void QuicCryptoServerConfig::set_source_address_token_lifetime_secs( uint32 lifetime_secs) { source_address_token_lifetime_secs_ = lifetime_secs; } void QuicCryptoServerConfig::set_server_nonce_strike_register_max_entries( uint32 max_entries) { DCHECK(!server_nonce_strike_register_.get()); server_nonce_strike_register_max_entries_ = max_entries; } void QuicCryptoServerConfig::set_server_nonce_strike_register_window_secs( uint32 window_secs) { DCHECK(!server_nonce_strike_register_.get()); server_nonce_strike_register_window_secs_ = window_secs; } void QuicCryptoServerConfig::AcquirePrimaryConfigChangedCb( PrimaryConfigChangedCallback* cb) { base::AutoLock locked(configs_lock_); primary_config_changed_cb_.reset(cb); } string QuicCryptoServerConfig::NewSourceAddressToken( const Config& config, const SourceAddressTokens& previous_tokens, const IPAddressNumber& 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(IPAddressToPackedString(DualstackIPAddress(ip))); 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()); } bool QuicCryptoServerConfig::HasProofSource() const { return proof_source_ != nullptr; } int QuicCryptoServerConfig::NumberOfConfigs() const { base::AutoLock locked(configs_lock_); return configs_.size(); } HandshakeFailureReason QuicCryptoServerConfig::ParseSourceAddressToken( const Config& config, StringPiece token, SourceAddressTokens* tokens) const { string storage; StringPiece 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 source_address_token; if (!source_address_token.ParseFromArray(plaintext.data(), plaintext.size())) { return SOURCE_ADDRESS_TOKEN_PARSE_FAILURE; } *tokens->add_tokens() = source_address_token; } return HANDSHAKE_OK; } HandshakeFailureReason QuicCryptoServerConfig::ValidateSourceAddressTokens( const SourceAddressTokens& source_address_tokens, const IPAddressNumber& 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 IPAddressNumber& ip, QuicWallTime now) const { if (source_address_token.ip() != IPAddressToPackedString(DualstackIPAddress(ip))) { // 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 */; string QuicCryptoServerConfig::NewServerNonce(QuicRandom* rand, QuicWallTime now) const { const uint32 timestamp = static_cast(now.ToUNIXSeconds()); uint8 server_nonce[kServerNoncePlaintextSize]; static_assert(sizeof(server_nonce) > sizeof(timestamp), "nonce too small"); server_nonce[0] = static_cast(timestamp >> 24); server_nonce[1] = static_cast(timestamp >> 16); server_nonce[2] = static_cast(timestamp >> 8); server_nonce[3] = static_cast(timestamp); rand->RandBytes(&server_nonce[sizeof(timestamp)], sizeof(server_nonce) - sizeof(timestamp)); return server_nonce_boxer_.Box( rand, StringPiece(reinterpret_cast(server_nonce), sizeof(server_nonce))); } HandshakeFailureReason QuicCryptoServerConfig::ValidateServerNonce( StringPiece token, QuicWallTime now) const { string storage; StringPiece plaintext; if (!server_nonce_boxer_.Unbox(token, &storage, &plaintext)) { return SERVER_NONCE_DECRYPTION_FAILURE; } // plaintext contains: // uint32 timestamp // uint8[20] random bytes if (plaintext.size() != kServerNoncePlaintextSize) { // This should never happen because the value decrypted correctly. LOG(DFATAL) << "Seemingly valid server nonce had incorrect length."; return SERVER_NONCE_INVALID_FAILURE; } uint8 server_nonce[32]; memcpy(server_nonce, plaintext.data(), 4); memcpy(server_nonce + 4, server_nonce_orbit_, sizeof(server_nonce_orbit_)); memcpy(server_nonce + 4 + sizeof(server_nonce_orbit_), plaintext.data() + 4, 20); static_assert(4 + sizeof(server_nonce_orbit_) + 20 == sizeof(server_nonce), "bad nonce buffer length"); InsertStatus nonce_error; { base::AutoLock auto_lock(server_nonce_strike_register_lock_); if (server_nonce_strike_register_.get() == nullptr) { server_nonce_strike_register_.reset(new StrikeRegister( server_nonce_strike_register_max_entries_, static_cast(now.ToUNIXSeconds()), server_nonce_strike_register_window_secs_, server_nonce_orbit_, StrikeRegister::NO_STARTUP_PERIOD_NEEDED)); } nonce_error = server_nonce_strike_register_->Insert( server_nonce, static_cast(now.ToUNIXSeconds())); } switch (nonce_error) { case NONCE_OK: return HANDSHAKE_OK; case NONCE_INVALID_FAILURE: case NONCE_INVALID_ORBIT_FAILURE: return SERVER_NONCE_INVALID_FAILURE; case NONCE_NOT_UNIQUE_FAILURE: return SERVER_NONCE_NOT_UNIQUE_FAILURE; case NONCE_INVALID_TIME_FAILURE: return SERVER_NONCE_INVALID_TIME_FAILURE; case NONCE_UNKNOWN_FAILURE: case STRIKE_REGISTER_TIMEOUT: case STRIKE_REGISTER_FAILURE: default: LOG(DFATAL) << "Unexpected server nonce error: " << nonce_error; return SERVER_NONCE_NOT_UNIQUE_FAILURE; } } QuicCryptoServerConfig::Config::Config() : channel_id_enabled(false), is_primary(false), primary_time(QuicWallTime::Zero()), priority(0), source_address_token_boxer(nullptr) {} QuicCryptoServerConfig::Config::~Config() { STLDeleteElements(&key_exchanges); } } // namespace net