| // 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/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "crypto/hkdf.h" |
| #include "crypto/secure_hash.h" |
| #include "net/base/net_util.h" |
| #include "net/quic/crypto/aes_128_gcm_decrypter.h" |
| #include "net/quic/crypto/aes_128_gcm_encrypter.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/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/quic_clock.h" |
| #include "net/quic/quic_protocol.h" |
| |
| using base::StringPiece; |
| using crypto::SecureHash; |
| using std::map; |
| using std::string; |
| using std::vector; |
| |
| namespace net { |
| |
| // kVersion contains the one (and, for the moment, only) version number that we |
| // implement. |
| static const uint16 kVersion = 0; |
| |
| // kLabel is constant that is used in key derivation to tie the resulting key |
| // to this protocol. |
| static const char kLabel[] = "QUIC key expansion"; |
| |
| using crypto::SecureHash; |
| |
| QuicServerConfigProtobuf::QuicServerConfigProtobuf() { |
| } |
| |
| QuicServerConfigProtobuf::~QuicServerConfigProtobuf() { |
| STLDeleteElements(&keys_); |
| } |
| |
| CryptoHandshakeMessage::CryptoHandshakeMessage() : tag_(0) {} |
| |
| CryptoHandshakeMessage::CryptoHandshakeMessage( |
| const CryptoHandshakeMessage& other) |
| : tag_(other.tag_), |
| tag_value_map_(other.tag_value_map_) { |
| // Don't copy serialized_. scoped_ptr doesn't have a copy constructor. |
| // The new object can reconstruct serialized_ lazily. |
| } |
| |
| 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(); |
| return *this; |
| } |
| |
| void CryptoHandshakeMessage::Clear() { |
| tag_ = 0; |
| tag_value_map_.clear(); |
| serialized_.reset(); |
| } |
| |
| const QuicData& CryptoHandshakeMessage::GetSerialized() const { |
| if (!serialized_.get()) { |
| serialized_.reset(CryptoFramer::ConstructHandshakeMessage(*this)); |
| } |
| return *serialized_.get(); |
| } |
| |
| void CryptoHandshakeMessage::Insert(CryptoTagValueMap::const_iterator begin, |
| CryptoTagValueMap::const_iterator end) { |
| tag_value_map_.insert(begin, end); |
| } |
| |
| void CryptoHandshakeMessage::SetTaglist(CryptoTag tag, ...) { |
| // Warning, if sizeof(CryptoTag) > sizeof(int) then this function will break |
| // because the terminating 0 will only be promoted to int. |
| COMPILE_ASSERT(sizeof(CryptoTag) <= sizeof(int), |
| crypto_tag_not_be_larger_than_int_or_varargs_will_break); |
| |
| vector<CryptoTag> tags; |
| va_list ap; |
| |
| va_start(ap, tag); |
| for (;;) { |
| CryptoTag list_item = va_arg(ap, CryptoTag); |
| 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(CryptoTag tag, |
| StringPiece value) { |
| tag_value_map_[tag] = value.as_string(); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetTaglist(CryptoTag tag, |
| const CryptoTag** out_tags, |
| size_t* out_len) const { |
| CryptoTagValueMap::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(CryptoTag) != 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 CryptoTag*>(it->second.data()); |
| *out_len = it->second.size() / sizeof(CryptoTag); |
| return ret; |
| } |
| |
| bool CryptoHandshakeMessage::GetStringPiece(CryptoTag tag, |
| StringPiece* out) const { |
| CryptoTagValueMap::const_iterator it = tag_value_map_.find(tag); |
| if (it == tag_value_map_.end()) { |
| return false; |
| } |
| *out = it->second; |
| return true; |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetNthValue16(CryptoTag 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() < 2) { |
| 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); |
| value.remove_prefix(2); |
| |
| 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); |
| } |
| } |
| |
| bool CryptoHandshakeMessage::GetString(CryptoTag tag, string* out) const { |
| CryptoTagValueMap::const_iterator it = tag_value_map_.find(tag); |
| if (it == tag_value_map_.end()) { |
| return false; |
| } |
| *out = it->second; |
| return true; |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetUint16(CryptoTag tag, |
| uint16* out) const { |
| return GetPOD(tag, out, sizeof(uint16)); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetUint32(CryptoTag tag, |
| uint32* out) const { |
| return GetPOD(tag, out, sizeof(uint32)); |
| } |
| |
| string CryptoHandshakeMessage::DebugString() const { |
| return DebugStringInternal(0); |
| } |
| |
| QuicErrorCode CryptoHandshakeMessage::GetPOD( |
| CryptoTag tag, void* out, size_t len) const { |
| CryptoTagValueMap::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; |
| } |
| |
| // TagToString is a utility function for pretty-printing handshake messages |
| // that converts a tag to a string. It will try to maintain the human friendly |
| // name if possible (i.e. kABCD -> "ABCD"), or will just treat it as a number |
| // if not. |
| static string TagToString(CryptoTag tag) { |
| char chars[4]; |
| bool ascii = true; |
| const CryptoTag orig_tag = tag; |
| |
| for (size_t i = 0; i < sizeof(chars); i++) { |
| chars[i] = tag; |
| if (chars[i] == 0 && i == 3) { |
| chars[i] = ' '; |
| } |
| if (!isprint(static_cast<unsigned char>(chars[i]))) { |
| ascii = false; |
| break; |
| } |
| tag >>= 8; |
| } |
| |
| if (ascii) { |
| return string(chars, sizeof(chars)); |
| } |
| |
| return base::UintToString(orig_tag); |
| } |
| |
| string CryptoHandshakeMessage::DebugStringInternal(size_t indent) const { |
| string ret = string(2 * indent, ' ') + TagToString(tag_) + "<\n"; |
| ++indent; |
| for (CryptoTagValueMap::const_iterator it = tag_value_map_.begin(); |
| it != tag_value_map_.end(); ++it) { |
| ret += string(2 * indent, ' ') + 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: |
| // tag lists |
| if (it->second.size() % sizeof(CryptoTag) == 0) { |
| for (size_t j = 0; j < it->second.size(); j += sizeof(CryptoTag)) { |
| CryptoTag tag; |
| memcpy(&tag, it->second.data() + j, sizeof(tag)); |
| if (j > 0) { |
| ret += ","; |
| } |
| ret += 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; |
| } |
| |
| 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; |
| } |
| |
| SourceAddressToken::SourceAddressToken() { |
| } |
| |
| SourceAddressToken::~SourceAddressToken() { |
| } |
| |
| string SourceAddressToken::SerializeAsString() const { |
| return ip_ + " " + base::Int64ToString(timestamp_); |
| } |
| |
| bool SourceAddressToken::ParseFromArray(unsigned char* plaintext, |
| size_t plaintext_length) { |
| string data(reinterpret_cast<const char*>(plaintext), plaintext_length); |
| std::vector<std::string> results; |
| base::SplitString(data, ' ', &results); |
| if (results.size() < 2) { |
| return false; |
| } |
| |
| int64 timestamp; |
| if (!base::StringToInt64(results[1], ×tamp)) { |
| return false; |
| } |
| |
| ip_ = results[0]; |
| timestamp_ = timestamp; |
| return true; |
| } |
| |
| QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters() |
| : version(0), |
| key_exchange(0), |
| aead(0) { |
| } |
| |
| QuicCryptoNegotiatedParameters::~QuicCryptoNegotiatedParameters() { |
| } |
| |
| |
| QuicCryptoConfig::QuicCryptoConfig() |
| : version(0) { |
| } |
| |
| QuicCryptoConfig::~QuicCryptoConfig() { |
| } |
| |
| |
| QuicCryptoClientConfig::QuicCryptoClientConfig() { |
| } |
| |
| QuicCryptoClientConfig::~QuicCryptoClientConfig() { |
| STLDeleteValues(&cached_states_); |
| } |
| |
| QuicCryptoClientConfig::CachedState::CachedState() { |
| } |
| |
| QuicCryptoClientConfig::CachedState::~CachedState() { |
| } |
| |
| bool QuicCryptoClientConfig::CachedState::is_complete() const { |
| return !server_config_.empty(); |
| } |
| |
| 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(); |
| } |
| |
| bool QuicCryptoClientConfig::CachedState::SetServerConfig( |
| StringPiece scfg) { |
| scfg_.reset(CryptoFramer::ParseMessage(scfg)); |
| if (!scfg_.get()) { |
| return false; |
| } |
| server_config_ = scfg.as_string(); |
| return true; |
| } |
| |
| const string& QuicCryptoClientConfig::CachedState::server_config() |
| const { |
| return server_config_; |
| } |
| |
| const string& QuicCryptoClientConfig::CachedState::source_address_token() |
| const { |
| return source_address_token_; |
| } |
| |
| void QuicCryptoClientConfig::CachedState::set_source_address_token( |
| StringPiece token) { |
| source_address_token_ = token.as_string(); |
| } |
| |
| void QuicCryptoClientConfig::SetDefaults() { |
| // Version must be 0. |
| version = kVersion; |
| |
| // Key exchange methods. |
| kexs.resize(2); |
| kexs[0] = kC255; |
| kexs[1] = kP256; |
| |
| // Authenticated encryption algorithms. |
| aead.resize(1); |
| aead[0] = kAESG; |
| } |
| |
| const QuicCryptoClientConfig::CachedState* QuicCryptoClientConfig::Lookup( |
| const string& server_hostname) const { |
| map<string, CachedState*>::const_iterator it = |
| cached_states_.find(server_hostname); |
| if (it == cached_states_.end()) { |
| return NULL; |
| } |
| return it->second; |
| } |
| |
| void QuicCryptoClientConfig::FillInchoateClientHello( |
| const string& server_hostname, |
| const CachedState* cached, |
| CryptoHandshakeMessage* out) const { |
| out->set_tag(kCHLO); |
| |
| // Server name indication. |
| // If server_hostname is not an IP address literal, it is a DNS hostname. |
| IPAddressNumber ip; |
| if (!server_hostname.empty() && |
| !ParseIPLiteralToNumber(server_hostname, &ip)) { |
| out->SetStringPiece(kSNI, server_hostname); |
| } |
| out->SetValue(kVERS, version); |
| |
| if (cached && !cached->source_address_token().empty()) { |
| out->SetStringPiece(kSRCT, cached->source_address_token()); |
| } |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::FillClientHello( |
| const string& server_hostname, |
| QuicGuid guid, |
| const CachedState* cached, |
| const QuicClock* clock, |
| QuicRandom* rand, |
| QuicCryptoNegotiatedParameters* out_params, |
| CryptoHandshakeMessage* out, |
| string* error_details) const { |
| FillInchoateClientHello(server_hostname, cached, out); |
| |
| const CryptoHandshakeMessage* scfg = cached->GetServerConfig(); |
| if (!scfg) { |
| // This should never happen as our caller should have checked |
| // cached->is_complete() before calling this function. |
| if (error_details) { |
| *error_details = "Handshake not ready"; |
| } |
| return QUIC_CRYPTO_INTERNAL_ERROR; |
| } |
| |
| StringPiece scid; |
| if (!scfg->GetStringPiece(kSCID, &scid)) { |
| if (error_details) { |
| *error_details = "SCFG missing SCID"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| out->SetStringPiece(kSCID, scid); |
| |
| // Calculate the mutual algorithms that the connection is going to use. |
| if (scfg->GetUint16(kVERS, &out_params->version) != QUIC_NO_ERROR || |
| out_params->version != kVersion) { |
| if (error_details) { |
| *error_details = "Bad version"; |
| } |
| return QUIC_VERSION_NOT_SUPPORTED; |
| } |
| |
| const CryptoTag* their_aeads; |
| const CryptoTag* 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) { |
| if (error_details) { |
| *error_details = "Missing AEAD or KEXS"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| size_t key_exchange_index; |
| if (!CryptoUtils::FindMutualTag(aead, |
| their_aeads, num_their_aeads, |
| CryptoUtils::PEER_PRIORITY, |
| &out_params->aead, |
| NULL) || |
| !CryptoUtils::FindMutualTag(kexs, |
| their_key_exchanges, num_their_key_exchanges, |
| CryptoUtils::PEER_PRIORITY, |
| &out_params->key_exchange, |
| &key_exchange_index)) { |
| if (error_details) { |
| *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->GetNthValue16(kPUBS, key_exchange_index, &public_value) != |
| QUIC_NO_ERROR) { |
| if (error_details) { |
| *error_details = "Missing public value"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| StringPiece orbit; |
| if (!scfg->GetStringPiece(kORBT, &orbit) || |
| orbit.size() != kOrbitSize) { |
| if (error_details) { |
| *error_details = "SCFG missing OBIT"; |
| } |
| return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } |
| |
| string nonce; |
| CryptoUtils::GenerateNonce(clock->NowAsDeltaSinceUnixEpoch(), rand, orbit, |
| &nonce); |
| out->SetStringPiece(kNONC, nonce); |
| |
| scoped_ptr<KeyExchange> key_exchange; |
| switch (out_params->key_exchange) { |
| case kC255: |
| key_exchange.reset(Curve25519KeyExchange::New( |
| Curve25519KeyExchange::NewPrivateKey(rand))); |
| break; |
| case kP256: |
| key_exchange.reset(P256KeyExchange::New( |
| P256KeyExchange::NewPrivateKey())); |
| break; |
| default: |
| DCHECK(false); |
| if (error_details) { |
| *error_details = "Configured to support an unknown key exchange"; |
| } |
| return QUIC_CRYPTO_INTERNAL_ERROR; |
| } |
| |
| if (!key_exchange->CalculateSharedKey(public_value, |
| &out_params->premaster_secret)) { |
| if (error_details) { |
| *error_details = "Key exchange failure"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| out->SetStringPiece(kPUBS, key_exchange->public_value()); |
| |
| string hkdf_input(kLabel, arraysize(kLabel)); |
| hkdf_input.append(reinterpret_cast<char*>(&guid), sizeof(guid)); |
| |
| const QuicData& client_hello_serialized = out->GetSerialized(); |
| hkdf_input.append(client_hello_serialized.data(), |
| client_hello_serialized.length()); |
| hkdf_input.append(cached->server_config()); |
| |
| CryptoUtils::DeriveKeys(out_params, nonce, hkdf_input, CryptoUtils::CLIENT); |
| |
| return QUIC_NO_ERROR; |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::ProcessRejection( |
| const string& server_hostname, |
| const CryptoHandshakeMessage& rej, |
| QuicCryptoNegotiatedParameters* out_params, |
| string* error_details) { |
| CachedState* cached; |
| |
| map<string, CachedState*>::const_iterator it = |
| cached_states_.find(server_hostname); |
| if (it == cached_states_.end()) { |
| cached = new CachedState; |
| cached_states_[server_hostname] = cached; |
| } else { |
| cached = it->second; |
| } |
| |
| StringPiece scfg; |
| if (!rej.GetStringPiece(kSCFG, &scfg)) { |
| if (error_details) { |
| *error_details = "Missing SCFG"; |
| } |
| return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; |
| } |
| |
| if (!cached->SetServerConfig(scfg)) { |
| if (error_details) { |
| *error_details = "Invalid SCFG"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| StringPiece token; |
| if (rej.GetStringPiece(kSRCT, &token)) { |
| cached->set_source_address_token(token); |
| } |
| |
| StringPiece nonce; |
| if (rej.GetStringPiece(kNONC, &nonce) && |
| nonce.size() == kNonceSize) { |
| out_params->server_nonce = nonce.as_string(); |
| } |
| |
| return QUIC_NO_ERROR; |
| } |
| |
| QuicErrorCode QuicCryptoClientConfig::ProcessServerHello( |
| const CryptoHandshakeMessage& server_hello, |
| const string& nonce, |
| QuicCryptoNegotiatedParameters* out_params, |
| string* error_details) { |
| if (server_hello.tag() != kSHLO) { |
| *error_details = "Bad tag"; |
| return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; |
| } |
| |
| // TODO(agl): |
| // learn about updated SCFGs. |
| // read ephemeral public value for forward-secret keys. |
| |
| return QUIC_NO_ERROR; |
| } |
| |
| // static |
| const char QuicCryptoServerConfig::TESTING[] = "secret string for testing"; |
| |
| QuicCryptoServerConfig::QuicCryptoServerConfig( |
| StringPiece source_address_token_secret) |
| // AES-GCM is used to encrypt and authenticate source address tokens. The |
| // full, 96-bit nonce is used but we must ensure that an attacker cannot |
| // obtain two source address tokens with the same nonce. This occurs with |
| // probability 0.5 after 2**48 values. We assume that obtaining 2**48 |
| // source address tokens is not possible: at a rate of 10M packets per |
| // second, it would still take the attacker a year to obtain the needed |
| // number of packets. |
| // |
| // TODO(agl): switch to an encrypter with a larger nonce space (i.e. |
| // Salsa20+Poly1305). |
| : strike_register_lock_(), |
| source_address_token_encrypter_(new Aes128GcmEncrypter), |
| source_address_token_decrypter_(new Aes128GcmDecrypter) { |
| crypto::HKDF hkdf(source_address_token_secret, StringPiece() /* no salt */, |
| "QUIC source address token key", |
| source_address_token_encrypter_->GetKeySize(), |
| 0 /* no fixed IV needed */); |
| source_address_token_encrypter_->SetKey(hkdf.server_write_key()); |
| source_address_token_decrypter_->SetKey(hkdf.server_write_key()); |
| } |
| |
| QuicCryptoServerConfig::~QuicCryptoServerConfig() { |
| STLDeleteValues(&configs_); |
| } |
| |
| // static |
| QuicServerConfigProtobuf* QuicCryptoServerConfig::DefaultConfig( |
| QuicRandom* rand, |
| const QuicClock* clock, |
| const CryptoHandshakeMessage& extra_tags) { |
| CryptoHandshakeMessage msg; |
| |
| const string curve25519_private_key = |
| Curve25519KeyExchange::NewPrivateKey(rand); |
| scoped_ptr<Curve25519KeyExchange> curve25519( |
| Curve25519KeyExchange::New(curve25519_private_key)); |
| StringPiece curve25519_public_value = curve25519->public_value(); |
| |
| const string p256_private_key = |
| P256KeyExchange::NewPrivateKey(); |
| scoped_ptr<P256KeyExchange> p256( |
| P256KeyExchange::New(p256_private_key)); |
| StringPiece p256_public_value = p256->public_value(); |
| |
| string encoded_public_values; |
| // First two bytes encode the length of the public value. |
| encoded_public_values.push_back(curve25519_public_value.size()); |
| encoded_public_values.push_back(curve25519_public_value.size() >> 8); |
| encoded_public_values.append(curve25519_public_value.data(), |
| curve25519_public_value.size()); |
| encoded_public_values.push_back(p256_public_value.size()); |
| encoded_public_values.push_back(p256_public_value.size() >> 8); |
| encoded_public_values.append(p256_public_value.data(), |
| p256_public_value.size()); |
| |
| msg.set_tag(kSCFG); |
| msg.SetTaglist(kKEXS, kC255, kP256, 0); |
| msg.SetTaglist(kAEAD, kAESG, 0); |
| msg.SetValue(kVERS, static_cast<uint16>(0)); |
| msg.SetStringPiece(kPUBS, encoded_public_values); |
| msg.Insert(extra_tags.tag_value_map().begin(), |
| extra_tags.tag_value_map().end()); |
| |
| char scid_bytes[16]; |
| rand->RandBytes(scid_bytes, sizeof(scid_bytes)); |
| msg.SetStringPiece(kSCID, StringPiece(scid_bytes, sizeof(scid_bytes))); |
| |
| char orbit_bytes[kOrbitSize]; |
| rand->RandBytes(orbit_bytes, sizeof(orbit_bytes)); |
| msg.SetStringPiece(kORBT, StringPiece(orbit_bytes, sizeof(orbit_bytes))); |
| |
| scoped_ptr<QuicData> serialized( |
| CryptoFramer::ConstructHandshakeMessage(msg)); |
| |
| scoped_ptr<QuicServerConfigProtobuf> 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); |
| 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) { |
| scoped_ptr<CryptoHandshakeMessage> msg( |
| CryptoFramer::ParseMessage(protobuf->config())); |
| |
| if (!msg.get()) { |
| LOG(WARNING) << "Failed to parse server config message"; |
| return NULL; |
| } |
| if (msg->tag() != kSCFG) { |
| LOG(WARNING) << "Server config message has tag " |
| << msg->tag() << " expected " |
| << kSCFG; |
| return NULL; |
| } |
| |
| scoped_ptr<Config> config(new Config); |
| config->serialized = protobuf->config(); |
| |
| StringPiece scid; |
| if (!msg->GetStringPiece(kSCID, &scid)) { |
| LOG(WARNING) << "Server config message is missing SCID"; |
| return NULL; |
| } |
| config->id = scid.as_string(); |
| |
| const CryptoTag* 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 NULL; |
| } |
| config->aead = vector<CryptoTag>(aead_tags, aead_tags + aead_len); |
| |
| const CryptoTag* 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 NULL; |
| } |
| |
| StringPiece orbit; |
| if (!msg->GetStringPiece(kORBT, &orbit)) { |
| LOG(WARNING) << "Server config message is missing OBIT"; |
| return NULL; |
| } |
| |
| if (orbit.size() != kOrbitSize) { |
| LOG(WARNING) << "Orbit value in server config is the wrong length." |
| " Got " << orbit.size() << " want " << kOrbitSize; |
| return NULL; |
| } |
| COMPILE_ASSERT(sizeof(config->orbit) == kOrbitSize, orbit_incorrect_size); |
| memcpy(config->orbit, orbit.data(), sizeof(config->orbit)); |
| |
| if (kexs_len != protobuf->key_size()) { |
| LOG(WARNING) << "Server config has " |
| << kexs_len |
| << " key exchange methods configured, but " |
| << protobuf->key_size() |
| << " private keys"; |
| return NULL; |
| } |
| |
| for (size_t i = 0; i < kexs_len; i++) { |
| const CryptoTag 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 NULL; |
| } |
| |
| scoped_ptr<KeyExchange> 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 NULL; |
| } |
| break; |
| case kP256: |
| ka.reset(P256KeyExchange::New(private_key)); |
| if (!ka.get()) { |
| LOG(WARNING) << "Server config contained an invalid P-256" |
| " private key."; |
| return NULL; |
| } |
| break; |
| default: |
| LOG(WARNING) << "Server config message contains unknown key exchange " |
| "method: " |
| << tag; |
| return NULL; |
| } |
| |
| for (vector<KeyExchange*>::const_iterator i = config->key_exchanges.begin(); |
| i != config->key_exchanges.end(); ++i) { |
| if ((*i)->tag() == tag) { |
| LOG(WARNING) << "Duplicate key exchange in config: " << tag; |
| return NULL; |
| } |
| } |
| |
| config->key_exchanges.push_back(ka.release()); |
| } |
| |
| if (msg->GetUint16(kVERS, &config->version) != QUIC_NO_ERROR) { |
| LOG(WARNING) << "Server config message is missing version"; |
| return NULL; |
| } |
| |
| if (config->version != kVersion) { |
| LOG(WARNING) << "Server config specifies an unsupported version"; |
| return NULL; |
| } |
| |
| scoped_ptr<SecureHash> sha256(SecureHash::Create(SecureHash::SHA256)); |
| sha256->Update(protobuf->config().data(), protobuf->config().size()); |
| char id_bytes[16]; |
| sha256->Finish(id_bytes, sizeof(id_bytes)); |
| const string id(id_bytes, sizeof(id_bytes)); |
| |
| configs_[id] = config.release(); |
| active_config_ = id; |
| |
| return msg.release(); |
| } |
| |
| CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig( |
| QuicRandom* rand, |
| const QuicClock* clock, |
| const CryptoHandshakeMessage& extra_tags) { |
| scoped_ptr<QuicServerConfigProtobuf> config(DefaultConfig( |
| rand, clock, extra_tags)); |
| return AddConfig(config.get()); |
| } |
| |
| QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( |
| const CryptoHandshakeMessage& client_hello, |
| QuicGuid guid, |
| const IPEndPoint& client_ip, |
| QuicTime::Delta now_since_unix_epoch, |
| QuicRandom* rand, |
| QuicCryptoNegotiatedParameters *params, |
| CryptoHandshakeMessage* out, |
| string* error_details) const { |
| CHECK(!configs_.empty()); |
| // FIXME(agl): we should use the client's SCID, not just the active config. |
| map<ServerConfigID, Config*>::const_iterator it = |
| configs_.find(active_config_); |
| if (it == configs_.end()) { |
| *error_details = "No valid server config loaded"; |
| return QUIC_CRYPTO_INTERNAL_ERROR; |
| } |
| const Config* const config(it->second); |
| |
| bool valid_source_address_token = false; |
| StringPiece srct; |
| if (client_hello.GetStringPiece(kSRCT, &srct) && |
| ValidateSourceAddressToken(srct, client_ip, now_since_unix_epoch)) { |
| valid_source_address_token = true; |
| } |
| |
| const string fresh_source_address_token = |
| NewSourceAddressToken(client_ip, rand, now_since_unix_epoch); |
| |
| // If we previously sent a REJ to this client then we may have stored a |
| // server nonce in |params|. In which case, we know that the connection |
| // is unique because the server nonce will be mixed into the key generation. |
| bool unique_by_server_nonce = !params->server_nonce.empty(); |
| // If we can't ensure uniqueness by a server nonce, then we will try and use |
| // the strike register. |
| bool unique_by_strike_register = false; |
| |
| StringPiece client_nonce; |
| bool client_nonce_well_formed = false; |
| if (client_hello.GetStringPiece(kNONC, &client_nonce) && |
| client_nonce.size() == kNonceSize) { |
| client_nonce_well_formed = true; |
| if (!unique_by_server_nonce) { |
| base::AutoLock auto_lock(strike_register_lock_); |
| |
| if (strike_register_.get() == NULL) { |
| strike_register_.reset(new StrikeRegister( |
| // TODO(agl): these magic numbers should come from config. |
| 1024 /* max entries */, |
| static_cast<uint32>(now_since_unix_epoch.ToSeconds()), |
| 600 /* window secs */, config->orbit)); |
| } |
| unique_by_strike_register = strike_register_->Insert( |
| reinterpret_cast<const uint8*>(client_nonce.data()), |
| static_cast<uint32>(now_since_unix_epoch.ToSeconds())); |
| } |
| } |
| |
| StringPiece scid; |
| if (!client_hello.GetStringPiece(kSCID, &scid) || |
| scid.as_string() != config->id || |
| !valid_source_address_token || |
| !client_nonce_well_formed || |
| (!unique_by_strike_register && |
| !unique_by_server_nonce)) { |
| // If the client didn't provide a server config ID, or gave the wrong one, |
| // then the handshake cannot possibly complete. We reject the handshake and |
| // give the client enough information to do better next time. |
| out->Clear(); |
| out->set_tag(kREJ); |
| out->SetStringPiece(kSCFG, config->serialized); |
| out->SetStringPiece(kSRCT, fresh_source_address_token); |
| if (params->server_nonce.empty()) { |
| CryptoUtils::GenerateNonce( |
| now_since_unix_epoch, rand, |
| StringPiece(reinterpret_cast<const char*>(config->orbit), |
| sizeof(config->orbit)), |
| ¶ms->server_nonce); |
| } |
| out->SetStringPiece(kNONC, params->server_nonce); |
| return QUIC_NO_ERROR; |
| } |
| |
| const CryptoTag* their_aeads; |
| const CryptoTag* 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) { |
| if (error_details) { |
| *error_details = "Missing or invalid AEAD or KEXS"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| size_t key_exchange_index; |
| if (!CryptoUtils::FindMutualTag(config->aead, |
| their_aeads, num_their_aeads, |
| CryptoUtils::LOCAL_PRIORITY, |
| ¶ms->aead, |
| NULL) || |
| !CryptoUtils::FindMutualTag(config->kexs, |
| their_key_exchanges, num_their_key_exchanges, |
| CryptoUtils::LOCAL_PRIORITY, |
| ¶ms->key_exchange, |
| &key_exchange_index)) { |
| if (error_details) { |
| *error_details = "Unsupported AEAD or KEXS"; |
| } |
| return QUIC_CRYPTO_NO_SUPPORT; |
| } |
| |
| StringPiece public_value; |
| if (!client_hello.GetStringPiece(kPUBS, &public_value)) { |
| if (error_details) { |
| *error_details = "Missing public value"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| if (!config->key_exchanges[key_exchange_index]->CalculateSharedKey( |
| public_value, ¶ms->premaster_secret)) { |
| if (error_details) { |
| *error_details = "Invalid public value"; |
| } |
| return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| } |
| |
| params->server_config_id = scid.as_string(); |
| |
| string hkdf_input(kLabel, arraysize(kLabel)); |
| hkdf_input.append(reinterpret_cast<char*>(&guid), sizeof(guid)); |
| |
| const QuicData& client_hello_serialized = client_hello.GetSerialized(); |
| hkdf_input.append(client_hello_serialized.data(), |
| client_hello_serialized.length()); |
| hkdf_input.append(config->serialized); |
| |
| CryptoUtils::DeriveKeys(params, client_nonce, hkdf_input, |
| CryptoUtils::SERVER); |
| |
| out->set_tag(kSHLO); |
| out->SetStringPiece(kSRCT, fresh_source_address_token); |
| return QUIC_NO_ERROR; |
| } |
| |
| string QuicCryptoServerConfig::NewSourceAddressToken( |
| const IPEndPoint& ip, |
| QuicRandom* rand, |
| QuicTime::Delta now_since_epoch) const { |
| SourceAddressToken source_address_token; |
| source_address_token.set_ip(ip.ToString()); |
| source_address_token.set_timestamp(now_since_epoch.ToSeconds()); |
| |
| string plaintext = source_address_token.SerializeAsString(); |
| char nonce[12]; |
| DCHECK_EQ(sizeof(nonce), |
| source_address_token_encrypter_->GetNoncePrefixSize() + |
| sizeof(QuicPacketSequenceNumber)); |
| rand->RandBytes(nonce, sizeof(nonce)); |
| |
| size_t ciphertext_size = |
| source_address_token_encrypter_->GetCiphertextSize(plaintext.size()); |
| string result; |
| result.resize(sizeof(nonce) + ciphertext_size); |
| memcpy(&result[0], &nonce, sizeof(nonce)); |
| |
| if (!source_address_token_encrypter_->Encrypt( |
| StringPiece(nonce, sizeof(nonce)), StringPiece(), plaintext, |
| reinterpret_cast<unsigned char*>(&result[sizeof(nonce)]))) { |
| DCHECK(false); |
| return string(); |
| } |
| |
| return result; |
| } |
| |
| bool QuicCryptoServerConfig::ValidateSourceAddressToken( |
| StringPiece token, |
| const IPEndPoint& ip, |
| QuicTime::Delta now_since_epoch) const { |
| char nonce[12]; |
| DCHECK_EQ(sizeof(nonce), |
| source_address_token_encrypter_->GetNoncePrefixSize() + |
| sizeof(QuicPacketSequenceNumber)); |
| |
| if (token.size() <= sizeof(nonce)) { |
| return false; |
| } |
| memcpy(&nonce, token.data(), sizeof(nonce)); |
| token.remove_prefix(sizeof(nonce)); |
| |
| unsigned char plaintext_stack[128]; |
| scoped_ptr<unsigned char[]> plaintext_heap; |
| unsigned char* plaintext; |
| if (token.size() <= sizeof(plaintext_stack)) { |
| plaintext = plaintext_stack; |
| } else { |
| plaintext_heap.reset(new unsigned char[token.size()]); |
| plaintext = plaintext_heap.get(); |
| } |
| size_t plaintext_length; |
| |
| if (!source_address_token_decrypter_->Decrypt( |
| StringPiece(nonce, sizeof(nonce)), StringPiece(), token, |
| plaintext, &plaintext_length)) { |
| return false; |
| } |
| |
| SourceAddressToken source_address_token; |
| if (!source_address_token.ParseFromArray(plaintext, plaintext_length)) { |
| return false; |
| } |
| |
| if (source_address_token.ip() != ip.ToString()) { |
| // It's for a different IP address. |
| return false; |
| } |
| |
| const QuicTime::Delta delta(now_since_epoch.Subtract( |
| QuicTime::Delta::FromSeconds(source_address_token.timestamp()))); |
| const int64 delta_secs = delta.ToSeconds(); |
| |
| // TODO(agl): consider whether and how these magic values should be moved to |
| // a config. |
| if (delta_secs < -3600) { |
| // We only allow timestamps to be from an hour in the future. |
| return false; |
| } |
| |
| if (delta_secs > 86400) { |
| // We allow one day into the past. |
| return false; |
| } |
| |
| return true; |
| } |
| |
| QuicCryptoServerConfig::Config::Config() { |
| } |
| |
| QuicCryptoServerConfig::Config::~Config() { |
| STLDeleteElements(&key_exchanges); |
| } |
| |
| } // namespace net |