| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/quic/crypto/crypto_handshake.h" |
| |
| #include <ctype.h> |
| |
| #include "base/memory/scoped_ptr.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "crypto/secure_hash.h" |
| #include "net/base/net_util.h" |
| #include "net/quic/crypto/cert_compressor.h" |
| #include "net/quic/crypto/channel_id.h" |
| #include "net/quic/crypto/common_cert_set.h" |
| #include "net/quic/crypto/crypto_framer.h" |
| #include "net/quic/crypto/crypto_utils.h" |
| #include "net/quic/crypto/curve25519_key_exchange.h" |
| #include "net/quic/crypto/key_exchange.h" |
| #include "net/quic/crypto/p256_key_exchange.h" |
| #include "net/quic/crypto/proof_verifier.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/quic_protocol.h" |
| #include "net/quic/quic_utils.h" |
| |
| using base::StringPiece; |
| using base::StringPrintf; |
| using std::map; |
| using std::string; |
| using std::vector; |
| |
| namespace net { |
| |
| CryptoHandshakeMessage::CryptoHandshakeMessage() |
| : tag_(0), |
| minimum_size_(0) {} |
| |
| CryptoHandshakeMessage::CryptoHandshakeMessage( |
| const CryptoHandshakeMessage& other) |
| : tag_(other.tag_), |
| tag_value_map_(other.tag_value_map_), |
| minimum_size_(other.minimum_size_) { |
| // Don't copy serialized_. scoped_ptr doesn't have a copy constructor. |
| // The new object can lazily reconstruct serialized_. |
| } |
| |
| CryptoHandshakeMessage::~CryptoHandshakeMessage() {} |
| |
| CryptoHandshakeMessage& CryptoHandshakeMessage::operator=( |
| const CryptoHandshakeMessage& other) { |
| tag_ = other.tag_; |
| tag_value_map_ = other.tag_value_map_; |
| // Don't copy serialized_. scoped_ptr doesn't have an assignment operator. |
| // However, invalidate serialized_. |
| serialized_.reset(); |
| minimum_size_ = other.minimum_size_; |
| return *this; |
| } |
| |
| void CryptoHandshakeMessage::Clear() { |
| tag_ = 0; |
| tag_value_map_.clear(); |
| minimum_size_ = 0; |
| serialized_.reset(); |
| } |
| |
| const QuicData& CryptoHandshakeMessage::GetSerialized() const { |
| if (!serialized_.get()) { |
| serialized_.reset(CryptoFramer::ConstructHandshakeMessage(*this)); |
| } |
| return *serialized_.get(); |
| } |
| |
| void CryptoHandshakeMessage::MarkDirty() { |
| serialized_.reset(); |
| } |
| |
| void CryptoHandshakeMessage::Insert(QuicTagValueMap::const_iterator begin, |
| QuicTagValueMap::const_iterator end) { |
| tag_value_map_.insert(begin, end); |
| } |
| |
| void CryptoHandshakeMessage::SetTaglist(QuicTag tag, ...) { |
| // Warning, if sizeof(QuicTag) > sizeof(int) then this function will break |
| // because the terminating 0 will only be promoted to int. |
| COMPILE_ASSERT(sizeof(QuicTag) <= sizeof(int), |
| crypto_tag_may_not_be_larger_than_int_or_varargs_will_break); |
| |
| vector<QuicTag> tags; |
| va_list ap; |
| |
| va_start(ap, tag); |
| for (;;) { |
| QuicTag list_item = va_arg(ap, QuicTag); |
| if (list_item == 0) { |
| break; |
| } |
| tags.push_back(list_item); |
| } |
| |
| // Because of the way that we keep tags in memory, we can copy the contents |
| // of the vector and get the correct bytes in wire format. See |
| // crypto_protocol.h. This assumes that the system is little-endian. |
| SetVector(tag, tags); |
| |
| va_end(ap); |
| } |
| |
| void CryptoHandshakeMessage::SetStringPiece(QuicTag tag, StringPiece value) { |
| tag_value_map_[tag] = value.as_string(); |
| } |
| |
| void CryptoHandshakeMessage::Erase(QuicTag tag) { |
| tag_value_map_.erase(tag); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetTaglist(QuicTag tag, |
| const QuicTag** out_tags, |
| size_t* out_len) const { |
| QuicTagValueMap::const_iterator it = tag_value_map_.find(tag); |
| QuicErrorCode ret = QUIC_NO_ERROR; |
| |
| if (it == tag_value_map_.end()) { |
| ret = QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } else if (it->second.size() % sizeof(QuicTag) != 0) { |
| ret = QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| if (ret != QUIC_NO_ERROR) { |
| *out_tags = NULL; |
| *out_len = 0; |
| return ret; |
| } |
| |
| *out_tags = reinterpret_cast<const QuicTag*>(it->second.data()); |
| *out_len = it->second.size() / sizeof(QuicTag); |
| return ret; |
| } |
| |
| bool CryptoHandshakeMessage::GetStringPiece(QuicTag tag, |
| StringPiece* out) const { |
| QuicTagValueMap::const_iterator it = tag_value_map_.find(tag); |
| if (it == tag_value_map_.end()) { |
| return false; |
| } |
| *out = it->second; |
| return true; |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetNthValue24(QuicTag tag, |
| unsigned index, |
| StringPiece* out) const { |
| StringPiece value; |
| if (!GetStringPiece(tag, &value)) { |
| return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } |
| |
| for (unsigned i = 0;; i++) { |
| if (value.empty()) { |
| return QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND; |
| } |
| if (value.size() < 3) { |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| const unsigned char* data = |
| reinterpret_cast<const unsigned char*>(value.data()); |
| size_t size = static_cast<size_t>(data[0]) | |
| (static_cast<size_t>(data[1]) << 8) | |
| (static_cast<size_t>(data[2]) << 16); |
| value.remove_prefix(3); |
| |
| if (value.size() < size) { |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| if (i == index) { |
| *out = StringPiece(value.data(), size); |
| return QUIC_NO_ERROR; |
| } |
| |
| value.remove_prefix(size); |
| } |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetUint16(QuicTag tag, |
| uint16* out) const { |
| return GetPOD(tag, out, sizeof(uint16)); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetUint32(QuicTag tag, |
| uint32* out) const { |
| return GetPOD(tag, out, sizeof(uint32)); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetUint64(QuicTag tag, |
| uint64* out) const { |
| return GetPOD(tag, out, sizeof(uint64)); |
| } |
| |
| size_t CryptoHandshakeMessage::size() const { |
| size_t ret = sizeof(QuicTag) + |
| sizeof(uint16) /* number of entries */ + |
| sizeof(uint16) /* padding */; |
| ret += (sizeof(QuicTag) + sizeof(uint32) /* end offset */) * |
| tag_value_map_.size(); |
| for (QuicTagValueMap::const_iterator i = tag_value_map_.begin(); |
| i != tag_value_map_.end(); ++i) { |
| ret += i->second.size(); |
| } |
| |
| return ret; |
| } |
| |
| void CryptoHandshakeMessage::set_minimum_size(size_t min_bytes) { |
| if (min_bytes == minimum_size_) { |
| return; |
| } |
| serialized_.reset(); |
| minimum_size_ = min_bytes; |
| } |
| |
| size_t CryptoHandshakeMessage::minimum_size() const { |
| return minimum_size_; |
| } |
| |
| string CryptoHandshakeMessage::DebugString() const { |
| return DebugStringInternal(0); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetPOD( |
| QuicTag tag, void* out, size_t len) const { |
| QuicTagValueMap::const_iterator it = tag_value_map_.find(tag); |
| QuicErrorCode ret = QUIC_NO_ERROR; |
| |
| if (it == tag_value_map_.end()) { |
| ret = QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } else if (it->second.size() != len) { |
| ret = QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| if (ret != QUIC_NO_ERROR) { |
| memset(out, 0, len); |
| return ret; |
| } |
| |
| memcpy(out, it->second.data(), len); |
| return ret; |
| } |
| |
| string CryptoHandshakeMessage::DebugStringInternal(size_t indent) const { |
| string ret = string(2 * indent, ' ') + QuicUtils::TagToString(tag_) + "<\n"; |
| ++indent; |
| for (QuicTagValueMap::const_iterator it = tag_value_map_.begin(); |
| it != tag_value_map_.end(); ++it) { |
| ret += string(2 * indent, ' ') + QuicUtils::TagToString(it->first) + ": "; |
| |
| bool done = false; |
| switch (it->first) { |
| case kKATO: |
| case kVERS: |
| // uint32 value |
| if (it->second.size() == 4) { |
| uint32 value; |
| memcpy(&value, it->second.data(), sizeof(value)); |
| ret += base::UintToString(value); |
| done = true; |
| } |
| break; |
| case kKEXS: |
| case kAEAD: |
| case kCGST: |
| case kPDMD: |
| // tag lists |
| if (it->second.size() % sizeof(QuicTag) == 0) { |
| for (size_t j = 0; j < it->second.size(); j += sizeof(QuicTag)) { |
| QuicTag tag; |
| memcpy(&tag, it->second.data() + j, sizeof(tag)); |
| if (j > 0) { |
| ret += ","; |
| } |
| ret += QuicUtils::TagToString(tag); |
| } |
| done = true; |
| } |
| break; |
| case kSCFG: |
| // nested messages. |
| if (!it->second.empty()) { |
| scoped_ptr<CryptoHandshakeMessage> msg( |
| CryptoFramer::ParseMessage(it->second)); |
| if (msg.get()) { |
| ret += "\n"; |
| ret += msg->DebugStringInternal(indent + 1); |
| |
| done = true; |
| } |
| } |
| break; |
| case kPAD: |
| ret += StringPrintf("(%d bytes of padding)", |
| static_cast<int>(it->second.size())); |
| done = true; |
| break; |
| } |
| |
| if (!done) { |
| // If there's no specific format for this tag, or the value is invalid, |
| // then just use hex. |
| ret += base::HexEncode(it->second.data(), it->second.size()); |
| } |
| ret += "\n"; |
| } |
| --indent; |
| ret += string(2 * indent, ' ') + ">"; |
| return ret; |
| } |
| |
| QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters() |
| : version(0), |
| key_exchange(0), |
| aead(0) { |
| } |
| |
| QuicCryptoNegotiatedParameters::~QuicCryptoNegotiatedParameters() {} |
| |
| CrypterPair::CrypterPair() {} |
| |
| CrypterPair::~CrypterPair() {} |
| |
| // static |
| const char QuicCryptoConfig::kInitialLabel[] = "QUIC key expansion"; |
| |
| // static |
| const char QuicCryptoConfig::kCETVLabel[] = "QUIC CETV block"; |
| |
| // static |
| const char QuicCryptoConfig::kForwardSecureLabel[] = |
| "QUIC forward secure key expansion"; |
| |
| QuicCryptoConfig::QuicCryptoConfig() |
| : version(0), |
| common_cert_sets(CommonCertSets::GetInstanceQUIC()) { |
| } |
| |
| QuicCryptoConfig::~QuicCryptoConfig() {} |
| |
| QuicCryptoClientConfig::QuicCryptoClientConfig() {} |
| |
| QuicCryptoClientConfig::~QuicCryptoClientConfig() { |
| STLDeleteValues(&cached_states_); |
| } |
| |
| QuicCryptoClientConfig::CachedState::CachedState() |
| : server_config_valid_(false) {} |
| |
| QuicCryptoClientConfig::CachedState::~CachedState() {} |
| |
| bool QuicCryptoClientConfig::CachedState::IsComplete(QuicWallTime now) const { |
| if (server_config_.empty() || !server_config_valid_) { |
| return false; |
| } |
| |
| const CryptoHandshakeMessage* scfg = GetServerConfig(); |
| if (!scfg) { |
| // Should be impossible short of cache corruption. |
| DCHECK(false); |
| return false; |
| } |
| |
| uint64 expiry_seconds; |
| if (scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR || |
| now.ToUNIXSeconds() >= expiry_seconds) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| const CryptoHandshakeMessage* |
| QuicCryptoClientConfig::CachedState::GetServerConfig() const { |
| if (server_config_.empty()) { |
| return NULL; |
| } |
| |
| if (!scfg_.get()) { |
| scfg_.reset(CryptoFramer::ParseMessage(server_config_)); |
| DCHECK(scfg_.get()); |
| } |
| return scfg_.get(); |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::CachedState::SetServerConfig( |
| StringPiece server_config, QuicWallTime now, string* error_details) { |
| const bool matches_existing = server_config == server_config_; |
| |
| // Even if the new server config matches the existing one, we still wish to |
| // reject it if it has expired. |
| scoped_ptr<CryptoHandshakeMessage> new_scfg_storage; |
| const CryptoHandshakeMessage* new_scfg; |
| |
| if (!matches_existing) { |
| new_scfg_storage.reset(CryptoFramer::ParseMessage(server_config)); |
| new_scfg = new_scfg_storage.get(); |
| } else { |
| new_scfg = GetServerConfig(); |
| } |
| |
| if (!new_scfg) { |
| *error_details = "SCFG invalid"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| uint64 expiry_seconds; |
| if (new_scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) { |
| *error_details = "SCFG missing EXPY"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| if (now.ToUNIXSeconds() >= expiry_seconds) { |
| *error_details = "SCFG has expired"; |
| return QUIC_CRYPTO_SERVER_CONFIG_EXPIRED; |
| } |
| |
| if (!matches_existing) { |
| server_config_ = server_config.as_string(); |
| server_config_valid_ = false; |
| scfg_.reset(new_scfg_storage.release()); |
| } |
| return QUIC_NO_ERROR; |
| } |
| |
| void QuicCryptoClientConfig::CachedState::InvalidateServerConfig() { |
| server_config_.clear(); |
| scfg_.reset(); |
| server_config_valid_ = false; |
| } |
| |
| void QuicCryptoClientConfig::CachedState::SetProof(const vector<string>& certs, |
| StringPiece signature) { |
| bool has_changed = |
| signature != server_config_sig_ || certs_.size() != certs.size(); |
| |
| if (!has_changed) { |
| for (size_t i = 0; i < certs_.size(); i++) { |
| if (certs_[i] != certs[i]) { |
| has_changed = true; |
| break; |
| } |
| } |
| } |
| |
| if (!has_changed) { |
| return; |
| } |
| |
| // If the proof has changed then it needs to be revalidated. |
| server_config_valid_ = false; |
| certs_ = certs; |
| server_config_sig_ = signature.as_string(); |
| } |
| |
| void QuicCryptoClientConfig::CachedState::SetProofValid() { |
| server_config_valid_ = true; |
| } |
| |
| const string& QuicCryptoClientConfig::CachedState::server_config() const { |
| return server_config_; |
| } |
| |
| const string& |
| QuicCryptoClientConfig::CachedState::source_address_token() const { |
| return source_address_token_; |
| } |
| |
| const vector<string>& QuicCryptoClientConfig::CachedState::certs() const { |
| return certs_; |
| } |
| |
| const string& QuicCryptoClientConfig::CachedState::signature() const { |
| return server_config_sig_; |
| } |
| |
| bool QuicCryptoClientConfig::CachedState::proof_valid() const { |
| return server_config_valid_; |
| } |
| |
| void QuicCryptoClientConfig::CachedState::set_source_address_token( |
| StringPiece token) { |
| source_address_token_ = token.as_string(); |
| } |
| |
| void QuicCryptoClientConfig::SetDefaults() { |
| // Version must be 0. |
| // TODO(agl): this version stuff is obsolete now. |
| version = QuicCryptoConfig::CONFIG_VERSION; |
| |
| // Key exchange methods. |
| kexs.resize(2); |
| kexs[0] = kC255; |
| kexs[1] = kP256; |
| |
| // Authenticated encryption algorithms. |
| aead.resize(1); |
| aead[0] = kAESG; |
| } |
| |
| QuicCryptoClientConfig::CachedState* QuicCryptoClientConfig::LookupOrCreate( |
| const string& server_hostname) { |
| map<string, CachedState*>::const_iterator it = |
| cached_states_.find(server_hostname); |
| if (it != cached_states_.end()) { |
| return it->second; |
| } |
| |
| CachedState* cached = new CachedState; |
| cached_states_.insert(make_pair(server_hostname, cached)); |
| return cached; |
| } |
| |
| void QuicCryptoClientConfig::FillInchoateClientHello( |
| const string& server_hostname, |
| const CachedState* cached, |
| QuicCryptoNegotiatedParameters* out_params, |
| CryptoHandshakeMessage* out) const { |
| out->set_tag(kCHLO); |
| out->set_minimum_size(kClientHelloMinimumSize); |
| |
| // Server name indication. We only send SNI if it's a valid domain name, as |
| // per the spec. |
| if (CryptoUtils::IsValidSNI(server_hostname)) { |
| out->SetStringPiece(kSNI, server_hostname); |
| } |
| out->SetValue(kVERS, version); |
| |
| if (!cached->source_address_token().empty()) { |
| out->SetStringPiece(kSourceAddressTokenTag, cached->source_address_token()); |
| } |
| |
| if (proof_verifier_.get()) { |
| out->SetTaglist(kPDMD, kX509, 0); |
| } |
| |
| if (common_cert_sets) { |
| out->SetStringPiece(kCCS, common_cert_sets->GetCommonHashes()); |
| } |
| |
| const vector<string>& certs = cached->certs(); |
| // We save |certs| in the QuicCryptoNegotiatedParameters so that, if the |
| // client config is being used for multiple connections, another connection |
| // doesn't update the cached certificates and cause us to be unable to |
| // process the server's compressed certificate chain. |
| out_params->cached_certs = certs; |
| if (!certs.empty()) { |
| vector<uint64> hashes; |
| hashes.reserve(certs.size()); |
| for (vector<string>::const_iterator i = certs.begin(); |
| i != certs.end(); ++i) { |
| hashes.push_back(QuicUtils::FNV1a_64_Hash(i->data(), i->size())); |
| } |
| out->SetVector(kCCRT, hashes); |
| } |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::FillClientHello( |
| const string& server_hostname, |
| QuicGuid guid, |
| const CachedState* cached, |
| QuicWallTime now, |
| QuicRandom* rand, |
| QuicCryptoNegotiatedParameters* out_params, |
| CryptoHandshakeMessage* out, |
| string* error_details) const { |
| DCHECK(error_details != NULL); |
| |
| FillInchoateClientHello(server_hostname, cached, out_params, out); |
| |
| const CryptoHandshakeMessage* scfg = cached->GetServerConfig(); |
| if (!scfg) { |
| // This should never happen as our caller should have checked |
| // cached->IsComplete() before calling this function. |
| *error_details = "Handshake not ready"; |
| return QUIC_CRYPTO_INTERNAL_ERROR; |
| } |
| |
| StringPiece scid; |
| if (!scfg->GetStringPiece(kSCID, &scid)) { |
| *error_details = "SCFG missing SCID"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| out->SetStringPiece(kSCID, scid); |
| |
| const QuicTag* their_aeads; |
| const QuicTag* their_key_exchanges; |
| size_t num_their_aeads, num_their_key_exchanges; |
| if (scfg->GetTaglist(kAEAD, &their_aeads, |
| &num_their_aeads) != QUIC_NO_ERROR || |
| scfg->GetTaglist(kKEXS, &their_key_exchanges, |
| &num_their_key_exchanges) != QUIC_NO_ERROR) { |
| *error_details = "Missing AEAD or KEXS"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| size_t key_exchange_index; |
| if (!QuicUtils::FindMutualTag( |
| aead, their_aeads, num_their_aeads, QuicUtils::PEER_PRIORITY, |
| &out_params->aead, NULL) || |
| !QuicUtils::FindMutualTag( |
| kexs, their_key_exchanges, num_their_key_exchanges, |
| QuicUtils::PEER_PRIORITY, &out_params->key_exchange, |
| &key_exchange_index)) { |
| *error_details = "Unsupported AEAD or KEXS"; |
| return QUIC_CRYPTO_NO_SUPPORT; |
| } |
| out->SetTaglist(kAEAD, out_params->aead, 0); |
| out->SetTaglist(kKEXS, out_params->key_exchange, 0); |
| |
| StringPiece public_value; |
| if (scfg->GetNthValue24(kPUBS, key_exchange_index, &public_value) != |
| QUIC_NO_ERROR) { |
| *error_details = "Missing public value"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| StringPiece orbit; |
| if (!scfg->GetStringPiece(kORBT, &orbit) || orbit.size() != kOrbitSize) { |
| *error_details = "SCFG missing OBIT"; |
| return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } |
| |
| CryptoUtils::GenerateNonce(now, rand, orbit, &out_params->client_nonce); |
| out->SetStringPiece(kNONC, out_params->client_nonce); |
| if (!out_params->server_nonce.empty()) { |
| out->SetStringPiece(kServerNonceTag, out_params->server_nonce); |
| } |
| |
| switch (out_params->key_exchange) { |
| case kC255: |
| out_params->client_key_exchange.reset(Curve25519KeyExchange::New( |
| Curve25519KeyExchange::NewPrivateKey(rand))); |
| break; |
| case kP256: |
| out_params->client_key_exchange.reset(P256KeyExchange::New( |
| P256KeyExchange::NewPrivateKey())); |
| break; |
| default: |
| DCHECK(false); |
| *error_details = "Configured to support an unknown key exchange"; |
| return QUIC_CRYPTO_INTERNAL_ERROR; |
| } |
| |
| if (!out_params->client_key_exchange->CalculateSharedKey( |
| public_value, &out_params->initial_premaster_secret)) { |
| *error_details = "Key exchange failure"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| out->SetStringPiece(kPUBS, out_params->client_key_exchange->public_value()); |
| |
| bool do_channel_id = false; |
| if (channel_id_signer_.get()) { |
| const QuicTag* their_proof_demands; |
| size_t num_their_proof_demands; |
| if (scfg->GetTaglist(kPDMD, &their_proof_demands, |
| &num_their_proof_demands) == QUIC_NO_ERROR) { |
| for (size_t i = 0; i < num_their_proof_demands; i++) { |
| if (their_proof_demands[i] == kCHID) { |
| do_channel_id = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (do_channel_id) { |
| // In order to calculate the encryption key for the CETV block we need to |
| // serialise the client hello as it currently is (i.e. without the CETV |
| // block). For this, the client hello is serialized without padding. |
| const size_t orig_min_size = out->minimum_size(); |
| out->set_minimum_size(0); |
| |
| CryptoHandshakeMessage cetv; |
| cetv.set_tag(kCETV); |
| |
| string hkdf_input; |
| const QuicData& client_hello_serialized = out->GetSerialized(); |
| hkdf_input.append(QuicCryptoConfig::kCETVLabel, |
| strlen(QuicCryptoConfig::kCETVLabel) + 1); |
| hkdf_input.append(reinterpret_cast<char*>(&guid), sizeof(guid)); |
| hkdf_input.append(client_hello_serialized.data(), |
| client_hello_serialized.length()); |
| hkdf_input.append(cached->server_config()); |
| |
| string key, signature; |
| if (!channel_id_signer_->Sign(server_hostname, hkdf_input, |
| &key, &signature)) { |
| *error_details = "Channel ID signature failed"; |
| return QUIC_INTERNAL_ERROR; |
| } |
| |
| cetv.SetStringPiece(kCIDK, key); |
| cetv.SetStringPiece(kCIDS, signature); |
| |
| CrypterPair crypters; |
| CryptoUtils::DeriveKeys(out_params->initial_premaster_secret, |
| out_params->aead, out_params->client_nonce, |
| out_params->server_nonce, hkdf_input, |
| CryptoUtils::CLIENT, &crypters); |
| |
| const QuicData& cetv_plaintext = cetv.GetSerialized(); |
| scoped_ptr<QuicData> cetv_ciphertext(crypters.encrypter->EncryptPacket( |
| 0 /* sequence number */, |
| StringPiece() /* associated data */, |
| cetv_plaintext.AsStringPiece())); |
| |
| out->SetStringPiece(kCETV, cetv_ciphertext->AsStringPiece()); |
| out->MarkDirty(); |
| |
| out->set_minimum_size(orig_min_size); |
| } |
| |
| out_params->hkdf_input_suffix.clear(); |
| out_params->hkdf_input_suffix.append(reinterpret_cast<char*>(&guid), |
| sizeof(guid)); |
| const QuicData& client_hello_serialized = out->GetSerialized(); |
| out_params->hkdf_input_suffix.append(client_hello_serialized.data(), |
| client_hello_serialized.length()); |
| out_params->hkdf_input_suffix.append(cached->server_config()); |
| |
| string hkdf_input; |
| const size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1; |
| hkdf_input.reserve(label_len + out_params->hkdf_input_suffix.size()); |
| hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len); |
| hkdf_input.append(out_params->hkdf_input_suffix); |
| |
| CryptoUtils::DeriveKeys(out_params->initial_premaster_secret, |
| out_params->aead, out_params->client_nonce, |
| out_params->server_nonce, hkdf_input, |
| CryptoUtils::CLIENT, &out_params->initial_crypters); |
| |
| return QUIC_NO_ERROR; |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::ProcessRejection( |
| CachedState* cached, |
| const CryptoHandshakeMessage& rej, |
| QuicWallTime now, |
| QuicCryptoNegotiatedParameters* out_params, |
| string* error_details) { |
| DCHECK(error_details != NULL); |
| |
| if (rej.tag() != kREJ) { |
| *error_details = "Message is not REJ"; |
| return QUIC_CRYPTO_INTERNAL_ERROR; |
| } |
| |
| StringPiece scfg; |
| if (!rej.GetStringPiece(kSCFG, &scfg)) { |
| *error_details = "Missing SCFG"; |
| return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } |
| |
| QuicErrorCode error = cached->SetServerConfig(scfg, now, error_details); |
| if (error != QUIC_NO_ERROR) { |
| return error; |
| } |
| |
| StringPiece token; |
| if (rej.GetStringPiece(kSourceAddressTokenTag, &token)) { |
| cached->set_source_address_token(token); |
| } |
| |
| StringPiece nonce; |
| if (rej.GetStringPiece(kServerNonceTag, &nonce)) { |
| out_params->server_nonce = nonce.as_string(); |
| } |
| |
| StringPiece proof, cert_bytes; |
| if (rej.GetStringPiece(kPROF, &proof) && |
| rej.GetStringPiece(kCertificateTag, &cert_bytes)) { |
| vector<string> certs; |
| if (!CertCompressor::DecompressChain(cert_bytes, out_params->cached_certs, |
| common_cert_sets, &certs)) { |
| *error_details = "Certificate data invalid"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| cached->SetProof(certs, proof); |
| } |
| |
| return QUIC_NO_ERROR; |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::ProcessServerHello( |
| const CryptoHandshakeMessage& server_hello, |
| QuicGuid guid, |
| QuicCryptoNegotiatedParameters* out_params, |
| string* error_details) { |
| DCHECK(error_details != NULL); |
| |
| if (server_hello.tag() != kSHLO) { |
| *error_details = "Bad tag"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; |
| } |
| |
| // TODO(agl): |
| // learn about updated SCFGs. |
| |
| StringPiece public_value; |
| if (!server_hello.GetStringPiece(kPUBS, &public_value)) { |
| *error_details = "server hello missing forward secure public value"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| if (!out_params->client_key_exchange->CalculateSharedKey( |
| public_value, &out_params->forward_secure_premaster_secret)) { |
| *error_details = "Key exchange failure"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| string hkdf_input; |
| const size_t label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1; |
| hkdf_input.reserve(label_len + out_params->hkdf_input_suffix.size()); |
| hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel, label_len); |
| hkdf_input.append(out_params->hkdf_input_suffix); |
| |
| CryptoUtils::DeriveKeys( |
| out_params->forward_secure_premaster_secret, out_params->aead, |
| out_params->client_nonce, out_params->server_nonce, hkdf_input, |
| CryptoUtils::CLIENT, &out_params->forward_secure_crypters); |
| |
| return QUIC_NO_ERROR; |
| } |
| |
| const ProofVerifier* QuicCryptoClientConfig::proof_verifier() const { |
| return proof_verifier_.get(); |
| } |
| |
| void QuicCryptoClientConfig::SetProofVerifier(ProofVerifier* verifier) { |
| proof_verifier_.reset(verifier); |
| } |
| |
| void QuicCryptoClientConfig::SetChannelIDSigner(ChannelIDSigner* signer) { |
| channel_id_signer_.reset(signer); |
| } |
| |
| } // namespace net |