Relocate QUICHE files into quiche/ directory within the quiche repo, and change the relative include paths accordingly. PiperOrigin-RevId: 440164720 Change-Id: I64d8a975d08888a3a86f6c51908e63d5cd45fa35
diff --git a/quiche/quic/core/crypto/aead_base_decrypter.cc b/quiche/quic/core/crypto/aead_base_decrypter.cc new file mode 100644 index 0000000..fecf67e --- /dev/null +++ b/quiche/quic/core/crypto/aead_base_decrypter.cc
@@ -0,0 +1,214 @@ +// 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 "quiche/quic/core/crypto/aead_base_decrypter.h" + +#include <cstdint> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/crypto.h" +#include "third_party/boringssl/src/include/openssl/err.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +namespace { + +// Clear OpenSSL error stack. +void ClearOpenSslErrors() { + while (ERR_get_error()) { + } +} + +// In debug builds only, log OpenSSL error stack. Then clear OpenSSL error +// stack. +void DLogOpenSslErrors() { +#ifdef NDEBUG + ClearOpenSslErrors(); +#else + while (uint32_t error = ERR_get_error()) { + char buf[120]; + ERR_error_string_n(error, buf, ABSL_ARRAYSIZE(buf)); + QUIC_DLOG(ERROR) << "OpenSSL error: " << buf; + } +#endif +} + +const EVP_AEAD* InitAndCall(const EVP_AEAD* (*aead_getter)()) { + // Ensure BoringSSL is initialized before calling |aead_getter|. In Chromium, + // the static initializer is disabled. + CRYPTO_library_init(); + return aead_getter(); +} + +} // namespace + +AeadBaseDecrypter::AeadBaseDecrypter(const EVP_AEAD* (*aead_getter)(), + size_t key_size, + size_t auth_tag_size, + size_t nonce_size, + bool use_ietf_nonce_construction) + : aead_alg_(InitAndCall(aead_getter)), + key_size_(key_size), + auth_tag_size_(auth_tag_size), + nonce_size_(nonce_size), + use_ietf_nonce_construction_(use_ietf_nonce_construction), + have_preliminary_key_(false) { + QUICHE_DCHECK_GT(256u, key_size); + QUICHE_DCHECK_GT(256u, auth_tag_size); + QUICHE_DCHECK_GT(256u, nonce_size); + QUICHE_DCHECK_LE(key_size_, sizeof(key_)); + QUICHE_DCHECK_LE(nonce_size_, sizeof(iv_)); +} + +AeadBaseDecrypter::~AeadBaseDecrypter() {} + +bool AeadBaseDecrypter::SetKey(absl::string_view key) { + QUICHE_DCHECK_EQ(key.size(), key_size_); + if (key.size() != key_size_) { + return false; + } + memcpy(key_, key.data(), key.size()); + + EVP_AEAD_CTX_cleanup(ctx_.get()); + if (!EVP_AEAD_CTX_init(ctx_.get(), aead_alg_, key_, key_size_, auth_tag_size_, + nullptr)) { + DLogOpenSslErrors(); + return false; + } + + return true; +} + +bool AeadBaseDecrypter::SetNoncePrefix(absl::string_view nonce_prefix) { + if (use_ietf_nonce_construction_) { + QUIC_BUG(quic_bug_10709_1) + << "Attempted to set nonce prefix on IETF QUIC crypter"; + return false; + } + QUICHE_DCHECK_EQ(nonce_prefix.size(), nonce_size_ - sizeof(QuicPacketNumber)); + if (nonce_prefix.size() != nonce_size_ - sizeof(QuicPacketNumber)) { + return false; + } + memcpy(iv_, nonce_prefix.data(), nonce_prefix.size()); + return true; +} + +bool AeadBaseDecrypter::SetIV(absl::string_view iv) { + if (!use_ietf_nonce_construction_) { + QUIC_BUG(quic_bug_10709_2) << "Attempted to set IV on Google QUIC crypter"; + return false; + } + QUICHE_DCHECK_EQ(iv.size(), nonce_size_); + if (iv.size() != nonce_size_) { + return false; + } + memcpy(iv_, iv.data(), iv.size()); + return true; +} + +bool AeadBaseDecrypter::SetPreliminaryKey(absl::string_view key) { + QUICHE_DCHECK(!have_preliminary_key_); + SetKey(key); + have_preliminary_key_ = true; + + return true; +} + +bool AeadBaseDecrypter::SetDiversificationNonce( + const DiversificationNonce& nonce) { + if (!have_preliminary_key_) { + return true; + } + + std::string key, nonce_prefix; + size_t prefix_size = nonce_size_; + if (!use_ietf_nonce_construction_) { + prefix_size -= sizeof(QuicPacketNumber); + } + DiversifyPreliminaryKey( + absl::string_view(reinterpret_cast<const char*>(key_), key_size_), + absl::string_view(reinterpret_cast<const char*>(iv_), prefix_size), nonce, + key_size_, prefix_size, &key, &nonce_prefix); + + if (!SetKey(key) || + (!use_ietf_nonce_construction_ && !SetNoncePrefix(nonce_prefix)) || + (use_ietf_nonce_construction_ && !SetIV(nonce_prefix))) { + QUICHE_DCHECK(false); + return false; + } + + have_preliminary_key_ = false; + return true; +} + +bool AeadBaseDecrypter::DecryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view ciphertext, + char* output, + size_t* output_length, + size_t max_output_length) { + if (ciphertext.length() < auth_tag_size_) { + return false; + } + + if (have_preliminary_key_) { + QUIC_BUG(quic_bug_10709_3) + << "Unable to decrypt while key diversification is pending"; + return false; + } + + uint8_t nonce[kMaxNonceSize]; + memcpy(nonce, iv_, nonce_size_); + size_t prefix_len = nonce_size_ - sizeof(packet_number); + if (use_ietf_nonce_construction_) { + for (size_t i = 0; i < sizeof(packet_number); ++i) { + nonce[prefix_len + i] ^= + (packet_number >> ((sizeof(packet_number) - i - 1) * 8)) & 0xff; + } + } else { + memcpy(nonce + prefix_len, &packet_number, sizeof(packet_number)); + } + if (!EVP_AEAD_CTX_open( + ctx_.get(), reinterpret_cast<uint8_t*>(output), output_length, + max_output_length, reinterpret_cast<const uint8_t*>(nonce), + nonce_size_, reinterpret_cast<const uint8_t*>(ciphertext.data()), + ciphertext.size(), + reinterpret_cast<const uint8_t*>(associated_data.data()), + associated_data.size())) { + // Because QuicFramer does trial decryption, decryption errors are expected + // when encryption level changes. So we don't log decryption errors. + ClearOpenSslErrors(); + return false; + } + return true; +} + +size_t AeadBaseDecrypter::GetKeySize() const { + return key_size_; +} + +size_t AeadBaseDecrypter::GetNoncePrefixSize() const { + return nonce_size_ - sizeof(QuicPacketNumber); +} + +size_t AeadBaseDecrypter::GetIVSize() const { + return nonce_size_; +} + +absl::string_view AeadBaseDecrypter::GetKey() const { + return absl::string_view(reinterpret_cast<const char*>(key_), key_size_); +} + +absl::string_view AeadBaseDecrypter::GetNoncePrefix() const { + return absl::string_view(reinterpret_cast<const char*>(iv_), + nonce_size_ - sizeof(QuicPacketNumber)); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aead_base_decrypter.h b/quiche/quic/core/crypto/aead_base_decrypter.h new file mode 100644 index 0000000..82a3e1c --- /dev/null +++ b/quiche/quic/core/crypto/aead_base_decrypter.h
@@ -0,0 +1,74 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AEAD_BASE_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AEAD_BASE_DECRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "quiche/quic/core/crypto/quic_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// AeadBaseDecrypter is the base class of AEAD QuicDecrypter subclasses. +class QUIC_EXPORT_PRIVATE AeadBaseDecrypter : public QuicDecrypter { + public: + // This takes the function pointer rather than the EVP_AEAD itself so + // subclasses do not need to call CRYPTO_library_init. + AeadBaseDecrypter(const EVP_AEAD* (*aead_getter)(), + size_t key_size, + size_t auth_tag_size, + size_t nonce_size, + bool use_ietf_nonce_construction); + AeadBaseDecrypter(const AeadBaseDecrypter&) = delete; + AeadBaseDecrypter& operator=(const AeadBaseDecrypter&) = delete; + ~AeadBaseDecrypter() override; + + // QuicDecrypter implementation + bool SetKey(absl::string_view key) override; + bool SetNoncePrefix(absl::string_view nonce_prefix) override; + bool SetIV(absl::string_view iv) override; + bool SetPreliminaryKey(absl::string_view key) override; + bool SetDiversificationNonce(const DiversificationNonce& nonce) override; + bool DecryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view ciphertext, + char* output, + size_t* output_length, + size_t max_output_length) override; + size_t GetKeySize() const override; + size_t GetNoncePrefixSize() const override; + size_t GetIVSize() const override; + absl::string_view GetKey() const override; + absl::string_view GetNoncePrefix() const override; + + protected: + // Make these constants available to the subclasses so that the subclasses + // can assert at compile time their key_size_ and nonce_size_ do not + // exceed the maximum. + static const size_t kMaxKeySize = 32; + static const size_t kMaxNonceSize = 12; + + private: + const EVP_AEAD* const aead_alg_; + const size_t key_size_; + const size_t auth_tag_size_; + const size_t nonce_size_; + const bool use_ietf_nonce_construction_; + bool have_preliminary_key_; + + // The key. + unsigned char key_[kMaxKeySize]; + // The IV used to construct the nonce. + unsigned char iv_[kMaxNonceSize]; + + bssl::ScopedEVP_AEAD_CTX ctx_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AEAD_BASE_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/aead_base_encrypter.cc b/quiche/quic/core/crypto/aead_base_encrypter.cc new file mode 100644 index 0000000..e93f270 --- /dev/null +++ b/quiche/quic/core/crypto/aead_base_encrypter.cc
@@ -0,0 +1,188 @@ +// 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 "quiche/quic/core/crypto/aead_base_encrypter.h" + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/crypto.h" +#include "third_party/boringssl/src/include/openssl/err.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +namespace { + +// In debug builds only, log OpenSSL error stack. Then clear OpenSSL error +// stack. +void DLogOpenSslErrors() { +#ifdef NDEBUG + while (ERR_get_error()) { + } +#else + while (unsigned long error = ERR_get_error()) { + char buf[120]; + ERR_error_string_n(error, buf, ABSL_ARRAYSIZE(buf)); + QUIC_DLOG(ERROR) << "OpenSSL error: " << buf; + } +#endif +} + +const EVP_AEAD* InitAndCall(const EVP_AEAD* (*aead_getter)()) { + // Ensure BoringSSL is initialized before calling |aead_getter|. In Chromium, + // the static initializer is disabled. + CRYPTO_library_init(); + return aead_getter(); +} + +} // namespace + +AeadBaseEncrypter::AeadBaseEncrypter(const EVP_AEAD* (*aead_getter)(), + size_t key_size, + size_t auth_tag_size, + size_t nonce_size, + bool use_ietf_nonce_construction) + : aead_alg_(InitAndCall(aead_getter)), + key_size_(key_size), + auth_tag_size_(auth_tag_size), + nonce_size_(nonce_size), + use_ietf_nonce_construction_(use_ietf_nonce_construction) { + QUICHE_DCHECK_LE(key_size_, sizeof(key_)); + QUICHE_DCHECK_LE(nonce_size_, sizeof(iv_)); + QUICHE_DCHECK_GE(kMaxNonceSize, nonce_size_); +} + +AeadBaseEncrypter::~AeadBaseEncrypter() {} + +bool AeadBaseEncrypter::SetKey(absl::string_view key) { + QUICHE_DCHECK_EQ(key.size(), key_size_); + if (key.size() != key_size_) { + return false; + } + memcpy(key_, key.data(), key.size()); + + EVP_AEAD_CTX_cleanup(ctx_.get()); + + if (!EVP_AEAD_CTX_init(ctx_.get(), aead_alg_, key_, key_size_, auth_tag_size_, + nullptr)) { + DLogOpenSslErrors(); + return false; + } + + return true; +} + +bool AeadBaseEncrypter::SetNoncePrefix(absl::string_view nonce_prefix) { + if (use_ietf_nonce_construction_) { + QUIC_BUG(quic_bug_10634_1) + << "Attempted to set nonce prefix on IETF QUIC crypter"; + return false; + } + QUICHE_DCHECK_EQ(nonce_prefix.size(), nonce_size_ - sizeof(QuicPacketNumber)); + if (nonce_prefix.size() != nonce_size_ - sizeof(QuicPacketNumber)) { + return false; + } + memcpy(iv_, nonce_prefix.data(), nonce_prefix.size()); + return true; +} + +bool AeadBaseEncrypter::SetIV(absl::string_view iv) { + if (!use_ietf_nonce_construction_) { + QUIC_BUG(quic_bug_10634_2) << "Attempted to set IV on Google QUIC crypter"; + return false; + } + QUICHE_DCHECK_EQ(iv.size(), nonce_size_); + if (iv.size() != nonce_size_) { + return false; + } + memcpy(iv_, iv.data(), iv.size()); + return true; +} + +bool AeadBaseEncrypter::Encrypt(absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext, + unsigned char* output) { + QUICHE_DCHECK_EQ(nonce.size(), nonce_size_); + + size_t ciphertext_len; + if (!EVP_AEAD_CTX_seal( + ctx_.get(), output, &ciphertext_len, + plaintext.size() + auth_tag_size_, + reinterpret_cast<const uint8_t*>(nonce.data()), nonce.size(), + reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size(), + reinterpret_cast<const uint8_t*>(associated_data.data()), + associated_data.size())) { + DLogOpenSslErrors(); + return false; + } + + return true; +} + +bool AeadBaseEncrypter::EncryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view plaintext, + char* output, + size_t* output_length, + size_t max_output_length) { + size_t ciphertext_size = GetCiphertextSize(plaintext.length()); + if (max_output_length < ciphertext_size) { + return false; + } + // TODO(ianswett): Introduce a check to ensure that we don't encrypt with the + // same packet number twice. + alignas(4) char nonce_buffer[kMaxNonceSize]; + memcpy(nonce_buffer, iv_, nonce_size_); + size_t prefix_len = nonce_size_ - sizeof(packet_number); + if (use_ietf_nonce_construction_) { + for (size_t i = 0; i < sizeof(packet_number); ++i) { + nonce_buffer[prefix_len + i] ^= + (packet_number >> ((sizeof(packet_number) - i - 1) * 8)) & 0xff; + } + } else { + memcpy(nonce_buffer + prefix_len, &packet_number, sizeof(packet_number)); + } + + if (!Encrypt(absl::string_view(nonce_buffer, nonce_size_), associated_data, + plaintext, reinterpret_cast<unsigned char*>(output))) { + return false; + } + *output_length = ciphertext_size; + return true; +} + +size_t AeadBaseEncrypter::GetKeySize() const { + return key_size_; +} + +size_t AeadBaseEncrypter::GetNoncePrefixSize() const { + return nonce_size_ - sizeof(QuicPacketNumber); +} + +size_t AeadBaseEncrypter::GetIVSize() const { + return nonce_size_; +} + +size_t AeadBaseEncrypter::GetMaxPlaintextSize(size_t ciphertext_size) const { + return ciphertext_size - std::min(ciphertext_size, auth_tag_size_); +} + +size_t AeadBaseEncrypter::GetCiphertextSize(size_t plaintext_size) const { + return plaintext_size + auth_tag_size_; +} + +absl::string_view AeadBaseEncrypter::GetKey() const { + return absl::string_view(reinterpret_cast<const char*>(key_), key_size_); +} + +absl::string_view AeadBaseEncrypter::GetNoncePrefix() const { + return absl::string_view(reinterpret_cast<const char*>(iv_), + GetNoncePrefixSize()); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aead_base_encrypter.h b/quiche/quic/core/crypto/aead_base_encrypter.h new file mode 100644 index 0000000..c170a87 --- /dev/null +++ b/quiche/quic/core/crypto/aead_base_encrypter.h
@@ -0,0 +1,80 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AEAD_BASE_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AEAD_BASE_ENCRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// AeadBaseEncrypter is the base class of AEAD QuicEncrypter subclasses. +class QUIC_EXPORT_PRIVATE AeadBaseEncrypter : public QuicEncrypter { + public: + // This takes the function pointer rather than the EVP_AEAD itself so + // subclasses do not need to call CRYPTO_library_init. + AeadBaseEncrypter(const EVP_AEAD* (*aead_getter)(), + size_t key_size, + size_t auth_tag_size, + size_t nonce_size, + bool use_ietf_nonce_construction); + AeadBaseEncrypter(const AeadBaseEncrypter&) = delete; + AeadBaseEncrypter& operator=(const AeadBaseEncrypter&) = delete; + ~AeadBaseEncrypter() override; + + // QuicEncrypter implementation + bool SetKey(absl::string_view key) override; + bool SetNoncePrefix(absl::string_view nonce_prefix) override; + bool SetIV(absl::string_view iv) override; + bool EncryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view plaintext, + char* output, + size_t* output_length, + size_t max_output_length) override; + size_t GetKeySize() const override; + size_t GetNoncePrefixSize() const override; + size_t GetIVSize() const override; + size_t GetMaxPlaintextSize(size_t ciphertext_size) const override; + size_t GetCiphertextSize(size_t plaintext_size) const override; + absl::string_view GetKey() const override; + absl::string_view GetNoncePrefix() const override; + + // Necessary so unit tests can explicitly specify a nonce, instead of an IV + // (or nonce prefix) and packet number. + bool Encrypt(absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext, + unsigned char* output); + + protected: + // Make these constants available to the subclasses so that the subclasses + // can assert at compile time their key_size_ and nonce_size_ do not + // exceed the maximum. + static const size_t kMaxKeySize = 32; + enum : size_t { kMaxNonceSize = 12 }; + + private: + const EVP_AEAD* const aead_alg_; + const size_t key_size_; + const size_t auth_tag_size_; + const size_t nonce_size_; + const bool use_ietf_nonce_construction_; + + // The key. + unsigned char key_[kMaxKeySize]; + // The IV used to construct the nonce. + unsigned char iv_[kMaxNonceSize]; + + bssl::ScopedEVP_AEAD_CTX ctx_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AEAD_BASE_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.cc b/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.cc new file mode 100644 index 0000000..8c6fd1d --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.cc
@@ -0,0 +1,35 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h" + +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "third_party/boringssl/src/include/openssl/tls1.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 16; +const size_t kNonceSize = 12; + +} // namespace + +Aes128Gcm12Decrypter::Aes128Gcm12Decrypter() + : AesBaseDecrypter(EVP_aead_aes_128_gcm, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ false) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +Aes128Gcm12Decrypter::~Aes128Gcm12Decrypter() {} + +uint32_t Aes128Gcm12Decrypter::cipher_id() const { + return TLS1_CK_AES_128_GCM_SHA256; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h b/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h new file mode 100644 index 0000000..38a9419 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h
@@ -0,0 +1,38 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_12_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_12_DECRYPTER_H_ + +#include <cstdint> + +#include "quiche/quic/core/crypto/aes_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An Aes128Gcm12Decrypter is a QuicDecrypter that implements the +// AEAD_AES_128_GCM_12 algorithm specified in RFC 5282. Create an instance by +// calling QuicDecrypter::Create(kAESG). +// +// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix +// of the nonce is four bytes. +class QUIC_EXPORT_PRIVATE Aes128Gcm12Decrypter : public AesBaseDecrypter { + public: + enum { + // Authentication tags are truncated to 96 bits. + kAuthTagSize = 12, + }; + + Aes128Gcm12Decrypter(); + Aes128Gcm12Decrypter(const Aes128Gcm12Decrypter&) = delete; + Aes128Gcm12Decrypter& operator=(const Aes128Gcm12Decrypter&) = delete; + ~Aes128Gcm12Decrypter() override; + + uint32_t cipher_id() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc b/quiche/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc new file mode 100644 index 0000000..64e11eb --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc
@@ -0,0 +1,288 @@ +// 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 "quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The AES GCM test vectors come from the file gcmDecrypt128.rsp +// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on +// 2013-02-01. The test vectors in that file look like this: +// +// [Keylen = 128] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = cf063a34d4a9a76c2c86787d3f96db71 +// IV = 113b9785971864c83b01c787 +// CT = +// AAD = +// Tag = 72ac8493e3a5228b5d130a69d2510e42 +// PT = +// +// Count = 1 +// Key = a49a5e26a2f8cb63d05546c2a62f5343 +// IV = 907763b19b9b4ab6bd4f0281 +// CT = +// AAD = +// Tag = a2be08210d8c470a8df6e8fbd79ec5cf +// FAIL +// +// ... +// +// The gcmDecrypt128.rsp file is huge (2.6 MB), so I selected just a +// few test vectors for this unit test. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t pt_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + // Input: + const char* key; + const char* iv; + const char* ct; + const char* aad; + const char* tag; + + // Expected output: + const char* pt; // An empty string "" means decryption succeeded and + // the plaintext is zero-length. nullptr means decryption + // failed. +}; + +const TestGroupInfo test_group_info[] = { + {128, 96, 0, 0, 128}, {128, 96, 0, 128, 128}, {128, 96, 128, 0, 128}, + {128, 96, 408, 160, 128}, {128, 96, 408, 720, 128}, {128, 96, 104, 0, 128}, +}; + +const TestVector test_group_0[] = { + {"cf063a34d4a9a76c2c86787d3f96db71", "113b9785971864c83b01c787", "", "", + "72ac8493e3a5228b5d130a69d2510e42", ""}, + { + "a49a5e26a2f8cb63d05546c2a62f5343", "907763b19b9b4ab6bd4f0281", "", "", + "a2be08210d8c470a8df6e8fbd79ec5cf", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_1[] = { + { + "d1f6af919cde85661208bdce0c27cb22", "898c6929b435017bf031c3c5", "", + "7c5faa40e636bbc91107e68010c92b9f", "ae45f11777540a2caeb128be8092468a", + nullptr // FAIL + }, + {"2370e320d4344208e0ff5683f243b213", "04dbb82f044d30831c441228", "", + "d43a8e5089eea0d026c03a85178b27da", "2a049c049d25aa95969b451d93c31c6e", + ""}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_2[] = { + {"e98b72a9881a84ca6b76e0f43e68647a", "8b23299fde174053f3d652ba", + "5a3c1cf1985dbb8bed818036fdd5ab42", "", "23c7ab0f952b7091cd324835043b5eb5", + "28286a321293253c3e0aa2704a278032"}, + {"33240636cd3236165f1a553b773e728e", "17c4d61493ecdc8f31700b12", + "47bb7e23f7bdfe05a8091ac90e4f8b2e", "", "b723c70e931d9785f40fd4ab1d612dc9", + "95695a5b12f2870b9cc5fdc8f218a97d"}, + { + "5164df856f1e9cac04a79b808dc5be39", "e76925d5355e0584ce871b2b", + "0216c899c88d6e32c958c7e553daa5bc", "", + "a145319896329c96df291f64efbe0e3a", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_3[] = { + {"af57f42c60c0fc5a09adb81ab86ca1c3", "a2dc01871f37025dc0fc9a79", + "b9a535864f48ea7b6b1367914978f9bfa087d854bb0e269bed8d279d2eea1210e48947" + "338b22f9bad09093276a331e9c79c7f4", + "41dc38988945fcb44faf2ef72d0061289ef8efd8", + "4f71e72bde0018f555c5adcce062e005", + "3803a0727eeb0ade441e0ec107161ded2d425ec0d102f21f51bf2cf9947c7ec4aa7279" + "5b2f69b041596e8817d0a3c16f8fadeb"}, + {"ebc753e5422b377d3cb64b58ffa41b61", "2e1821efaced9acf1f241c9b", + "069567190554e9ab2b50a4e1fbf9c147340a5025fdbd201929834eaf6532325899ccb9" + "f401823e04b05817243d2142a3589878", + "b9673412fd4f88ba0e920f46dd6438ff791d8eef", + "534d9234d2351cf30e565de47baece0b", + "39077edb35e9c5a4b1e4c2a6b9bb1fce77f00f5023af40333d6d699014c2bcf4209c18" + "353a18017f5b36bfc00b1f6dcb7ed485"}, + { + "52bdbbf9cf477f187ec010589cb39d58", "d3be36d3393134951d324b31", + "700188da144fa692cf46e4a8499510a53d90903c967f7f13e8a1bd8151a74adc4fe63e" + "32b992760b3a5f99e9a47838867000a9", + "93c4fc6a4135f54d640b0c976bf755a06a292c33", + "8ca4e38aa3dfa6b1d0297021ccf3ea5f", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_4[] = { + {"da2bb7d581493d692380c77105590201", "44aa3e7856ca279d2eb020c6", + "9290d430c9e89c37f0446dbd620c9a6b34b1274aeb6f911f75867efcf95b6feda69f1a" + "f4ee16c761b3c9aeac3da03aa9889c88", + "4cd171b23bddb3a53cdf959d5c1710b481eb3785a90eb20a2345ee00d0bb7868c367ab" + "12e6f4dd1dee72af4eee1d197777d1d6499cc541f34edbf45cda6ef90b3c024f9272d7" + "2ec1909fb8fba7db88a4d6f7d3d925980f9f9f72", + "9e3ac938d3eb0cadd6f5c9e35d22ba38", + "9bbf4c1a2742f6ac80cb4e8a052e4a8f4f07c43602361355b717381edf9fabd4cb7e3a" + "d65dbd1378b196ac270588dd0621f642"}, + {"d74e4958717a9d5c0e235b76a926cae8", "0b7471141e0c70b1995fd7b1", + "e701c57d2330bf066f9ff8cf3ca4343cafe4894651cd199bdaaa681ba486b4a65c5a22" + "b0f1420be29ea547d42c713bc6af66aa", + "4a42b7aae8c245c6f1598a395316e4b8484dbd6e64648d5e302021b1d3fa0a38f46e22" + "bd9c8080b863dc0016482538a8562a4bd0ba84edbe2697c76fd039527ac179ec5506cf" + "34a6039312774cedebf4961f3978b14a26509f96", + "e192c23cb036f0b31592989119eed55d", + "840d9fb95e32559fb3602e48590280a172ca36d9b49ab69510f5bd552bfab7a306f85f" + "f0a34bc305b88b804c60b90add594a17"}, + { + "1986310c725ac94ecfe6422e75fc3ee7", "93ec4214fa8e6dc4e3afc775", + "b178ec72f85a311ac4168f42a4b2c23113fbea4b85f4b9dabb74e143eb1b8b0a361e02" + "43edfd365b90d5b325950df0ada058f9", + "e80b88e62c49c958b5e0b8b54f532d9ff6aa84c8a40132e93e55b59fc24e8decf28463" + "139f155d1e8ce4ee76aaeefcd245baa0fc519f83a5fb9ad9aa40c4b21126013f576c42" + "72c2cb136c8fd091cc4539877a5d1e72d607f960", + "8b347853f11d75e81e8a95010be81f17", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_5[] = { + {"387218b246c1a8257748b56980e50c94", "dd7e014198672be39f95b69d", + "cdba9e73eaf3d38eceb2b04a8d", "", "ecf90f4a47c9c626d6fb2c765d201556", + "48f5b426baca03064554cc2b30"}, + {"294de463721e359863887c820524b3d4", "3338b35c9d57a5d28190e8c9", + "2f46634e74b8e4c89812ac83b9", "", "dabd506764e68b82a7e720aa18da0abe", + "46a2e55c8e264df211bd112685"}, + {"28ead7fd2179e0d12aa6d5d88c58c2dc", "5055347f18b4d5add0ae5c41", + "142d8210c3fb84774cdbd0447a", "", "5fd321d9cdb01952dc85f034736c2a7d", + "3b95b981086ee73cc4d0cc1422"}, + { + "7d7b6c988137b8d470c57bf674a09c87", "9edf2aa970d016ac962e1fd8", + "a85b66c3cb5eab91d5bdc8bc0e", "", "dc054efc01f3afd21d9c2484819f569a", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector* const test_group_array[] = { + test_group_0, test_group_1, test_group_2, + test_group_3, test_group_4, test_group_5, +}; + +} // namespace + +namespace quic { +namespace test { + +// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the plaintext. +QuicData* DecryptWithNonce(Aes128Gcm12Decrypter* decrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view ciphertext) { + uint64_t packet_number; + absl::string_view nonce_prefix(nonce.data(), + nonce.size() - sizeof(packet_number)); + decrypter->SetNoncePrefix(nonce_prefix); + memcpy(&packet_number, nonce.data() + nonce_prefix.size(), + sizeof(packet_number)); + std::unique_ptr<char[]> output(new char[ciphertext.length()]); + size_t output_length = 0; + const bool success = decrypter->DecryptPacket( + packet_number, associated_data, ciphertext, output.get(), &output_length, + ciphertext.length()); + if (!success) { + return nullptr; + } + return new QuicData(output.release(), output_length, true); +} + +class Aes128Gcm12DecrypterTest : public QuicTest {}; + +TEST_F(Aes128Gcm12DecrypterTest, Decrypt) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // If not present then decryption is expected to fail. + bool has_pt = test_vectors[j].pt; + + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[j].key); + std::string iv = absl::HexStringToBytes(test_vectors[j].iv); + std::string ct = absl::HexStringToBytes(test_vectors[j].ct); + std::string aad = absl::HexStringToBytes(test_vectors[j].aad); + std::string tag = absl::HexStringToBytes(test_vectors[j].tag); + std::string pt; + if (has_pt) { + pt = absl::HexStringToBytes(test_vectors[j].pt); + } + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.pt_len, ct.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + if (has_pt) { + EXPECT_EQ(test_info.pt_len, pt.length() * 8); + } + + // The test vectors have 16 byte authenticators but this code only uses + // the first 12. + ASSERT_LE(static_cast<size_t>(Aes128Gcm12Decrypter::kAuthTagSize), + tag.length()); + tag.resize(Aes128Gcm12Decrypter::kAuthTagSize); + std::string ciphertext = ct + tag; + + Aes128Gcm12Decrypter decrypter; + ASSERT_TRUE(decrypter.SetKey(key)); + + std::unique_ptr<QuicData> decrypted(DecryptWithNonce( + &decrypter, iv, + // This deliberately tests that the decrypter can + // handle an AAD that is set to nullptr, as opposed + // to a zero-length, non-nullptr pointer. + aad.length() ? aad : absl::string_view(), ciphertext)); + if (!decrypted) { + EXPECT_FALSE(has_pt); + continue; + } + EXPECT_TRUE(has_pt); + + ASSERT_EQ(pt.length(), decrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "plaintext", decrypted->data(), pt.length(), pt.data(), pt.length()); + } + } +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.cc b/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.cc new file mode 100644 index 0000000..1018c41 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.cc
@@ -0,0 +1,30 @@ +// 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 "quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h" + +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 16; +const size_t kNonceSize = 12; + +} // namespace + +Aes128Gcm12Encrypter::Aes128Gcm12Encrypter() + : AesBaseEncrypter(EVP_aead_aes_128_gcm, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ false) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +Aes128Gcm12Encrypter::~Aes128Gcm12Encrypter() {} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h b/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h new file mode 100644 index 0000000..64f0292 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h
@@ -0,0 +1,34 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_ + +#include "quiche/quic/core/crypto/aes_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An Aes128Gcm12Encrypter is a QuicEncrypter that implements the +// AEAD_AES_128_GCM_12 algorithm specified in RFC 5282. Create an instance by +// calling QuicEncrypter::Create(kAESG). +// +// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix +// of the nonce is four bytes. +class QUIC_EXPORT_PRIVATE Aes128Gcm12Encrypter : public AesBaseEncrypter { + public: + enum { + // Authentication tags are truncated to 96 bits. + kAuthTagSize = 12, + }; + + Aes128Gcm12Encrypter(); + Aes128Gcm12Encrypter(const Aes128Gcm12Encrypter&) = delete; + Aes128Gcm12Encrypter& operator=(const Aes128Gcm12Encrypter&) = delete; + ~Aes128Gcm12Encrypter() override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_128_gcm_12_encrypter_test.cc b/quiche/quic/core/crypto/aes_128_gcm_12_encrypter_test.cc new file mode 100644 index 0000000..47dbd67 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_12_encrypter_test.cc
@@ -0,0 +1,244 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The AES GCM test vectors come from the file gcmEncryptExtIV128.rsp +// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on +// 2013-02-01. The test vectors in that file look like this: +// +// [Keylen = 128] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = 11754cd72aec309bf52f7687212e8957 +// IV = 3c819d9a9bed087615030b65 +// PT = +// AAD = +// CT = +// Tag = 250327c674aaf477aef2675748cf6971 +// +// Count = 1 +// Key = ca47248ac0b6f8372a97ac43508308ed +// IV = ffd2b598feabc9019262d2be +// PT = +// AAD = +// CT = +// Tag = 60d20404af527d248d893ae495707d1a +// +// ... +// +// The gcmEncryptExtIV128.rsp file is huge (2.8 MB), so I selected just a +// few test vectors for this unit test. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t pt_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + const char* key; + const char* iv; + const char* pt; + const char* aad; + const char* ct; + const char* tag; +}; + +const TestGroupInfo test_group_info[] = { + {128, 96, 0, 0, 128}, {128, 96, 0, 128, 128}, {128, 96, 128, 0, 128}, + {128, 96, 408, 160, 128}, {128, 96, 408, 720, 128}, {128, 96, 104, 0, 128}, +}; + +const TestVector test_group_0[] = { + {"11754cd72aec309bf52f7687212e8957", "3c819d9a9bed087615030b65", "", "", "", + "250327c674aaf477aef2675748cf6971"}, + {"ca47248ac0b6f8372a97ac43508308ed", "ffd2b598feabc9019262d2be", "", "", "", + "60d20404af527d248d893ae495707d1a"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_1[] = { + {"77be63708971c4e240d1cb79e8d77feb", "e0e00f19fed7ba0136a797f3", "", + "7a43ec1d9c0a5a78a0b16533a6213cab", "", + "209fcc8d3675ed938e9c7166709dd946"}, + {"7680c5d3ca6154758e510f4d25b98820", "f8f105f9c3df4965780321f8", "", + "c94c410194c765e3dcc7964379758ed3", "", + "94dca8edfcf90bb74b153c8d48a17930"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_2[] = { + {"7fddb57453c241d03efbed3ac44e371c", "ee283a3fc75575e33efd4887", + "d5de42b461646c255c87bd2962d3b9a2", "", "2ccda4a5415cb91e135c2a0f78c9b2fd", + "b36d1df9b9d5e596f83e8b7f52971cb3"}, + {"ab72c77b97cb5fe9a382d9fe81ffdbed", "54cc7dc2c37ec006bcc6d1da", + "007c5e5b3e59df24a7c355584fc1518d", "", "0e1bde206a07a9c2c1b65300f8c64997", + "2b4401346697138c7a4891ee59867d0c"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_3[] = { + {"fe47fcce5fc32665d2ae399e4eec72ba", "5adb9609dbaeb58cbd6e7275", + "7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1" + "b840382c4bccaf3bafb4ca8429bea063", + "88319d6e1d3ffa5f987199166c8a9b56c2aeba5a", + "98f4826f05a265e6dd2be82db241c0fbbbf9ffb1c173aa83964b7cf539304373636525" + "3ddbc5db8778371495da76d269e5db3e", + "291ef1982e4defedaa2249f898556b47"}, + {"ec0c2ba17aa95cd6afffe949da9cc3a8", "296bce5b50b7d66096d627ef", + "b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987" + "b764b9611f6c0f8641843d5d58f3a242", + "f8d00f05d22bf68599bcdeb131292ad6e2df5d14", + "a7443d31c26bdf2a1c945e29ee4bd344a99cfaf3aa71f8b3f191f83c2adfc7a0716299" + "5506fde6309ffc19e716eddf1a828c5a", + "890147971946b627c40016da1ecf3e77"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_4[] = { + {"2c1f21cf0f6fb3661943155c3e3d8492", "23cb5ff362e22426984d1907", + "42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d6" + "8b5615ba7c1220ff6510e259f06655d8", + "5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e" + "3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f" + "4488f33cfb5e979e42b6e1cfc0a60238982a7aec", + "81824f0e0d523db30d3da369fdc0d60894c7a0a20646dd015073ad2732bd989b14a222" + "b6ad57af43e1895df9dca2a5344a62cc", + "57a3ee28136e94c74838997ae9823f3a"}, + {"d9f7d2411091f947b4d6f1e2d1f0fb2e", "e1934f5db57cc983e6b180e7", + "73ed042327f70fe9c572a61545eda8b2a0c6e1d6c291ef19248e973aee6c312012f490" + "c2c6f6166f4a59431e182663fcaea05a", + "0a8a18a7150e940c3d87b38e73baee9a5c049ee21795663e264b694a949822b639092d" + "0e67015e86363583fcf0ca645af9f43375f05fdb4ce84f411dcbca73c2220dea03a201" + "15d2e51398344b16bee1ed7c499b353d6c597af8", + "aaadbd5c92e9151ce3db7210b8714126b73e43436d242677afa50384f2149b831f1d57" + "3c7891c2a91fbc48db29967ec9542b23", + "21b51ca862cb637cdd03b99a0f93b134"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_5[] = { + {"fe9bb47deb3a61e423c2231841cfd1fb", "4d328eb776f500a2f7fb47aa", + "f1cc3818e421876bb6b8bbd6c9", "", "b88c5c1977b35b517b0aeae967", + "43fd4727fe5cdb4b5b42818dea7ef8c9"}, + {"6703df3701a7f54911ca72e24dca046a", "12823ab601c350ea4bc2488c", + "793cd125b0b84a043e3ac67717", "", "b2051c80014f42f08735a7b0cd", + "38e6bcd29962e5f2c13626b85a877101"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector* const test_group_array[] = { + test_group_0, test_group_1, test_group_2, + test_group_3, test_group_4, test_group_5, +}; + +} // namespace + +namespace quic { +namespace test { + +// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the ciphertext. +QuicData* EncryptWithNonce(Aes128Gcm12Encrypter* encrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext) { + size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length()); + std::unique_ptr<char[]> ciphertext(new char[ciphertext_size]); + + if (!encrypter->Encrypt(nonce, associated_data, plaintext, + reinterpret_cast<unsigned char*>(ciphertext.get()))) { + return nullptr; + } + + return new QuicData(ciphertext.release(), ciphertext_size, true); +} + +class Aes128Gcm12EncrypterTest : public QuicTest {}; + +TEST_F(Aes128Gcm12EncrypterTest, Encrypt) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[j].key); + std::string iv = absl::HexStringToBytes(test_vectors[j].iv); + std::string pt = absl::HexStringToBytes(test_vectors[j].pt); + std::string aad = absl::HexStringToBytes(test_vectors[j].aad); + std::string ct = absl::HexStringToBytes(test_vectors[j].ct); + std::string tag = absl::HexStringToBytes(test_vectors[j].tag); + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.pt_len, pt.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.pt_len, ct.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + + Aes128Gcm12Encrypter encrypter; + ASSERT_TRUE(encrypter.SetKey(key)); + std::unique_ptr<QuicData> encrypted( + EncryptWithNonce(&encrypter, iv, + // This deliberately tests that the encrypter can + // handle an AAD that is set to nullptr, as opposed + // to a zero-length, non-nullptr pointer. + aad.length() ? aad : absl::string_view(), pt)); + ASSERT_TRUE(encrypted.get()); + + // The test vectors have 16 byte authenticators but this code only uses + // the first 12. + ASSERT_LE(static_cast<size_t>(Aes128Gcm12Encrypter::kAuthTagSize), + tag.length()); + tag.resize(Aes128Gcm12Encrypter::kAuthTagSize); + + ASSERT_EQ(ct.length() + tag.length(), encrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "ciphertext", encrypted->data(), ct.length(), ct.data(), ct.length()); + quiche::test::CompareCharArraysWithHexError( + "authentication tag", encrypted->data() + ct.length(), tag.length(), + tag.data(), tag.length()); + } + } +} + +TEST_F(Aes128Gcm12EncrypterTest, GetMaxPlaintextSize) { + Aes128Gcm12Encrypter encrypter; + EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012)); + EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112)); + EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22)); + EXPECT_EQ(0u, encrypter.GetMaxPlaintextSize(11)); +} + +TEST_F(Aes128Gcm12EncrypterTest, GetCiphertextSize) { + Aes128Gcm12Encrypter encrypter; + EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000)); + EXPECT_EQ(112u, encrypter.GetCiphertextSize(100)); + EXPECT_EQ(22u, encrypter.GetCiphertextSize(10)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_decrypter.cc b/quiche/quic/core/crypto/aes_128_gcm_decrypter.cc new file mode 100644 index 0000000..d714ca0 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_decrypter.cc
@@ -0,0 +1,37 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_128_gcm_decrypter.h" + +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "third_party/boringssl/src/include/openssl/tls1.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 16; +const size_t kNonceSize = 12; + +} // namespace + +Aes128GcmDecrypter::Aes128GcmDecrypter() + : AesBaseDecrypter(EVP_aead_aes_128_gcm, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ true) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +Aes128GcmDecrypter::~Aes128GcmDecrypter() {} + +uint32_t Aes128GcmDecrypter::cipher_id() const { + return TLS1_CK_AES_128_GCM_SHA256; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_decrypter.h b/quiche/quic/core/crypto/aes_128_gcm_decrypter.h new file mode 100644 index 0000000..c5f4d17 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_decrypter.h
@@ -0,0 +1,36 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_DECRYPTER_H_ + +#include <cstdint> + +#include "quiche/quic/core/crypto/aes_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An Aes128GcmDecrypter is a QuicDecrypter that implements the +// AEAD_AES_128_GCM algorithm specified in RFC 5116 for use in IETF QUIC. +// +// It uses an authentication tag of 16 bytes (128 bits). It uses a 12 byte IV +// that is XOR'd with the packet number to compute the nonce. +class QUIC_EXPORT_PRIVATE Aes128GcmDecrypter : public AesBaseDecrypter { + public: + enum { + kAuthTagSize = 16, + }; + + Aes128GcmDecrypter(); + Aes128GcmDecrypter(const Aes128GcmDecrypter&) = delete; + Aes128GcmDecrypter& operator=(const Aes128GcmDecrypter&) = delete; + ~Aes128GcmDecrypter() override; + + uint32_t cipher_id() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_128_gcm_decrypter_test.cc b/quiche/quic/core/crypto/aes_128_gcm_decrypter_test.cc new file mode 100644 index 0000000..e02b433 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_decrypter_test.cc
@@ -0,0 +1,291 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_128_gcm_decrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The AES GCM test vectors come from the file gcmDecrypt128.rsp +// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on +// 2013-02-01. The test vectors in that file look like this: +// +// [Keylen = 128] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = cf063a34d4a9a76c2c86787d3f96db71 +// IV = 113b9785971864c83b01c787 +// CT = +// AAD = +// Tag = 72ac8493e3a5228b5d130a69d2510e42 +// PT = +// +// Count = 1 +// Key = a49a5e26a2f8cb63d05546c2a62f5343 +// IV = 907763b19b9b4ab6bd4f0281 +// CT = +// AAD = +// Tag = a2be08210d8c470a8df6e8fbd79ec5cf +// FAIL +// +// ... +// +// The gcmDecrypt128.rsp file is huge (2.6 MB), so I selected just a +// few test vectors for this unit test. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t pt_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + // Input: + const char* key; + const char* iv; + const char* ct; + const char* aad; + const char* tag; + + // Expected output: + const char* pt; // An empty string "" means decryption succeeded and + // the plaintext is zero-length. nullptr means decryption + // failed. +}; + +const TestGroupInfo test_group_info[] = { + {128, 96, 0, 0, 128}, {128, 96, 0, 128, 128}, {128, 96, 128, 0, 128}, + {128, 96, 408, 160, 128}, {128, 96, 408, 720, 128}, {128, 96, 104, 0, 128}, +}; + +const TestVector test_group_0[] = { + {"cf063a34d4a9a76c2c86787d3f96db71", "113b9785971864c83b01c787", "", "", + "72ac8493e3a5228b5d130a69d2510e42", ""}, + { + "a49a5e26a2f8cb63d05546c2a62f5343", "907763b19b9b4ab6bd4f0281", "", "", + "a2be08210d8c470a8df6e8fbd79ec5cf", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_1[] = { + { + "d1f6af919cde85661208bdce0c27cb22", "898c6929b435017bf031c3c5", "", + "7c5faa40e636bbc91107e68010c92b9f", "ae45f11777540a2caeb128be8092468a", + nullptr // FAIL + }, + {"2370e320d4344208e0ff5683f243b213", "04dbb82f044d30831c441228", "", + "d43a8e5089eea0d026c03a85178b27da", "2a049c049d25aa95969b451d93c31c6e", + ""}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_2[] = { + {"e98b72a9881a84ca6b76e0f43e68647a", "8b23299fde174053f3d652ba", + "5a3c1cf1985dbb8bed818036fdd5ab42", "", "23c7ab0f952b7091cd324835043b5eb5", + "28286a321293253c3e0aa2704a278032"}, + {"33240636cd3236165f1a553b773e728e", "17c4d61493ecdc8f31700b12", + "47bb7e23f7bdfe05a8091ac90e4f8b2e", "", "b723c70e931d9785f40fd4ab1d612dc9", + "95695a5b12f2870b9cc5fdc8f218a97d"}, + { + "5164df856f1e9cac04a79b808dc5be39", "e76925d5355e0584ce871b2b", + "0216c899c88d6e32c958c7e553daa5bc", "", + "a145319896329c96df291f64efbe0e3a", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_3[] = { + {"af57f42c60c0fc5a09adb81ab86ca1c3", "a2dc01871f37025dc0fc9a79", + "b9a535864f48ea7b6b1367914978f9bfa087d854bb0e269bed8d279d2eea1210e48947" + "338b22f9bad09093276a331e9c79c7f4", + "41dc38988945fcb44faf2ef72d0061289ef8efd8", + "4f71e72bde0018f555c5adcce062e005", + "3803a0727eeb0ade441e0ec107161ded2d425ec0d102f21f51bf2cf9947c7ec4aa7279" + "5b2f69b041596e8817d0a3c16f8fadeb"}, + {"ebc753e5422b377d3cb64b58ffa41b61", "2e1821efaced9acf1f241c9b", + "069567190554e9ab2b50a4e1fbf9c147340a5025fdbd201929834eaf6532325899ccb9" + "f401823e04b05817243d2142a3589878", + "b9673412fd4f88ba0e920f46dd6438ff791d8eef", + "534d9234d2351cf30e565de47baece0b", + "39077edb35e9c5a4b1e4c2a6b9bb1fce77f00f5023af40333d6d699014c2bcf4209c18" + "353a18017f5b36bfc00b1f6dcb7ed485"}, + { + "52bdbbf9cf477f187ec010589cb39d58", "d3be36d3393134951d324b31", + "700188da144fa692cf46e4a8499510a53d90903c967f7f13e8a1bd8151a74adc4fe63e" + "32b992760b3a5f99e9a47838867000a9", + "93c4fc6a4135f54d640b0c976bf755a06a292c33", + "8ca4e38aa3dfa6b1d0297021ccf3ea5f", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_4[] = { + {"da2bb7d581493d692380c77105590201", "44aa3e7856ca279d2eb020c6", + "9290d430c9e89c37f0446dbd620c9a6b34b1274aeb6f911f75867efcf95b6feda69f1a" + "f4ee16c761b3c9aeac3da03aa9889c88", + "4cd171b23bddb3a53cdf959d5c1710b481eb3785a90eb20a2345ee00d0bb7868c367ab" + "12e6f4dd1dee72af4eee1d197777d1d6499cc541f34edbf45cda6ef90b3c024f9272d7" + "2ec1909fb8fba7db88a4d6f7d3d925980f9f9f72", + "9e3ac938d3eb0cadd6f5c9e35d22ba38", + "9bbf4c1a2742f6ac80cb4e8a052e4a8f4f07c43602361355b717381edf9fabd4cb7e3a" + "d65dbd1378b196ac270588dd0621f642"}, + {"d74e4958717a9d5c0e235b76a926cae8", "0b7471141e0c70b1995fd7b1", + "e701c57d2330bf066f9ff8cf3ca4343cafe4894651cd199bdaaa681ba486b4a65c5a22" + "b0f1420be29ea547d42c713bc6af66aa", + "4a42b7aae8c245c6f1598a395316e4b8484dbd6e64648d5e302021b1d3fa0a38f46e22" + "bd9c8080b863dc0016482538a8562a4bd0ba84edbe2697c76fd039527ac179ec5506cf" + "34a6039312774cedebf4961f3978b14a26509f96", + "e192c23cb036f0b31592989119eed55d", + "840d9fb95e32559fb3602e48590280a172ca36d9b49ab69510f5bd552bfab7a306f85f" + "f0a34bc305b88b804c60b90add594a17"}, + { + "1986310c725ac94ecfe6422e75fc3ee7", "93ec4214fa8e6dc4e3afc775", + "b178ec72f85a311ac4168f42a4b2c23113fbea4b85f4b9dabb74e143eb1b8b0a361e02" + "43edfd365b90d5b325950df0ada058f9", + "e80b88e62c49c958b5e0b8b54f532d9ff6aa84c8a40132e93e55b59fc24e8decf28463" + "139f155d1e8ce4ee76aaeefcd245baa0fc519f83a5fb9ad9aa40c4b21126013f576c42" + "72c2cb136c8fd091cc4539877a5d1e72d607f960", + "8b347853f11d75e81e8a95010be81f17", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_5[] = { + {"387218b246c1a8257748b56980e50c94", "dd7e014198672be39f95b69d", + "cdba9e73eaf3d38eceb2b04a8d", "", "ecf90f4a47c9c626d6fb2c765d201556", + "48f5b426baca03064554cc2b30"}, + {"294de463721e359863887c820524b3d4", "3338b35c9d57a5d28190e8c9", + "2f46634e74b8e4c89812ac83b9", "", "dabd506764e68b82a7e720aa18da0abe", + "46a2e55c8e264df211bd112685"}, + {"28ead7fd2179e0d12aa6d5d88c58c2dc", "5055347f18b4d5add0ae5c41", + "142d8210c3fb84774cdbd0447a", "", "5fd321d9cdb01952dc85f034736c2a7d", + "3b95b981086ee73cc4d0cc1422"}, + { + "7d7b6c988137b8d470c57bf674a09c87", "9edf2aa970d016ac962e1fd8", + "a85b66c3cb5eab91d5bdc8bc0e", "", "dc054efc01f3afd21d9c2484819f569a", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector* const test_group_array[] = { + test_group_0, test_group_1, test_group_2, + test_group_3, test_group_4, test_group_5, +}; + +} // namespace + +namespace quic { +namespace test { + +// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the plaintext. +QuicData* DecryptWithNonce(Aes128GcmDecrypter* decrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view ciphertext) { + decrypter->SetIV(nonce); + std::unique_ptr<char[]> output(new char[ciphertext.length()]); + size_t output_length = 0; + const bool success = + decrypter->DecryptPacket(0, associated_data, ciphertext, output.get(), + &output_length, ciphertext.length()); + if (!success) { + return nullptr; + } + return new QuicData(output.release(), output_length, true); +} + +class Aes128GcmDecrypterTest : public QuicTest {}; + +TEST_F(Aes128GcmDecrypterTest, Decrypt) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // If not present then decryption is expected to fail. + bool has_pt = test_vectors[j].pt; + + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[j].key); + std::string iv = absl::HexStringToBytes(test_vectors[j].iv); + std::string ct = absl::HexStringToBytes(test_vectors[j].ct); + std::string aad = absl::HexStringToBytes(test_vectors[j].aad); + std::string tag = absl::HexStringToBytes(test_vectors[j].tag); + std::string pt; + if (has_pt) { + pt = absl::HexStringToBytes(test_vectors[j].pt); + } + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.pt_len, ct.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + if (has_pt) { + EXPECT_EQ(test_info.pt_len, pt.length() * 8); + } + std::string ciphertext = ct + tag; + + Aes128GcmDecrypter decrypter; + ASSERT_TRUE(decrypter.SetKey(key)); + + std::unique_ptr<QuicData> decrypted(DecryptWithNonce( + &decrypter, iv, + // This deliberately tests that the decrypter can + // handle an AAD that is set to nullptr, as opposed + // to a zero-length, non-nullptr pointer. + aad.length() ? aad : absl::string_view(), ciphertext)); + if (!decrypted) { + EXPECT_FALSE(has_pt); + continue; + } + EXPECT_TRUE(has_pt); + + ASSERT_EQ(pt.length(), decrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "plaintext", decrypted->data(), pt.length(), pt.data(), pt.length()); + } + } +} + +TEST_F(Aes128GcmDecrypterTest, GenerateHeaderProtectionMask) { + Aes128GcmDecrypter decrypter; + std::string key = absl::HexStringToBytes("d9132370cb18476ab833649cf080d970"); + std::string sample = + absl::HexStringToBytes("d1d7998068517adb769b48b924a32c47"); + QuicDataReader sample_reader(sample.data(), sample.size()); + ASSERT_TRUE(decrypter.SetHeaderProtectionKey(key)); + std::string mask = decrypter.GenerateHeaderProtectionMask(&sample_reader); + std::string expected_mask = + absl::HexStringToBytes("b132c37d6164da4ea4dc9b763aceec27"); + quiche::test::CompareCharArraysWithHexError( + "header protection mask", mask.data(), mask.size(), expected_mask.data(), + expected_mask.size()); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_encrypter.cc b/quiche/quic/core/crypto/aes_128_gcm_encrypter.cc new file mode 100644 index 0000000..56f698f --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_encrypter.cc
@@ -0,0 +1,30 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_128_gcm_encrypter.h" + +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 16; +const size_t kNonceSize = 12; + +} // namespace + +Aes128GcmEncrypter::Aes128GcmEncrypter() + : AesBaseEncrypter(EVP_aead_aes_128_gcm, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ true) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +Aes128GcmEncrypter::~Aes128GcmEncrypter() {} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_128_gcm_encrypter.h b/quiche/quic/core/crypto/aes_128_gcm_encrypter.h new file mode 100644 index 0000000..a40735c --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_encrypter.h
@@ -0,0 +1,32 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_ENCRYPTER_H_ + +#include "quiche/quic/core/crypto/aes_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An Aes128GcmEncrypter is a QuicEncrypter that implements the +// AEAD_AES_128_GCM algorithm specified in RFC 5116 for use in IETF QUIC. +// +// It uses an authentication tag of 16 bytes (128 bits). It uses a 12 byte IV +// that is XOR'd with the packet number to compute the nonce. +class QUIC_EXPORT_PRIVATE Aes128GcmEncrypter : public AesBaseEncrypter { + public: + enum { + kAuthTagSize = 16, + }; + + Aes128GcmEncrypter(); + Aes128GcmEncrypter(const Aes128GcmEncrypter&) = delete; + Aes128GcmEncrypter& operator=(const Aes128GcmEncrypter&) = delete; + ~Aes128GcmEncrypter() override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_128_GCM_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_128_gcm_encrypter_test.cc b/quiche/quic/core/crypto/aes_128_gcm_encrypter_test.cc new file mode 100644 index 0000000..7086094 --- /dev/null +++ b/quiche/quic/core/crypto/aes_128_gcm_encrypter_test.cc
@@ -0,0 +1,273 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_128_gcm_encrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The AES GCM test vectors come from the file gcmEncryptExtIV128.rsp +// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on +// 2013-02-01. The test vectors in that file look like this: +// +// [Keylen = 128] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = 11754cd72aec309bf52f7687212e8957 +// IV = 3c819d9a9bed087615030b65 +// PT = +// AAD = +// CT = +// Tag = 250327c674aaf477aef2675748cf6971 +// +// Count = 1 +// Key = ca47248ac0b6f8372a97ac43508308ed +// IV = ffd2b598feabc9019262d2be +// PT = +// AAD = +// CT = +// Tag = 60d20404af527d248d893ae495707d1a +// +// ... +// +// The gcmEncryptExtIV128.rsp file is huge (2.8 MB), so I selected just a +// few test vectors for this unit test. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t pt_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + const char* key; + const char* iv; + const char* pt; + const char* aad; + const char* ct; + const char* tag; +}; + +const TestGroupInfo test_group_info[] = { + {128, 96, 0, 0, 128}, {128, 96, 0, 128, 128}, {128, 96, 128, 0, 128}, + {128, 96, 408, 160, 128}, {128, 96, 408, 720, 128}, {128, 96, 104, 0, 128}, +}; + +const TestVector test_group_0[] = { + {"11754cd72aec309bf52f7687212e8957", "3c819d9a9bed087615030b65", "", "", "", + "250327c674aaf477aef2675748cf6971"}, + {"ca47248ac0b6f8372a97ac43508308ed", "ffd2b598feabc9019262d2be", "", "", "", + "60d20404af527d248d893ae495707d1a"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_1[] = { + {"77be63708971c4e240d1cb79e8d77feb", "e0e00f19fed7ba0136a797f3", "", + "7a43ec1d9c0a5a78a0b16533a6213cab", "", + "209fcc8d3675ed938e9c7166709dd946"}, + {"7680c5d3ca6154758e510f4d25b98820", "f8f105f9c3df4965780321f8", "", + "c94c410194c765e3dcc7964379758ed3", "", + "94dca8edfcf90bb74b153c8d48a17930"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_2[] = { + {"7fddb57453c241d03efbed3ac44e371c", "ee283a3fc75575e33efd4887", + "d5de42b461646c255c87bd2962d3b9a2", "", "2ccda4a5415cb91e135c2a0f78c9b2fd", + "b36d1df9b9d5e596f83e8b7f52971cb3"}, + {"ab72c77b97cb5fe9a382d9fe81ffdbed", "54cc7dc2c37ec006bcc6d1da", + "007c5e5b3e59df24a7c355584fc1518d", "", "0e1bde206a07a9c2c1b65300f8c64997", + "2b4401346697138c7a4891ee59867d0c"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_3[] = { + {"fe47fcce5fc32665d2ae399e4eec72ba", "5adb9609dbaeb58cbd6e7275", + "7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1" + "b840382c4bccaf3bafb4ca8429bea063", + "88319d6e1d3ffa5f987199166c8a9b56c2aeba5a", + "98f4826f05a265e6dd2be82db241c0fbbbf9ffb1c173aa83964b7cf539304373636525" + "3ddbc5db8778371495da76d269e5db3e", + "291ef1982e4defedaa2249f898556b47"}, + {"ec0c2ba17aa95cd6afffe949da9cc3a8", "296bce5b50b7d66096d627ef", + "b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987" + "b764b9611f6c0f8641843d5d58f3a242", + "f8d00f05d22bf68599bcdeb131292ad6e2df5d14", + "a7443d31c26bdf2a1c945e29ee4bd344a99cfaf3aa71f8b3f191f83c2adfc7a0716299" + "5506fde6309ffc19e716eddf1a828c5a", + "890147971946b627c40016da1ecf3e77"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_4[] = { + {"2c1f21cf0f6fb3661943155c3e3d8492", "23cb5ff362e22426984d1907", + "42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d6" + "8b5615ba7c1220ff6510e259f06655d8", + "5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e" + "3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f" + "4488f33cfb5e979e42b6e1cfc0a60238982a7aec", + "81824f0e0d523db30d3da369fdc0d60894c7a0a20646dd015073ad2732bd989b14a222" + "b6ad57af43e1895df9dca2a5344a62cc", + "57a3ee28136e94c74838997ae9823f3a"}, + {"d9f7d2411091f947b4d6f1e2d1f0fb2e", "e1934f5db57cc983e6b180e7", + "73ed042327f70fe9c572a61545eda8b2a0c6e1d6c291ef19248e973aee6c312012f490" + "c2c6f6166f4a59431e182663fcaea05a", + "0a8a18a7150e940c3d87b38e73baee9a5c049ee21795663e264b694a949822b639092d" + "0e67015e86363583fcf0ca645af9f43375f05fdb4ce84f411dcbca73c2220dea03a201" + "15d2e51398344b16bee1ed7c499b353d6c597af8", + "aaadbd5c92e9151ce3db7210b8714126b73e43436d242677afa50384f2149b831f1d57" + "3c7891c2a91fbc48db29967ec9542b23", + "21b51ca862cb637cdd03b99a0f93b134"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_5[] = { + {"fe9bb47deb3a61e423c2231841cfd1fb", "4d328eb776f500a2f7fb47aa", + "f1cc3818e421876bb6b8bbd6c9", "", "b88c5c1977b35b517b0aeae967", + "43fd4727fe5cdb4b5b42818dea7ef8c9"}, + {"6703df3701a7f54911ca72e24dca046a", "12823ab601c350ea4bc2488c", + "793cd125b0b84a043e3ac67717", "", "b2051c80014f42f08735a7b0cd", + "38e6bcd29962e5f2c13626b85a877101"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector* const test_group_array[] = { + test_group_0, test_group_1, test_group_2, + test_group_3, test_group_4, test_group_5, +}; + +} // namespace + +namespace quic { +namespace test { + +// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the ciphertext. +QuicData* EncryptWithNonce(Aes128GcmEncrypter* encrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext) { + size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length()); + std::unique_ptr<char[]> ciphertext(new char[ciphertext_size]); + + if (!encrypter->Encrypt(nonce, associated_data, plaintext, + reinterpret_cast<unsigned char*>(ciphertext.get()))) { + return nullptr; + } + + return new QuicData(ciphertext.release(), ciphertext_size, true); +} + +class Aes128GcmEncrypterTest : public QuicTest {}; + +TEST_F(Aes128GcmEncrypterTest, Encrypt) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[j].key); + std::string iv = absl::HexStringToBytes(test_vectors[j].iv); + std::string pt = absl::HexStringToBytes(test_vectors[j].pt); + std::string aad = absl::HexStringToBytes(test_vectors[j].aad); + std::string ct = absl::HexStringToBytes(test_vectors[j].ct); + std::string tag = absl::HexStringToBytes(test_vectors[j].tag); + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.pt_len, pt.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.pt_len, ct.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + + Aes128GcmEncrypter encrypter; + ASSERT_TRUE(encrypter.SetKey(key)); + std::unique_ptr<QuicData> encrypted( + EncryptWithNonce(&encrypter, iv, + // This deliberately tests that the encrypter can + // handle an AAD that is set to nullptr, as opposed + // to a zero-length, non-nullptr pointer. + aad.length() ? aad : absl::string_view(), pt)); + ASSERT_TRUE(encrypted.get()); + + ASSERT_EQ(ct.length() + tag.length(), encrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "ciphertext", encrypted->data(), ct.length(), ct.data(), ct.length()); + quiche::test::CompareCharArraysWithHexError( + "authentication tag", encrypted->data() + ct.length(), tag.length(), + tag.data(), tag.length()); + } + } +} + +TEST_F(Aes128GcmEncrypterTest, EncryptPacket) { + std::string key = absl::HexStringToBytes("d95a145250826c25a77b6a84fd4d34fc"); + std::string iv = absl::HexStringToBytes("50c4431ebb18283448e276e2"); + uint64_t packet_num = 0x13278f44; + std::string aad = + absl::HexStringToBytes("875d49f64a70c9cbe713278f44ff000005"); + std::string pt = absl::HexStringToBytes("aa0003a250bd000000000001"); + std::string ct = absl::HexStringToBytes( + "7dd4708b989ee7d38a013e3656e9b37beefd05808fe1ab41e3b4f2c0"); + + std::vector<char> out(ct.size()); + size_t out_size; + + Aes128GcmEncrypter encrypter; + ASSERT_TRUE(encrypter.SetKey(key)); + ASSERT_TRUE(encrypter.SetIV(iv)); + ASSERT_TRUE(encrypter.EncryptPacket(packet_num, aad, pt, out.data(), + &out_size, out.size())); + EXPECT_EQ(out_size, out.size()); + quiche::test::CompareCharArraysWithHexError("ciphertext", out.data(), + out.size(), ct.data(), ct.size()); +} + +TEST_F(Aes128GcmEncrypterTest, GetMaxPlaintextSize) { + Aes128GcmEncrypter encrypter; + EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1016)); + EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(116)); + EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(26)); +} + +TEST_F(Aes128GcmEncrypterTest, GetCiphertextSize) { + Aes128GcmEncrypter encrypter; + EXPECT_EQ(1016u, encrypter.GetCiphertextSize(1000)); + EXPECT_EQ(116u, encrypter.GetCiphertextSize(100)); + EXPECT_EQ(26u, encrypter.GetCiphertextSize(10)); +} + +TEST_F(Aes128GcmEncrypterTest, GenerateHeaderProtectionMask) { + Aes128GcmEncrypter encrypter; + std::string key = absl::HexStringToBytes("d9132370cb18476ab833649cf080d970"); + std::string sample = + absl::HexStringToBytes("d1d7998068517adb769b48b924a32c47"); + ASSERT_TRUE(encrypter.SetHeaderProtectionKey(key)); + std::string mask = encrypter.GenerateHeaderProtectionMask(sample); + std::string expected_mask = + absl::HexStringToBytes("b132c37d6164da4ea4dc9b763aceec27"); + quiche::test::CompareCharArraysWithHexError( + "header protection mask", mask.data(), mask.size(), expected_mask.data(), + expected_mask.size()); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_256_gcm_decrypter.cc b/quiche/quic/core/crypto/aes_256_gcm_decrypter.cc new file mode 100644 index 0000000..f0cdc37 --- /dev/null +++ b/quiche/quic/core/crypto/aes_256_gcm_decrypter.cc
@@ -0,0 +1,37 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_256_gcm_decrypter.h" + +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "third_party/boringssl/src/include/openssl/tls1.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 32; +const size_t kNonceSize = 12; + +} // namespace + +Aes256GcmDecrypter::Aes256GcmDecrypter() + : AesBaseDecrypter(EVP_aead_aes_256_gcm, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ true) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +Aes256GcmDecrypter::~Aes256GcmDecrypter() {} + +uint32_t Aes256GcmDecrypter::cipher_id() const { + return TLS1_CK_AES_256_GCM_SHA384; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_256_gcm_decrypter.h b/quiche/quic/core/crypto/aes_256_gcm_decrypter.h new file mode 100644 index 0000000..dc4f8c0 --- /dev/null +++ b/quiche/quic/core/crypto/aes_256_gcm_decrypter.h
@@ -0,0 +1,36 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_256_GCM_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_256_GCM_DECRYPTER_H_ + +#include <cstdint> + +#include "quiche/quic/core/crypto/aes_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An Aes256GcmDecrypter is a QuicDecrypter that implements the +// AEAD_AES_256_GCM algorithm specified in RFC 5116 for use in IETF QUIC. +// +// It uses an authentication tag of 16 bytes (128 bits). It uses a 12 byte IV +// that is XOR'd with the packet number to compute the nonce. +class QUIC_EXPORT_PRIVATE Aes256GcmDecrypter : public AesBaseDecrypter { + public: + enum { + kAuthTagSize = 16, + }; + + Aes256GcmDecrypter(); + Aes256GcmDecrypter(const Aes256GcmDecrypter&) = delete; + Aes256GcmDecrypter& operator=(const Aes256GcmDecrypter&) = delete; + ~Aes256GcmDecrypter() override; + + uint32_t cipher_id() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_256_GCM_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_256_gcm_decrypter_test.cc b/quiche/quic/core/crypto/aes_256_gcm_decrypter_test.cc new file mode 100644 index 0000000..7c48c8c --- /dev/null +++ b/quiche/quic/core/crypto/aes_256_gcm_decrypter_test.cc
@@ -0,0 +1,297 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_256_gcm_decrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The AES GCM test vectors come from the file gcmDecrypt256.rsp +// downloaded from +// https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES#GCMVS +// on 2017-09-27. The test vectors in that file look like this: +// +// [Keylen = 256] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = f5a2b27c74355872eb3ef6c5feafaa740e6ae990d9d48c3bd9bb8235e589f010 +// IV = 58d2240f580a31c1d24948e9 +// CT = +// AAD = +// Tag = 15e051a5e4a5f5da6cea92e2ebee5bac +// PT = +// +// Count = 1 +// Key = e5a8123f2e2e007d4e379ba114a2fb66e6613f57c72d4e4f024964053028a831 +// IV = 51e43385bf533e168427e1ad +// CT = +// AAD = +// Tag = 38fe845c66e66bdd884c2aecafd280e6 +// FAIL +// +// ... +// +// The gcmDecrypt256.rsp file is huge (3.0 MB), so a few test vectors were +// selected for this unit test. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t pt_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + // Input: + const char* key; + const char* iv; + const char* ct; + const char* aad; + const char* tag; + + // Expected output: + const char* pt; // An empty string "" means decryption succeeded and + // the plaintext is zero-length. nullptr means decryption + // failed. +}; + +const TestGroupInfo test_group_info[] = { + {256, 96, 0, 0, 128}, {256, 96, 0, 128, 128}, {256, 96, 128, 0, 128}, + {256, 96, 408, 160, 128}, {256, 96, 408, 720, 128}, {256, 96, 104, 0, 128}, +}; + +const TestVector test_group_0[] = { + {"f5a2b27c74355872eb3ef6c5feafaa740e6ae990d9d48c3bd9bb8235e589f010", + "58d2240f580a31c1d24948e9", "", "", "15e051a5e4a5f5da6cea92e2ebee5bac", + ""}, + { + "e5a8123f2e2e007d4e379ba114a2fb66e6613f57c72d4e4f024964053028a831", + "51e43385bf533e168427e1ad", "", "", "38fe845c66e66bdd884c2aecafd280e6", + nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_1[] = { + {"6dfdafd6703c285c01f14fd10a6012862b2af950d4733abb403b2e745b26945d", + "3749d0b3d5bacb71be06ade6", "", "c0d249871992e70302ae008193d1e89f", + "4aa4cc69f84ee6ac16d9bfb4e05de500", ""}, + { + "2c392a5eb1a9c705371beda3a901c7c61dca4d93b4291de1dd0dd15ec11ffc45", + "0723fb84a08f4ea09841f32a", "", "140be561b6171eab942c486a94d33d43", + "aa0e1c9b57975bfc91aa137231977d2c", nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_2[] = { + {"4c8ebfe1444ec1b2d503c6986659af2c94fafe945f72c1e8486a5acfedb8a0f8", + "473360e0ad24889959858995", "d2c78110ac7e8f107c0df0570bd7c90c", "", + "c26a379b6d98ef2852ead8ce83a833a7", "7789b41cb3ee548814ca0b388c10b343"}, + {"3934f363fd9f771352c4c7a060682ed03c2864223a1573b3af997e2ababd60ab", + "efe2656d878c586e41c539c4", "e0de64302ac2d04048d65a87d2ad09fe", "", + "33cbd8d2fb8a3a03e30c1eb1b53c1d99", "697aff2d6b77e5ed6232770e400c1ead"}, + { + "c997768e2d14e3d38259667a6649079de77beb4543589771e5068e6cd7cd0b14", + "835090aed9552dbdd45277e2", "9f6607d68e22ccf21928db0986be126e", "", + "f32617f67c574fd9f44ef76ff880ab9f", nullptr // FAIL + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_3[] = { + { + "e9d381a9c413bee66175d5586a189836e5c20f5583535ab4d3f3e612dc21700e", + "23e81571da1c7821c681c7ca", + "a25f3f580306cd5065d22a6b7e9660110af7204bb77d370f7f34bee547feeff7b32a59" + "6fce29c9040e68b1589aad48da881990", + "6f39c9ae7b8e8a58a95f0dd8ea6a9087cbccdfd6", + "5b6dcd70eefb0892fab1539298b92a4b", + nullptr // FAIL + }, + {"6450d4501b1e6cfbe172c4c8570363e96b496591b842661c28c2f6c908379cad", + "7e4262035e0bf3d60e91668a", + "5a99b336fd3cfd82f10fb08f7045012415f0d9a06bb92dcf59c6f0dbe62d433671aacb8a1" + "c52ce7bbf6aea372bf51e2ba79406", + "f1c522f026e4c5d43851da516a1b78768ab18171", + "fe93b01636f7bb0458041f213e98de65", + "17449e236ef5858f6d891412495ead4607bfae2a2d735182a2a0242f9d52fc5345ef912db" + "e16f3bb4576fe3bcafe336dee6085"}, + {"90f2e71ccb1148979cb742efc8f921de95457d898c84ce28edeed701650d3a26", + "aba58ad60047ba553f6e4c98", + "3fc77a5fe9203d091c7916587c9763cf2e4d0d53ca20b078b851716f1dab4873fe342b7b3" + "01402f015d00263bf3f77c58a99d6", + "2abe465df6e5be47f05b92c9a93d76ae3611fac5", + "9cb3d04637048bc0bddef803ffbb56cf", + "1d21639640e11638a2769e3fab78778f84be3f4a8ce28dfd99cb2e75171e05ea8e94e30aa" + "78b54bb402b39d613616a8ed951dc"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_4[] = { + { + "e36aca93414b13f5313e76a7244588ee116551d1f34c32859166f2eb0ac1a9b7", + "e9e701b1ccef6bddd03391d8", + "5b059ac6733b6de0e8cf5b88b7301c02c993426f71bb12abf692e9deeacfac1ff1644c" + "87d4df130028f515f0feda636309a24d", + "6a08fe6e55a08f283cec4c4b37676e770f402af6102f548ad473ec6236da764f7076ff" + "d41bbd9611b439362d899682b7b0f839fc5a68d9df54afd1e2b3c4e7d072454ee27111" + "d52193d28b9c4f925d2a8b451675af39191a2cba", + "43c7c9c93cc265fc8e192000e0417b5b", + nullptr // FAIL + }, + {"5f72046245d3f4a0877e50a86554bfd57d1c5e073d1ed3b5451f6d0fc2a8507a", + "ea6f5b391e44b751b26bce6f", + "0e6e0b2114c40769c15958d965a14dcf50b680e0185a4409d77d894ca15b1e698dd83b353" + "6b18c05d8cd0873d1edce8150ecb5", + "9b3a68c941d42744673fb60fea49075eae77322e7e70e34502c115b6495ebfc796d629080" + "7653c6b53cd84281bd0311656d0013f44619d2748177e99e8f8347c989a7b59f9d8dcf00f" + "31db0684a4a83e037e8777bae55f799b0d", + "fdaaff86ceb937502cd9012d03585800", + "b0a881b751cc1eb0c912a4cf9bd971983707dbd2411725664503455c55db25cdb19bc669c" + "2654a3a8011de6bf7eff3f9f07834"}, + {"ab639bae205547607506522bd3cdca7861369e2b42ef175ff135f6ba435d5a8e", + "5fbb63eb44bd59fee458d8f6", + "9a34c62bed0972285503a32812877187a54dedbd55d2317fed89282bf1af4ba0b6bb9f9e1" + "6dd86da3b441deb7841262bc6bd63", + "1ef2b1768b805587935ffaf754a11bd2a305076d6374f1f5098b1284444b78f55408a786d" + "a37e1b7f1401c330d3585ef56f3e4d35eaaac92e1381d636477dc4f4beaf559735e902d6b" + "e58723257d4ac1ed9bd213de387f35f3c4", + "e0299e079bff46fd12e36d1c60e41434", + "e5a3ce804a8516cdd12122c091256b789076576040dbf3c55e8be3c016025896b8a72532b" + "fd51196cc82efca47aa0fd8e2e0dc"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_5[] = { + { + "8b37c4b8cf634704920059866ad96c49e9da502c63fca4a3a7a4dcec74cb0610", + "cb59344d2b06c4ae57cd0ea4", "66ab935c93555e786b775637a3", "", + "d8733acbb564d8afaa99d7ca2e2f92a9", nullptr // FAIL + }, + {"a71dac1377a3bf5d7fb1b5e36bee70d2e01de2a84a1c1009ba7448f7f26131dc", + "c5b60dda3f333b1146e9da7c", "43af49ec1ae3738a20755034d6", "", + "6f80b6ef2d8830a55eb63680a8dff9e0", "5b87141335f2becac1a559e05f"}, + {"dc1f64681014be221b00793bbcf5a5bc675b968eb7a3a3d5aa5978ef4fa45ecc", + "056ae9a1a69e38af603924fe", "33013a48d9ea0df2911d583271", "", + "5b8f9cc22303e979cd1524187e9f70fe", "2a7e05612191c8bce2f529dca9"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector* const test_group_array[] = { + test_group_0, test_group_1, test_group_2, + test_group_3, test_group_4, test_group_5, +}; + +} // namespace + +namespace quic { +namespace test { + +// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the plaintext. +QuicData* DecryptWithNonce(Aes256GcmDecrypter* decrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view ciphertext) { + decrypter->SetIV(nonce); + std::unique_ptr<char[]> output(new char[ciphertext.length()]); + size_t output_length = 0; + const bool success = + decrypter->DecryptPacket(0, associated_data, ciphertext, output.get(), + &output_length, ciphertext.length()); + if (!success) { + return nullptr; + } + return new QuicData(output.release(), output_length, true); +} + +class Aes256GcmDecrypterTest : public QuicTest {}; + +TEST_F(Aes256GcmDecrypterTest, Decrypt) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // If not present then decryption is expected to fail. + bool has_pt = test_vectors[j].pt; + + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[j].key); + std::string iv = absl::HexStringToBytes(test_vectors[j].iv); + std::string ct = absl::HexStringToBytes(test_vectors[j].ct); + std::string aad = absl::HexStringToBytes(test_vectors[j].aad); + std::string tag = absl::HexStringToBytes(test_vectors[j].tag); + std::string pt; + if (has_pt) { + pt = absl::HexStringToBytes(test_vectors[j].pt); + } + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.pt_len, ct.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + if (has_pt) { + EXPECT_EQ(test_info.pt_len, pt.length() * 8); + } + std::string ciphertext = ct + tag; + + Aes256GcmDecrypter decrypter; + ASSERT_TRUE(decrypter.SetKey(key)); + + std::unique_ptr<QuicData> decrypted(DecryptWithNonce( + &decrypter, iv, + // This deliberately tests that the decrypter can + // handle an AAD that is set to nullptr, as opposed + // to a zero-length, non-nullptr pointer. + aad.length() ? aad : absl::string_view(), ciphertext)); + if (!decrypted) { + EXPECT_FALSE(has_pt); + continue; + } + EXPECT_TRUE(has_pt); + + ASSERT_EQ(pt.length(), decrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "plaintext", decrypted->data(), pt.length(), pt.data(), pt.length()); + } + } +} + +TEST_F(Aes256GcmDecrypterTest, GenerateHeaderProtectionMask) { + Aes256GcmDecrypter decrypter; + std::string key = absl::HexStringToBytes( + "ed23ecbf54d426def5c52c3dcfc84434e62e57781d3125bb21ed91b7d3e07788"); + std::string sample = + absl::HexStringToBytes("4d190c474be2b8babafb49ec4e38e810"); + QuicDataReader sample_reader(sample.data(), sample.size()); + ASSERT_TRUE(decrypter.SetHeaderProtectionKey(key)); + std::string mask = decrypter.GenerateHeaderProtectionMask(&sample_reader); + std::string expected_mask = + absl::HexStringToBytes("db9ed4e6ccd033af2eae01407199c56e"); + quiche::test::CompareCharArraysWithHexError( + "header protection mask", mask.data(), mask.size(), expected_mask.data(), + expected_mask.size()); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_256_gcm_encrypter.cc b/quiche/quic/core/crypto/aes_256_gcm_encrypter.cc new file mode 100644 index 0000000..fce2207 --- /dev/null +++ b/quiche/quic/core/crypto/aes_256_gcm_encrypter.cc
@@ -0,0 +1,30 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_256_gcm_encrypter.h" + +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 32; +const size_t kNonceSize = 12; + +} // namespace + +Aes256GcmEncrypter::Aes256GcmEncrypter() + : AesBaseEncrypter(EVP_aead_aes_256_gcm, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ true) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +Aes256GcmEncrypter::~Aes256GcmEncrypter() {} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_256_gcm_encrypter.h b/quiche/quic/core/crypto/aes_256_gcm_encrypter.h new file mode 100644 index 0000000..9ba47f1 --- /dev/null +++ b/quiche/quic/core/crypto/aes_256_gcm_encrypter.h
@@ -0,0 +1,32 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_256_GCM_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_256_GCM_ENCRYPTER_H_ + +#include "quiche/quic/core/crypto/aes_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An Aes256GcmEncrypter is a QuicEncrypter that implements the +// AEAD_AES_256_GCM algorithm specified in RFC 5116 for use in IETF QUIC. +// +// It uses an authentication tag of 16 bytes (128 bits). It uses a 12 byte IV +// that is XOR'd with the packet number to compute the nonce. +class QUIC_EXPORT_PRIVATE Aes256GcmEncrypter : public AesBaseEncrypter { + public: + enum { + kAuthTagSize = 16, + }; + + Aes256GcmEncrypter(); + Aes256GcmEncrypter(const Aes256GcmEncrypter&) = delete; + Aes256GcmEncrypter& operator=(const Aes256GcmEncrypter&) = delete; + ~Aes256GcmEncrypter() override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_256_GCM_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_256_gcm_encrypter_test.cc b/quiche/quic/core/crypto/aes_256_gcm_encrypter_test.cc new file mode 100644 index 0000000..6389fdb --- /dev/null +++ b/quiche/quic/core/crypto/aes_256_gcm_encrypter_test.cc
@@ -0,0 +1,259 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/aes_256_gcm_encrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The AES GCM test vectors come from the file gcmEncryptExtIV256.rsp +// downloaded from +// https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES#GCMVS +// on 2017-09-27. The test vectors in that file look like this: +// +// [Keylen = 256] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4 +// IV = 516c33929df5a3284ff463d7 +// PT = +// AAD = +// CT = +// Tag = bdc1ac884d332457a1d2664f168c76f0 +// +// Count = 1 +// Key = 5fe0861cdc2690ce69b3658c7f26f8458eec1c9243c5ba0845305d897e96ca0f +// IV = 770ac1a5a3d476d5d96944a1 +// PT = +// AAD = +// CT = +// Tag = 196d691e1047093ca4b3d2ef4baba216 +// +// ... +// +// The gcmEncryptExtIV256.rsp file is huge (3.2 MB), so a few test vectors were +// selected for this unit test. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t pt_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + const char* key; + const char* iv; + const char* pt; + const char* aad; + const char* ct; + const char* tag; +}; + +const TestGroupInfo test_group_info[] = { + {256, 96, 0, 0, 128}, {256, 96, 0, 128, 128}, {256, 96, 128, 0, 128}, + {256, 96, 408, 160, 128}, {256, 96, 408, 720, 128}, {256, 96, 104, 0, 128}, +}; + +const TestVector test_group_0[] = { + {"b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4", + "516c33929df5a3284ff463d7", "", "", "", + "bdc1ac884d332457a1d2664f168c76f0"}, + {"5fe0861cdc2690ce69b3658c7f26f8458eec1c9243c5ba0845305d897e96ca0f", + "770ac1a5a3d476d5d96944a1", "", "", "", + "196d691e1047093ca4b3d2ef4baba216"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_1[] = { + {"78dc4e0aaf52d935c3c01eea57428f00ca1fd475f5da86a49c8dd73d68c8e223", + "d79cf22d504cc793c3fb6c8a", "", "b96baa8c1c75a671bfb2d08d06be5f36", "", + "3e5d486aa2e30b22e040b85723a06e76"}, + {"4457ff33683cca6ca493878bdc00373893a9763412eef8cddb54f91318e0da88", + "699d1f29d7b8c55300bb1fd2", "", "6749daeea367d0e9809e2dc2f309e6e3", "", + "d60c74d2517fde4a74e0cd4709ed43a9"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_2[] = { + {"31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22", + "0d18e06c7c725ac9e362e1ce", "2db5168e932556f8089a0622981d017d", "", + "fa4362189661d163fcd6a56d8bf0405a", "d636ac1bbedd5cc3ee727dc2ab4a9489"}, + {"460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99", + "8a4a16b9e210eb68bcb6f58d", "99e4e926ffe927f691893fb79a96b067", "", + "133fc15751621b5f325c7ff71ce08324", "ec4e87e0cf74a13618d0b68636ba9fa7"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_3[] = { + {"24501ad384e473963d476edcfe08205237acfd49b5b8f33857f8114e863fec7f", + "9ff18563b978ec281b3f2794", + "27f348f9cdc0c5bd5e66b1ccb63ad920ff2219d14e8d631b3872265cf117ee86757accb15" + "8bd9abb3868fdc0d0b074b5f01b2c", + "adb5ec720ccf9898500028bf34afccbcaca126ef", + "eb7cb754c824e8d96f7c6d9b76c7d26fb874ffbf1d65c6f64a698d839b0b06145dae82057" + "ad55994cf59ad7f67c0fa5e85fab8", + "bc95c532fecc594c36d1550286a7a3f0"}, + {"fb43f5ab4a1738a30c1e053d484a94254125d55dccee1ad67c368bc1a985d235", + "9fbb5f8252db0bca21f1c230", + "34b797bb82250e23c5e796db2c37e488b3b99d1b981cea5e5b0c61a0b39adb6bd6ef1f507" + "22e2e4f81115cfcf53f842e2a6c08", + "98f8ae1735c39f732e2cbee1156dabeb854ec7a2", + "871cd53d95a8b806bd4821e6c4456204d27fd704ba3d07ce25872dc604ea5c5ea13322186" + "b7489db4fa060c1fd4159692612c8", + "07b48e4a32fac47e115d7ac7445d8330"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_4[] = { + {"148579a3cbca86d5520d66c0ec71ca5f7e41ba78e56dc6eebd566fed547fe691", + "b08a5ea1927499c6ecbfd4e0", + "9d0b15fdf1bd595f91f8b3abc0f7dec927dfd4799935a1795d9ce00c9b879434420fe42c2" + "75a7cd7b39d638fb81ca52b49dc41", + "e4f963f015ffbb99ee3349bbaf7e8e8e6c2a71c230a48f9d59860a29091d2747e01a5ca57" + "2347e247d25f56ba7ae8e05cde2be3c97931292c02370208ecd097ef692687fecf2f419d3" + "200162a6480a57dad408a0dfeb492e2c5d", + "2097e372950a5e9383c675e89eea1c314f999159f5611344b298cda45e62843716f215f82" + "ee663919c64002a5c198d7878fd3f", + "adbecdb0d5c2224d804d2886ff9a5760"}, + {"e49af19182faef0ebeeba9f2d3be044e77b1212358366e4ef59e008aebcd9788", + "e7f37d79a6a487a5a703edbb", + "461cd0caf7427a3d44408d825ed719237272ecd503b9094d1f62c97d63ed83a0b50bdc804" + "ffdd7991da7a5b6dcf48d4bcd2cbc", + "19a9a1cfc647346781bef51ed9070d05f99a0e0192a223c5cd2522dbdf97d9739dd39fb17" + "8ade3339e68774b058aa03e9a20a9a205bc05f32381df4d63396ef691fefd5a71b49a2ad8" + "2d5ea428778ca47ee1398792762413cff4", + "32ca3588e3e56eb4c8301b009d8b84b8a900b2b88ca3c21944205e9dd7311757b51394ae9" + "0d8bb3807b471677614f4198af909", + "3e403d035c71d88f1be1a256c89ba6ad"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector test_group_5[] = { + {"82c4f12eeec3b2d3d157b0f992d292b237478d2cecc1d5f161389b97f999057a", + "7b40b20f5f397177990ef2d1", "982a296ee1cd7086afad976945", "", + "ec8e05a0471d6b43a59ca5335f", "113ddeafc62373cac2f5951bb9165249"}, + {"db4340af2f835a6c6d7ea0ca9d83ca81ba02c29b7410f221cb6071114e393240", + "40e438357dd80a85cac3349e", "8ddb3397bd42853193cb0f80c9", "", + "b694118c85c41abf69e229cb0f", "c07f1b8aafbd152f697eb67f2a85fe45"}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +const TestVector* const test_group_array[] = { + test_group_0, test_group_1, test_group_2, + test_group_3, test_group_4, test_group_5, +}; + +} // namespace + +namespace quic { +namespace test { + +// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the ciphertext. +QuicData* EncryptWithNonce(Aes256GcmEncrypter* encrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext) { + size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length()); + std::unique_ptr<char[]> ciphertext(new char[ciphertext_size]); + + if (!encrypter->Encrypt(nonce, associated_data, plaintext, + reinterpret_cast<unsigned char*>(ciphertext.get()))) { + return nullptr; + } + + return new QuicData(ciphertext.release(), ciphertext_size, true); +} + +class Aes256GcmEncrypterTest : public QuicTest {}; + +TEST_F(Aes256GcmEncrypterTest, Encrypt) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[j].key); + std::string iv = absl::HexStringToBytes(test_vectors[j].iv); + std::string pt = absl::HexStringToBytes(test_vectors[j].pt); + std::string aad = absl::HexStringToBytes(test_vectors[j].aad); + std::string ct = absl::HexStringToBytes(test_vectors[j].ct); + std::string tag = absl::HexStringToBytes(test_vectors[j].tag); + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.pt_len, pt.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.pt_len, ct.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + + Aes256GcmEncrypter encrypter; + ASSERT_TRUE(encrypter.SetKey(key)); + std::unique_ptr<QuicData> encrypted( + EncryptWithNonce(&encrypter, iv, + // This deliberately tests that the encrypter can + // handle an AAD that is set to nullptr, as opposed + // to a zero-length, non-nullptr pointer. + aad.length() ? aad : absl::string_view(), pt)); + ASSERT_TRUE(encrypted.get()); + + ASSERT_EQ(ct.length() + tag.length(), encrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "ciphertext", encrypted->data(), ct.length(), ct.data(), ct.length()); + quiche::test::CompareCharArraysWithHexError( + "authentication tag", encrypted->data() + ct.length(), tag.length(), + tag.data(), tag.length()); + } + } +} + +TEST_F(Aes256GcmEncrypterTest, GetMaxPlaintextSize) { + Aes256GcmEncrypter encrypter; + EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1016)); + EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(116)); + EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(26)); +} + +TEST_F(Aes256GcmEncrypterTest, GetCiphertextSize) { + Aes256GcmEncrypter encrypter; + EXPECT_EQ(1016u, encrypter.GetCiphertextSize(1000)); + EXPECT_EQ(116u, encrypter.GetCiphertextSize(100)); + EXPECT_EQ(26u, encrypter.GetCiphertextSize(10)); +} + +TEST_F(Aes256GcmEncrypterTest, GenerateHeaderProtectionMask) { + Aes256GcmEncrypter encrypter; + std::string key = absl::HexStringToBytes( + "ed23ecbf54d426def5c52c3dcfc84434e62e57781d3125bb21ed91b7d3e07788"); + std::string sample = + absl::HexStringToBytes("4d190c474be2b8babafb49ec4e38e810"); + ASSERT_TRUE(encrypter.SetHeaderProtectionKey(key)); + std::string mask = encrypter.GenerateHeaderProtectionMask(sample); + std::string expected_mask = + absl::HexStringToBytes("db9ed4e6ccd033af2eae01407199c56e"); + quiche::test::CompareCharArraysWithHexError( + "header protection mask", mask.data(), mask.size(), expected_mask.data(), + expected_mask.size()); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_base_decrypter.cc b/quiche/quic/core/crypto/aes_base_decrypter.cc new file mode 100644 index 0000000..fb21ffe --- /dev/null +++ b/quiche/quic/core/crypto/aes_base_decrypter.cc
@@ -0,0 +1,52 @@ +// 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 "quiche/quic/core/crypto/aes_base_decrypter.h" + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aes.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +bool AesBaseDecrypter::SetHeaderProtectionKey(absl::string_view key) { + if (key.size() != GetKeySize()) { + QUIC_BUG(quic_bug_10649_1) << "Invalid key size for header protection"; + return false; + } + if (AES_set_encrypt_key(reinterpret_cast<const uint8_t*>(key.data()), + key.size() * 8, &pne_key_) != 0) { + QUIC_BUG(quic_bug_10649_2) << "Unexpected failure of AES_set_encrypt_key"; + return false; + } + return true; +} + +std::string AesBaseDecrypter::GenerateHeaderProtectionMask( + QuicDataReader* sample_reader) { + absl::string_view sample; + if (!sample_reader->ReadStringPiece(&sample, AES_BLOCK_SIZE)) { + return std::string(); + } + std::string out(AES_BLOCK_SIZE, 0); + AES_encrypt(reinterpret_cast<const uint8_t*>(sample.data()), + reinterpret_cast<uint8_t*>(const_cast<char*>(out.data())), + &pne_key_); + return out; +} + +QuicPacketCount AesBaseDecrypter::GetIntegrityLimit() const { + // For AEAD_AES_128_GCM ... endpoints that do not attempt to remove + // protection from packets larger than 2^11 bytes can attempt to remove + // protection from at most 2^57 packets. + // For AEAD_AES_256_GCM [the limit] is substantially larger than the limit for + // AEAD_AES_128_GCM. However, this document recommends that the same limit be + // applied to both functions as either limit is acceptably large. + // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-integrity-limit + static_assert(kMaxIncomingPacketSize <= 2048, + "This key limit requires limits on decryption payload sizes"); + return 144115188075855872U; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_base_decrypter.h b/quiche/quic/core/crypto/aes_base_decrypter.h new file mode 100644 index 0000000..1be0f7e --- /dev/null +++ b/quiche/quic/core/crypto/aes_base_decrypter.h
@@ -0,0 +1,33 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_BASE_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_BASE_DECRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aes.h" +#include "quiche/quic/core/crypto/aead_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE AesBaseDecrypter : public AeadBaseDecrypter { + public: + using AeadBaseDecrypter::AeadBaseDecrypter; + + bool SetHeaderProtectionKey(absl::string_view key) override; + std::string GenerateHeaderProtectionMask( + QuicDataReader* sample_reader) override; + QuicPacketCount GetIntegrityLimit() const override; + + private: + // The key used for packet number encryption. + AES_KEY pne_key_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_BASE_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/aes_base_encrypter.cc b/quiche/quic/core/crypto/aes_base_encrypter.cc new file mode 100644 index 0000000..74298ed --- /dev/null +++ b/quiche/quic/core/crypto/aes_base_encrypter.cc
@@ -0,0 +1,48 @@ +// 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 "quiche/quic/core/crypto/aes_base_encrypter.h" + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aes.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +bool AesBaseEncrypter::SetHeaderProtectionKey(absl::string_view key) { + if (key.size() != GetKeySize()) { + QUIC_BUG(quic_bug_10726_1) + << "Invalid key size for header protection: " << key.size(); + return false; + } + if (AES_set_encrypt_key(reinterpret_cast<const uint8_t*>(key.data()), + key.size() * 8, &pne_key_) != 0) { + QUIC_BUG(quic_bug_10726_2) << "Unexpected failure of AES_set_encrypt_key"; + return false; + } + return true; +} + +std::string AesBaseEncrypter::GenerateHeaderProtectionMask( + absl::string_view sample) { + if (sample.size() != AES_BLOCK_SIZE) { + return std::string(); + } + std::string out(AES_BLOCK_SIZE, 0); + AES_encrypt(reinterpret_cast<const uint8_t*>(sample.data()), + reinterpret_cast<uint8_t*>(const_cast<char*>(out.data())), + &pne_key_); + return out; +} + +QuicPacketCount AesBaseEncrypter::GetConfidentialityLimit() const { + // For AEAD_AES_128_GCM and AEAD_AES_256_GCM ... endpoints that do not send + // packets larger than 2^11 bytes cannot protect more than 2^28 packets. + // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-confidentiality-limit + static_assert(kMaxOutgoingPacketSize <= 2048, + "This key limit requires limits on encryption payload sizes"); + return 268435456U; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/aes_base_encrypter.h b/quiche/quic/core/crypto/aes_base_encrypter.h new file mode 100644 index 0000000..b929cdd --- /dev/null +++ b/quiche/quic/core/crypto/aes_base_encrypter.h
@@ -0,0 +1,32 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_AES_BASE_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_AES_BASE_ENCRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aes.h" +#include "quiche/quic/core/crypto/aead_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE AesBaseEncrypter : public AeadBaseEncrypter { + public: + using AeadBaseEncrypter::AeadBaseEncrypter; + + bool SetHeaderProtectionKey(absl::string_view key) override; + std::string GenerateHeaderProtectionMask(absl::string_view sample) override; + QuicPacketCount GetConfidentialityLimit() const override; + + private: + // The key used for packet number encryption. + AES_KEY pne_key_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_AES_BASE_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/boring_utils.h b/quiche/quic/core/crypto/boring_utils.h new file mode 100644 index 0000000..4a45cf4 --- /dev/null +++ b/quiche/quic/core/crypto/boring_utils.h
@@ -0,0 +1,34 @@ +// Copyright 2020 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_BORING_UTILS_H_ +#define QUICHE_QUIC_CORE_CRYPTO_BORING_UTILS_H_ + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +inline QUIC_EXPORT_PRIVATE absl::string_view CbsToStringPiece(CBS cbs) { + return absl::string_view(reinterpret_cast<const char*>(CBS_data(&cbs)), + CBS_len(&cbs)); +} + +inline QUIC_EXPORT_PRIVATE CBS StringPieceToCbs(absl::string_view piece) { + CBS result; + CBS_init(&result, reinterpret_cast<const uint8_t*>(piece.data()), + piece.size()); + return result; +} + +inline QUIC_EXPORT_PRIVATE bool AddStringToCbb(CBB* cbb, + absl::string_view piece) { + return 1 == CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(piece.data()), + piece.size()); +} + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_BORING_UTILS_H_
diff --git a/quiche/quic/core/crypto/cert_compressor.cc b/quiche/quic/core/crypto/cert_compressor.cc new file mode 100644 index 0000000..4765643 --- /dev/null +++ b/quiche/quic/core/crypto/cert_compressor.cc
@@ -0,0 +1,599 @@ +// 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 "quiche/quic/core/crypto/cert_compressor.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" +#include "third_party/zlib/zlib.h" + +namespace quic { + +namespace { + +// kCommonCertSubstrings contains ~1500 bytes of common certificate substrings +// in order to help zlib. This was generated via a fairly dumb algorithm from +// the Alexa Top 5000 set - we could probably do better. +static const unsigned char kCommonCertSubstrings[] = { + 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, + 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, + 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, + 0x5f, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, + 0x06, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6d, 0x01, 0x07, + 0x17, 0x01, 0x30, 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, + 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x53, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x34, + 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, + 0x32, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x2d, 0x61, 0x69, 0x61, 0x2e, + 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x45, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, + 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x2e, 0x63, 0x65, + 0x72, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4a, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, + 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x7b, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x0e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, + 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd2, + 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x2e, + 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, + 0x04, 0x14, 0xb4, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, + 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x30, 0x0b, 0x06, 0x03, + 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, + 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, + 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, + 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, + 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, + 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x27, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, + 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, + 0x04, 0x05, 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37, + 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, + 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x0c, + 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, + 0x30, 0x1d, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, + 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x03, 0x02, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, + 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, + 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, + 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, + 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x67, 0x64, 0x73, 0x31, 0x2d, 0x32, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, + 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, + 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x70, 0x73, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, + 0x0d, 0x31, 0x33, 0x30, 0x35, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x73, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x02, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, + 0x3d, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, + 0xf8, 0x45, 0x01, 0x07, 0x17, 0x06, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x53, 0x31, 0x17, + 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, 0x72, + 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, + 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, + 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, 0x39, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, 0x73, + 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, + 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x31, 0x10, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x31, 0x13, 0x30, 0x11, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x47, 0x31, 0x13, 0x30, 0x11, + 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, + 0x03, 0x13, 0x02, 0x55, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x14, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0f, 0x13, 0x14, 0x50, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x12, 0x31, 0x21, 0x30, + 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x20, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x31, 0x14, 0x31, 0x31, + 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x53, 0x65, 0x65, + 0x20, 0x77, 0x77, 0x77, 0x2e, 0x72, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, + 0x75, 0x72, 0x65, 0x2e, 0x67, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, + 0x69, 0x67, 0x6e, 0x31, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, + 0x2e, 0x63, 0x72, 0x6c, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, + 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x45, 0x63, 0x72, + 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x64, 0x31, 0x1a, + 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x45, 0x56, 0x49, 0x6e, 0x74, 0x6c, 0x2d, 0x63, 0x63, 0x72, + 0x74, 0x2e, 0x67, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x69, 0x63, 0x65, 0x72, + 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x6f, 0x63, 0x73, 0x70, 0x2e, + 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, + 0x30, 0x39, 0x72, 0x61, 0x70, 0x69, 0x64, 0x73, 0x73, 0x6c, 0x2e, 0x63, + 0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, + 0x79, 0x2f, 0x30, 0x81, 0x80, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, + 0x07, 0x01, 0x01, 0x04, 0x74, 0x30, 0x72, 0x30, 0x24, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, + 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x4a, 0x06, + 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, + 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72, + 0x74, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, + 0x80, 0x14, 0xfd, 0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee, + 0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x27, + 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x86, 0x30, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, +}; + +// CertEntry represents a certificate in compressed form. Each entry is one of +// the three types enumerated in |Type|. +struct CertEntry { + public: + enum Type { + // Type 0 is reserved to mean "end of list" in the wire format. + + // COMPRESSED means that the certificate is included in the trailing zlib + // data. + COMPRESSED = 1, + // CACHED means that the certificate is already known to the peer and will + // be replaced by its 64-bit hash (in |hash|). + CACHED = 2, + }; + + Type type; + uint64_t hash; + uint64_t set_hash; + uint32_t index; +}; + +// MatchCerts returns a vector of CertEntries describing how to most +// efficiently represent |certs| to a peer who has cached the certificates +// with the 64-bit, FNV-1a hashes in |client_cached_cert_hashes|. +std::vector<CertEntry> MatchCerts(const std::vector<std::string>& certs, + absl::string_view client_cached_cert_hashes) { + std::vector<CertEntry> entries; + entries.reserve(certs.size()); + + const bool cached_valid = + client_cached_cert_hashes.size() % sizeof(uint64_t) == 0 && + !client_cached_cert_hashes.empty(); + + for (auto i = certs.begin(); i != certs.end(); ++i) { + CertEntry entry; + + if (cached_valid) { + bool cached = false; + + uint64_t hash = QuicUtils::FNV1a_64_Hash(*i); + // This assumes that the machine is little-endian. + for (size_t j = 0; j < client_cached_cert_hashes.size(); + j += sizeof(uint64_t)) { + uint64_t cached_hash; + memcpy(&cached_hash, client_cached_cert_hashes.data() + j, + sizeof(uint64_t)); + if (hash != cached_hash) { + continue; + } + + entry.type = CertEntry::CACHED; + entry.hash = hash; + entries.push_back(entry); + cached = true; + break; + } + + if (cached) { + continue; + } + } + + entry.type = CertEntry::COMPRESSED; + entries.push_back(entry); + } + + return entries; +} + +// CertEntriesSize returns the size, in bytes, of the serialised form of +// |entries|. +size_t CertEntriesSize(const std::vector<CertEntry>& entries) { + size_t entries_size = 0; + + for (auto i = entries.begin(); i != entries.end(); ++i) { + entries_size++; + switch (i->type) { + case CertEntry::COMPRESSED: + break; + case CertEntry::CACHED: + entries_size += sizeof(uint64_t); + break; + } + } + + entries_size++; // for end marker + + return entries_size; +} + +// SerializeCertEntries serialises |entries| to |out|, which must have enough +// space to contain them. +void SerializeCertEntries(uint8_t* out, const std::vector<CertEntry>& entries) { + for (auto i = entries.begin(); i != entries.end(); ++i) { + *out++ = static_cast<uint8_t>(i->type); + switch (i->type) { + case CertEntry::COMPRESSED: + break; + case CertEntry::CACHED: + memcpy(out, &i->hash, sizeof(i->hash)); + out += sizeof(uint64_t); + break; + } + } + + *out++ = 0; // end marker +} + +// ZlibDictForEntries returns a string that contains the zlib pre-shared +// dictionary to use in order to decompress a zlib block following |entries|. +// |certs| is one-to-one with |entries| and contains the certificates for those +// entries that are CACHED. +std::string ZlibDictForEntries(const std::vector<CertEntry>& entries, + const std::vector<std::string>& certs) { + std::string zlib_dict; + + // The dictionary starts with the cached certs in reverse order. + size_t zlib_dict_size = 0; + for (size_t i = certs.size() - 1; i < certs.size(); i--) { + if (entries[i].type != CertEntry::COMPRESSED) { + zlib_dict_size += certs[i].size(); + } + } + + // At the end of the dictionary is a block of common certificate substrings. + zlib_dict_size += sizeof(kCommonCertSubstrings); + + zlib_dict.reserve(zlib_dict_size); + + for (size_t i = certs.size() - 1; i < certs.size(); i--) { + if (entries[i].type != CertEntry::COMPRESSED) { + zlib_dict += certs[i]; + } + } + + zlib_dict += std::string(reinterpret_cast<const char*>(kCommonCertSubstrings), + sizeof(kCommonCertSubstrings)); + + QUICHE_DCHECK_EQ(zlib_dict.size(), zlib_dict_size); + + return zlib_dict; +} + +// HashCerts returns the FNV-1a hashes of |certs|. +std::vector<uint64_t> HashCerts(const std::vector<std::string>& certs) { + std::vector<uint64_t> ret; + ret.reserve(certs.size()); + + for (auto i = certs.begin(); i != certs.end(); ++i) { + ret.push_back(QuicUtils::FNV1a_64_Hash(*i)); + } + + return ret; +} + +// ParseEntries parses the serialised form of a vector of CertEntries from +// |in_out| and writes them to |out_entries|. CACHED entries are resolved using +// |cached_certs| and written to |out_certs|. |in_out| is updated to contain +// the trailing data. +bool ParseEntries(absl::string_view* in_out, + const std::vector<std::string>& cached_certs, + std::vector<CertEntry>* out_entries, + std::vector<std::string>* out_certs) { + absl::string_view in = *in_out; + std::vector<uint64_t> cached_hashes; + + out_entries->clear(); + out_certs->clear(); + + for (;;) { + if (in.empty()) { + return false; + } + CertEntry entry; + const uint8_t type_byte = in[0]; + in.remove_prefix(1); + + if (type_byte == 0) { + break; + } + + entry.type = static_cast<CertEntry::Type>(type_byte); + + switch (entry.type) { + case CertEntry::COMPRESSED: + out_certs->push_back(std::string()); + break; + case CertEntry::CACHED: { + if (in.size() < sizeof(uint64_t)) { + return false; + } + memcpy(&entry.hash, in.data(), sizeof(uint64_t)); + in.remove_prefix(sizeof(uint64_t)); + + if (cached_hashes.size() != cached_certs.size()) { + cached_hashes = HashCerts(cached_certs); + } + bool found = false; + for (size_t i = 0; i < cached_hashes.size(); i++) { + if (cached_hashes[i] == entry.hash) { + out_certs->push_back(cached_certs[i]); + found = true; + break; + } + } + if (!found) { + return false; + } + break; + } + + default: + return false; + } + out_entries->push_back(entry); + } + + *in_out = in; + return true; +} + +// ScopedZLib deals with the automatic destruction of a zlib context. +class ScopedZLib { + public: + enum Type { + INFLATE, + DEFLATE, + }; + + explicit ScopedZLib(Type type) : z_(nullptr), type_(type) {} + + void reset(z_stream* z) { + Clear(); + z_ = z; + } + + ~ScopedZLib() { Clear(); } + + private: + void Clear() { + if (!z_) { + return; + } + + if (type_ == DEFLATE) { + deflateEnd(z_); + } else { + inflateEnd(z_); + } + z_ = nullptr; + } + + z_stream* z_; + const Type type_; +}; + +} // anonymous namespace + +// static +std::string CertCompressor::CompressChain( + const std::vector<std::string>& certs, + absl::string_view client_cached_cert_hashes) { + const std::vector<CertEntry> entries = + MatchCerts(certs, client_cached_cert_hashes); + QUICHE_DCHECK_EQ(entries.size(), certs.size()); + + size_t uncompressed_size = 0; + for (size_t i = 0; i < entries.size(); i++) { + if (entries[i].type == CertEntry::COMPRESSED) { + uncompressed_size += 4 /* uint32_t length */ + certs[i].size(); + } + } + + size_t compressed_size = 0; + z_stream z; + ScopedZLib scoped_z(ScopedZLib::DEFLATE); + + if (uncompressed_size > 0) { + memset(&z, 0, sizeof(z)); + int rv = deflateInit(&z, Z_DEFAULT_COMPRESSION); + QUICHE_DCHECK_EQ(Z_OK, rv); + if (rv != Z_OK) { + return ""; + } + scoped_z.reset(&z); + + std::string zlib_dict = ZlibDictForEntries(entries, certs); + + rv = deflateSetDictionary( + &z, reinterpret_cast<const uint8_t*>(&zlib_dict[0]), zlib_dict.size()); + QUICHE_DCHECK_EQ(Z_OK, rv); + if (rv != Z_OK) { + return ""; + } + + compressed_size = deflateBound(&z, uncompressed_size); + } + + const size_t entries_size = CertEntriesSize(entries); + + std::string result; + result.resize(entries_size + (uncompressed_size > 0 ? 4 : 0) + + compressed_size); + + uint8_t* j = reinterpret_cast<uint8_t*>(&result[0]); + SerializeCertEntries(j, entries); + j += entries_size; + + if (uncompressed_size == 0) { + return result; + } + + uint32_t uncompressed_size_32 = uncompressed_size; + memcpy(j, &uncompressed_size_32, sizeof(uint32_t)); + j += sizeof(uint32_t); + + int rv; + + z.next_out = j; + z.avail_out = compressed_size; + + for (size_t i = 0; i < certs.size(); i++) { + if (entries[i].type != CertEntry::COMPRESSED) { + continue; + } + + uint32_t length32 = certs[i].size(); + z.next_in = reinterpret_cast<uint8_t*>(&length32); + z.avail_in = sizeof(length32); + rv = deflate(&z, Z_NO_FLUSH); + QUICHE_DCHECK_EQ(Z_OK, rv); + QUICHE_DCHECK_EQ(0u, z.avail_in); + if (rv != Z_OK || z.avail_in) { + return ""; + } + + z.next_in = + const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(certs[i].data())); + z.avail_in = certs[i].size(); + rv = deflate(&z, Z_NO_FLUSH); + QUICHE_DCHECK_EQ(Z_OK, rv); + QUICHE_DCHECK_EQ(0u, z.avail_in); + if (rv != Z_OK || z.avail_in) { + return ""; + } + } + + z.avail_in = 0; + rv = deflate(&z, Z_FINISH); + QUICHE_DCHECK_EQ(Z_STREAM_END, rv); + if (rv != Z_STREAM_END) { + return ""; + } + + result.resize(result.size() - z.avail_out); + return result; +} + +// static +bool CertCompressor::DecompressChain( + absl::string_view in, + const std::vector<std::string>& cached_certs, + std::vector<std::string>* out_certs) { + std::vector<CertEntry> entries; + if (!ParseEntries(&in, cached_certs, &entries, out_certs)) { + return false; + } + QUICHE_DCHECK_EQ(entries.size(), out_certs->size()); + + std::unique_ptr<uint8_t[]> uncompressed_data; + absl::string_view uncompressed; + + if (!in.empty()) { + if (in.size() < sizeof(uint32_t)) { + return false; + } + + uint32_t uncompressed_size; + memcpy(&uncompressed_size, in.data(), sizeof(uncompressed_size)); + in.remove_prefix(sizeof(uint32_t)); + + if (uncompressed_size > 128 * 1024) { + return false; + } + + uncompressed_data = std::make_unique<uint8_t[]>(uncompressed_size); + z_stream z; + ScopedZLib scoped_z(ScopedZLib::INFLATE); + + memset(&z, 0, sizeof(z)); + z.next_out = uncompressed_data.get(); + z.avail_out = uncompressed_size; + z.next_in = + const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(in.data())); + z.avail_in = in.size(); + + if (Z_OK != inflateInit(&z)) { + return false; + } + scoped_z.reset(&z); + + int rv = inflate(&z, Z_FINISH); + if (rv == Z_NEED_DICT) { + std::string zlib_dict = ZlibDictForEntries(entries, *out_certs); + const uint8_t* dict = reinterpret_cast<const uint8_t*>(zlib_dict.data()); + if (Z_OK != inflateSetDictionary(&z, dict, zlib_dict.size())) { + return false; + } + rv = inflate(&z, Z_FINISH); + } + + if (Z_STREAM_END != rv || z.avail_out > 0 || z.avail_in > 0) { + return false; + } + + uncompressed = absl::string_view( + reinterpret_cast<char*>(uncompressed_data.get()), uncompressed_size); + } + + for (size_t i = 0; i < entries.size(); i++) { + switch (entries[i].type) { + case CertEntry::COMPRESSED: + if (uncompressed.size() < sizeof(uint32_t)) { + return false; + } + uint32_t cert_len; + memcpy(&cert_len, uncompressed.data(), sizeof(cert_len)); + uncompressed.remove_prefix(sizeof(uint32_t)); + if (uncompressed.size() < cert_len) { + return false; + } + (*out_certs)[i] = std::string(uncompressed.substr(0, cert_len)); + uncompressed.remove_prefix(cert_len); + break; + case CertEntry::CACHED: + break; + } + } + + if (!uncompressed.empty()) { + return false; + } + + return true; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/cert_compressor.h b/quiche/quic/core/crypto/cert_compressor.h new file mode 100644 index 0000000..9509ccd --- /dev/null +++ b/quiche/quic/core/crypto/cert_compressor.h
@@ -0,0 +1,45 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CERT_COMPRESSOR_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CERT_COMPRESSOR_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// CertCompressor provides functions for compressing and decompressing +// certificate chains using two techniquies: +// 1) The peer may provide a list of a 64-bit, FNV-1a hashes of certificates +// that they already have. In the event that one of them is to be +// compressed, it can be replaced with just the hash. +// 2) Otherwise the certificates are compressed with zlib using a pre-shared +// dictionary that consists of the certificates handled with the above +// methods and a small chunk of common substrings. +class QUIC_EXPORT_PRIVATE CertCompressor { + public: + CertCompressor() = delete; + + // CompressChain compresses the certificates in |certs| and returns a + // compressed representation. client_cached_cert_hashes| contains + // 64-bit, FNV-1a hashes of certificates that the peer already possesses. + static std::string CompressChain(const std::vector<std::string>& certs, + absl::string_view client_cached_cert_hashes); + + // DecompressChain decompresses the result of |CompressChain|, given in |in|, + // into a series of certificates that are written to |out_certs|. + // |cached_certs| contains certificates that the peer may have omitted. + static bool DecompressChain(absl::string_view in, + const std::vector<std::string>& cached_certs, + std::vector<std::string>* out_certs); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CERT_COMPRESSOR_H_
diff --git a/quiche/quic/core/crypto/cert_compressor_test.cc b/quiche/quic/core/crypto/cert_compressor_test.cc new file mode 100644 index 0000000..d98f4c7 --- /dev/null +++ b/quiche/quic/core/crypto/cert_compressor_test.cc
@@ -0,0 +1,119 @@ +// 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 "quiche/quic/core/crypto/cert_compressor.h" + +#include <memory> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" + +namespace quic { +namespace test { + +class CertCompressorTest : public QuicTest {}; + +TEST_F(CertCompressorTest, EmptyChain) { + std::vector<std::string> chain; + const std::string compressed = + CertCompressor::CompressChain(chain, absl::string_view()); + EXPECT_EQ("00", absl::BytesToHexString(compressed)); + + std::vector<std::string> chain2, cached_certs; + ASSERT_TRUE( + CertCompressor::DecompressChain(compressed, cached_certs, &chain2)); + EXPECT_EQ(chain.size(), chain2.size()); +} + +TEST_F(CertCompressorTest, Compressed) { + std::vector<std::string> chain; + chain.push_back("testcert"); + const std::string compressed = + CertCompressor::CompressChain(chain, absl::string_view()); + ASSERT_GE(compressed.size(), 2u); + EXPECT_EQ("0100", absl::BytesToHexString(compressed.substr(0, 2))); + + std::vector<std::string> chain2, cached_certs; + ASSERT_TRUE( + CertCompressor::DecompressChain(compressed, cached_certs, &chain2)); + EXPECT_EQ(chain.size(), chain2.size()); + EXPECT_EQ(chain[0], chain2[0]); +} + +TEST_F(CertCompressorTest, Common) { + std::vector<std::string> chain; + chain.push_back("testcert"); + static const uint64_t set_hash = 42; + const std::string compressed = CertCompressor::CompressChain( + chain, absl::string_view(reinterpret_cast<const char*>(&set_hash), + sizeof(set_hash))); + ASSERT_GE(compressed.size(), 2u); + // 01 is the prefix for a zlib "compressed" cert not common or cached. + EXPECT_EQ("0100", absl::BytesToHexString(compressed.substr(0, 2))); + + std::vector<std::string> chain2, cached_certs; + ASSERT_TRUE( + CertCompressor::DecompressChain(compressed, cached_certs, &chain2)); + EXPECT_EQ(chain.size(), chain2.size()); + EXPECT_EQ(chain[0], chain2[0]); +} + +TEST_F(CertCompressorTest, Cached) { + std::vector<std::string> chain; + chain.push_back("testcert"); + uint64_t hash = QuicUtils::FNV1a_64_Hash(chain[0]); + absl::string_view hash_bytes(reinterpret_cast<char*>(&hash), sizeof(hash)); + const std::string compressed = + CertCompressor::CompressChain(chain, hash_bytes); + + EXPECT_EQ("02" /* cached */ + absl::BytesToHexString(hash_bytes) + + "00" /* end of list */, + absl::BytesToHexString(compressed)); + + std::vector<std::string> cached_certs, chain2; + cached_certs.push_back(chain[0]); + ASSERT_TRUE( + CertCompressor::DecompressChain(compressed, cached_certs, &chain2)); + EXPECT_EQ(chain.size(), chain2.size()); + EXPECT_EQ(chain[0], chain2[0]); +} + +TEST_F(CertCompressorTest, BadInputs) { + std::vector<std::string> cached_certs, chain; + + EXPECT_FALSE(CertCompressor::DecompressChain( + absl::BytesToHexString("04") /* bad entry type */, cached_certs, &chain)); + + EXPECT_FALSE(CertCompressor::DecompressChain( + absl::BytesToHexString("01") /* no terminator */, cached_certs, &chain)); + + EXPECT_FALSE(CertCompressor::DecompressChain( + absl::BytesToHexString("0200") /* hash truncated */, cached_certs, + &chain)); + + EXPECT_FALSE(CertCompressor::DecompressChain( + absl::BytesToHexString("0300") /* hash and index truncated */, + cached_certs, &chain)); + + /* without a CommonCertSets */ + EXPECT_FALSE( + CertCompressor::DecompressChain(absl::BytesToHexString("03" + "0000000000000000" + "00000000"), + cached_certs, &chain)); + + /* incorrect hash and index */ + EXPECT_FALSE( + CertCompressor::DecompressChain(absl::BytesToHexString("03" + "a200000000000000" + "00000000"), + cached_certs, &chain)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/certificate_util.cc b/quiche/quic/core/crypto/certificate_util.cc new file mode 100644 index 0000000..e56d9cf --- /dev/null +++ b/quiche/quic/core/crypto/certificate_util.cc
@@ -0,0 +1,280 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/certificate_util.h" + +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec_key.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/pkcs7.h" +#include "third_party/boringssl/src/include/openssl/pool.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" +#include "third_party/boringssl/src/include/openssl/stack.h" +#include "quiche/quic/core/crypto/boring_utils.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { +namespace { +bool AddEcdsa256SignatureAlgorithm(CBB* cbb) { + // See RFC 5758. This is the encoding of OID 1.2.840.10045.4.3.2. + static const uint8_t kEcdsaWithSha256[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02}; + + // An AlgorithmIdentifier is described in RFC 5280, 4.1.1.2. + CBB sequence, oid; + if (!CBB_add_asn1(cbb, &sequence, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&sequence, &oid, CBS_ASN1_OBJECT)) { + return false; + } + + if (!CBB_add_bytes(&oid, kEcdsaWithSha256, sizeof(kEcdsaWithSha256))) { + return false; + } + + // RFC 5758, section 3.2: ecdsa-with-sha256 MUST omit the parameters field. + return CBB_flush(cbb); +} + +// Adds an X.509 Name with the specified distinguished name to |cbb|. +bool AddName(CBB* cbb, absl::string_view name) { + // See RFC 4519. + static const uint8_t kCommonName[] = {0x55, 0x04, 0x03}; + static const uint8_t kCountryName[] = {0x55, 0x04, 0x06}; + static const uint8_t kOrganizationName[] = {0x55, 0x04, 0x0a}; + static const uint8_t kOrganizationalUnitName[] = {0x55, 0x04, 0x0b}; + + std::vector<std::string> attributes = + absl::StrSplit(name, ',', absl::SkipEmpty()); + + if (attributes.empty()) { + QUIC_LOG(ERROR) << "Missing DN or wrong format"; + return false; + } + + // See RFC 5280, section 4.1.2.4. + CBB rdns; + if (!CBB_add_asn1(cbb, &rdns, CBS_ASN1_SEQUENCE)) { + return false; + } + + for (const std::string& attribute : attributes) { + std::vector<std::string> parts = + absl::StrSplit(absl::StripAsciiWhitespace(attribute), '='); + if (parts.size() != 2) { + QUIC_LOG(ERROR) << "Wrong DN format at " + attribute; + return false; + } + + const std::string& type_string = parts[0]; + const std::string& value_string = parts[1]; + absl::Span<const uint8_t> type_bytes; + if (type_string == "CN") { + type_bytes = kCommonName; + } else if (type_string == "C") { + type_bytes = kCountryName; + } else if (type_string == "O") { + type_bytes = kOrganizationName; + } else if (type_string == "OU") { + type_bytes = kOrganizationalUnitName; + } else { + QUIC_LOG(ERROR) << "Unrecognized type " + type_string; + return false; + } + + CBB rdn, attr, type, value; + if (!CBB_add_asn1(&rdns, &rdn, CBS_ASN1_SET) || + !CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&attr, &type, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&type, type_bytes.data(), type_bytes.size()) || + !CBB_add_asn1(&attr, &value, + type_string == "C" ? CBS_ASN1_PRINTABLESTRING + : CBS_ASN1_UTF8STRING) || + !AddStringToCbb(&value, value_string) || !CBB_flush(&rdns)) { + return false; + } + } + if (!CBB_flush(cbb)) { + return false; + } + return true; +} + +bool CBBAddTime(CBB* cbb, const CertificateTimestamp& timestamp) { + CBB child; + std::string formatted_time; + + // Per RFC 5280, 4.1.2.5, times which fit in UTCTime must be encoded as + // UTCTime rather than GeneralizedTime. + const bool is_utc_time = (1950 <= timestamp.year && timestamp.year < 2050); + if (is_utc_time) { + uint16_t year = timestamp.year - 1900; + if (year >= 100) { + year -= 100; + } + formatted_time = absl::StrFormat("%02d", year); + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_UTCTIME)) { + return false; + } + } else { + formatted_time = absl::StrFormat("%04d", timestamp.year); + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_GENERALIZEDTIME)) { + return false; + } + } + + absl::StrAppendFormat(&formatted_time, "%02d%02d%02d%02d%02dZ", + timestamp.month, timestamp.day, timestamp.hour, + timestamp.minute, timestamp.second); + + static const size_t kGeneralizedTimeLength = 15; + static const size_t kUTCTimeLength = 13; + QUICHE_DCHECK_EQ(formatted_time.size(), + is_utc_time ? kUTCTimeLength : kGeneralizedTimeLength); + + return AddStringToCbb(&child, formatted_time) && CBB_flush(cbb); +} + +bool CBBAddExtension(CBB* extensions, absl::Span<const uint8_t> oid, + bool critical, absl::Span<const uint8_t> contents) { + CBB extension, cbb_oid, cbb_contents; + if (!CBB_add_asn1(extensions, &extension, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&extension, &cbb_oid, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&cbb_oid, oid.data(), oid.size()) || + (critical && !CBB_add_asn1_bool(&extension, 1)) || + !CBB_add_asn1(&extension, &cbb_contents, CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&cbb_contents, contents.data(), contents.size()) || + !CBB_flush(extensions)) { + return false; + } + + return true; +} + +bool IsEcdsa256Key(const EVP_PKEY& evp_key) { + if (EVP_PKEY_id(&evp_key) != EVP_PKEY_EC) { + return false; + } + const EC_KEY* key = EVP_PKEY_get0_EC_KEY(&evp_key); + if (key == nullptr) { + return false; + } + const EC_GROUP* group = EC_KEY_get0_group(key); + if (group == nullptr) { + return false; + } + return EC_GROUP_get_curve_name(group) == NID_X9_62_prime256v1; +} + +} // namespace + +bssl::UniquePtr<EVP_PKEY> MakeKeyPairForSelfSignedCertificate() { + bssl::UniquePtr<EVP_PKEY_CTX> context( + EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); + if (!context) { + return nullptr; + } + if (EVP_PKEY_keygen_init(context.get()) != 1) { + return nullptr; + } + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context.get(), + NID_X9_62_prime256v1) != 1) { + return nullptr; + } + EVP_PKEY* raw_key = nullptr; + if (EVP_PKEY_keygen(context.get(), &raw_key) != 1) { + return nullptr; + } + return bssl::UniquePtr<EVP_PKEY>(raw_key); +} + +std::string CreateSelfSignedCertificate(EVP_PKEY& key, + const CertificateOptions& options) { + std::string error; + if (!IsEcdsa256Key(key)) { + QUIC_LOG(ERROR) << "CreateSelfSignedCert only accepts ECDSA P-256 keys"; + return error; + } + + // See RFC 5280, section 4.1. First, construct the TBSCertificate. + bssl::ScopedCBB cbb; + CBB tbs_cert, version, validity; + uint8_t* tbs_cert_bytes; + size_t tbs_cert_len; + + if (!CBB_init(cbb.get(), 64) || + !CBB_add_asn1(cbb.get(), &tbs_cert, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&tbs_cert, &version, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || + !CBB_add_asn1_uint64(&version, 2) || // X.509 version 3 + !CBB_add_asn1_uint64(&tbs_cert, options.serial_number) || + !AddEcdsa256SignatureAlgorithm(&tbs_cert) || // signature algorithm + !AddName(&tbs_cert, options.subject) || // issuer + !CBB_add_asn1(&tbs_cert, &validity, CBS_ASN1_SEQUENCE) || + !CBBAddTime(&validity, options.validity_start) || + !CBBAddTime(&validity, options.validity_end) || + !AddName(&tbs_cert, options.subject) || // subject + !EVP_marshal_public_key(&tbs_cert, &key)) { // subjectPublicKeyInfo + return error; + } + + CBB outer_extensions, extensions; + if (!CBB_add_asn1(&tbs_cert, &outer_extensions, + 3 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED) || + !CBB_add_asn1(&outer_extensions, &extensions, CBS_ASN1_SEQUENCE)) { + return error; + } + + // Key Usage + constexpr uint8_t kKeyUsageOid[] = {0x55, 0x1d, 0x0f}; + constexpr uint8_t kKeyUsageContent[] = { + 0x3, // BIT STRING + 0x2, // Length + 0x0, // Unused bits + 0x80, // bit(0): digitalSignature + }; + CBBAddExtension(&extensions, kKeyUsageOid, true, kKeyUsageContent); + + // TODO(wub): Add more extensions here if needed. + + if (!CBB_finish(cbb.get(), &tbs_cert_bytes, &tbs_cert_len)) { + return error; + } + + bssl::UniquePtr<uint8_t> delete_tbs_cert_bytes(tbs_cert_bytes); + + // Sign the TBSCertificate and write the entire certificate. + CBB cert, signature; + bssl::ScopedEVP_MD_CTX ctx; + uint8_t* sig_out; + size_t sig_len; + uint8_t* cert_bytes; + size_t cert_len; + if (!CBB_init(cbb.get(), tbs_cert_len) || + !CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE) || + !CBB_add_bytes(&cert, tbs_cert_bytes, tbs_cert_len) || + !AddEcdsa256SignatureAlgorithm(&cert) || + !CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING) || + !CBB_add_u8(&signature, 0 /* no unused bits */) || + !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, &key) || + // Compute the maximum signature length. + !EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes, + tbs_cert_len) || + !CBB_reserve(&signature, &sig_out, sig_len) || + // Actually sign the TBSCertificate. + !EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes, + tbs_cert_len) || + !CBB_did_write(&signature, sig_len) || + !CBB_finish(cbb.get(), &cert_bytes, &cert_len)) { + return error; + } + bssl::UniquePtr<uint8_t> delete_cert_bytes(cert_bytes); + return std::string(reinterpret_cast<char*>(cert_bytes), cert_len); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/certificate_util.h b/quiche/quic/core/crypto/certificate_util.h new file mode 100644 index 0000000..40c9f87 --- /dev/null +++ b/quiche/quic/core/crypto/certificate_util.h
@@ -0,0 +1,46 @@ +// Copyright 2021 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_ + +#include <string> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +struct QUIC_NO_EXPORT CertificateTimestamp { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +struct QUIC_NO_EXPORT CertificateOptions { + absl::string_view subject; + uint64_t serial_number; + CertificateTimestamp validity_start; // a.k.a not_valid_before + CertificateTimestamp validity_end; // a.k.a not_valid_after +}; + +// Creates a ECDSA P-256 key pair. +QUIC_EXPORT_PRIVATE bssl::UniquePtr<EVP_PKEY> +MakeKeyPairForSelfSignedCertificate(); + +// Creates a self-signed, DER-encoded X.509 certificate. +// |key| must be a ECDSA P-256 key. +// This is mostly stolen from Chromium's net/cert/x509_util.h, with +// modifications to make it work in QUICHE. +QUIC_EXPORT_PRIVATE std::string CreateSelfSignedCertificate( + EVP_PKEY& key, const CertificateOptions& options); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_
diff --git a/quiche/quic/core/crypto/certificate_util_test.cc b/quiche/quic/core/crypto/certificate_util_test.cc new file mode 100644 index 0000000..1c67a4a --- /dev/null +++ b/quiche/quic/core/crypto/certificate_util_test.cc
@@ -0,0 +1,49 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/certificate_util.h" + +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/platform/api/quic_test_output.h" + +namespace quic { +namespace test { +namespace { + +TEST(CertificateUtilTest, CreateSelfSignedCertificate) { + bssl::UniquePtr<EVP_PKEY> key = MakeKeyPairForSelfSignedCertificate(); + ASSERT_NE(key, nullptr); + + CertificatePrivateKey cert_key(std::move(key)); + + CertificateOptions options; + options.subject = "CN=subject"; + options.serial_number = 0x12345678; + options.validity_start = {2020, 1, 1, 0, 0, 0}; + options.validity_end = {2049, 12, 31, 0, 0, 0}; + std::string der_cert = + CreateSelfSignedCertificate(*cert_key.private_key(), options); + ASSERT_FALSE(der_cert.empty()); + + QuicSaveTestOutput("CertificateUtilTest_CreateSelfSignedCert.crt", der_cert); + + std::unique_ptr<CertificateView> cert_view = + CertificateView::ParseSingleCertificate(der_cert); + ASSERT_NE(cert_view, nullptr); + EXPECT_EQ(cert_view->public_key_type(), PublicKeyType::kP256); + + absl::optional<std::string> subject = cert_view->GetHumanReadableSubject(); + ASSERT_TRUE(subject.has_value()); + EXPECT_EQ(*subject, options.subject); + + EXPECT_TRUE( + cert_key.ValidForSignatureAlgorithm(SSL_SIGN_ECDSA_SECP256R1_SHA256)); + EXPECT_TRUE(cert_key.MatchesPublicKey(*cert_view)); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/certificate_view.cc b/quiche/quic/core/crypto/certificate_view.cc new file mode 100644 index 0000000..aa2440b --- /dev/null +++ b/quiche/quic/core/crypto/certificate_view.cc
@@ -0,0 +1,650 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/certificate_view.h" + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "third_party/boringssl/src/include/openssl/base.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec.h" +#include "third_party/boringssl/src/include/openssl/ec_key.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/nid.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/boring_utils.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_ip_address.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/common/platform/api/quiche_time_utils.h" +#include "quiche/common/quiche_data_reader.h" +#include "quiche/common/quiche_text_utils.h" + +namespace quic { +namespace { + +using ::quiche::QuicheTextUtils; + +// The literals below were encoded using `ascii2der | xxd -i`. The comments +// above the literals are the contents in the der2ascii syntax. + +// X.509 version 3 (version numbering starts with zero). +// INTEGER { 2 } +constexpr uint8_t kX509Version[] = {0x02, 0x01, 0x02}; + +// 2.5.29.17 +constexpr uint8_t kSubjectAltNameOid[] = {0x55, 0x1d, 0x11}; + +PublicKeyType PublicKeyTypeFromKey(EVP_PKEY* public_key) { + switch (EVP_PKEY_id(public_key)) { + case EVP_PKEY_RSA: + return PublicKeyType::kRsa; + case EVP_PKEY_EC: { + const EC_KEY* key = EVP_PKEY_get0_EC_KEY(public_key); + if (key == nullptr) { + return PublicKeyType::kUnknown; + } + const EC_GROUP* group = EC_KEY_get0_group(key); + if (group == nullptr) { + return PublicKeyType::kUnknown; + } + const int curve_nid = EC_GROUP_get_curve_name(group); + switch (curve_nid) { + case NID_X9_62_prime256v1: + return PublicKeyType::kP256; + case NID_secp384r1: + return PublicKeyType::kP384; + default: + return PublicKeyType::kUnknown; + } + } + case EVP_PKEY_ED25519: + return PublicKeyType::kEd25519; + default: + return PublicKeyType::kUnknown; + } +} + +PublicKeyType PublicKeyTypeFromSignatureAlgorithm( + uint16_t signature_algorithm) { + switch (signature_algorithm) { + case SSL_SIGN_RSA_PSS_RSAE_SHA256: + return PublicKeyType::kRsa; + case SSL_SIGN_ECDSA_SECP256R1_SHA256: + return PublicKeyType::kP256; + case SSL_SIGN_ECDSA_SECP384R1_SHA384: + return PublicKeyType::kP384; + case SSL_SIGN_ED25519: + return PublicKeyType::kEd25519; + default: + return PublicKeyType::kUnknown; + } +} + +std::string AttributeNameToString(const CBS& oid_cbs) { + absl::string_view oid = CbsToStringPiece(oid_cbs); + + // We only handle OIDs of form 2.5.4.N, which have binary encoding of + // "55 04 0N". + if (oid.length() == 3 && absl::StartsWith(oid, "\x55\x04")) { + // clang-format off + switch (oid[2]) { + case '\x3': return "CN"; + case '\x7': return "L"; + case '\x8': return "ST"; + case '\xa': return "O"; + case '\xb': return "OU"; + case '\x6': return "C"; + } + // clang-format on + } + + bssl::UniquePtr<char> oid_representation(CBS_asn1_oid_to_text(&oid_cbs)); + if (oid_representation == nullptr) { + return absl::StrCat("(", absl::BytesToHexString(oid), ")"); + } + return std::string(oid_representation.get()); +} + +} // namespace + +absl::optional<std::string> X509NameAttributeToString(CBS input) { + CBS name, value; + unsigned value_tag; + if (!CBS_get_asn1(&input, &name, CBS_ASN1_OBJECT) || + !CBS_get_any_asn1(&input, &value, &value_tag) || CBS_len(&input) != 0) { + return absl::nullopt; + } + // Note that this does not process encoding of |input| in any way. This works + // fine for the most cases. + return absl::StrCat(AttributeNameToString(name), "=", + absl::CHexEscape(CbsToStringPiece(value))); +} + +namespace { + +template <unsigned inner_tag, + char separator, + absl::optional<std::string> (*parser)(CBS)> +absl::optional<std::string> ParseAndJoin(CBS input) { + std::vector<std::string> pieces; + while (CBS_len(&input) != 0) { + CBS attribute; + if (!CBS_get_asn1(&input, &attribute, inner_tag)) { + return absl::nullopt; + } + absl::optional<std::string> formatted = parser(attribute); + if (!formatted.has_value()) { + return absl::nullopt; + } + pieces.push_back(*formatted); + } + + return absl::StrJoin(pieces, std::string({separator})); +} + +absl::optional<std::string> RelativeDistinguishedNameToString(CBS input) { + return ParseAndJoin<CBS_ASN1_SEQUENCE, '+', X509NameAttributeToString>(input); +} + +absl::optional<std::string> DistinguishedNameToString(CBS input) { + return ParseAndJoin<CBS_ASN1_SET, ',', RelativeDistinguishedNameToString>( + input); +} + +} // namespace + +std::string PublicKeyTypeToString(PublicKeyType type) { + switch (type) { + case PublicKeyType::kRsa: + return "RSA"; + case PublicKeyType::kP256: + return "ECDSA P-256"; + case PublicKeyType::kP384: + return "ECDSA P-384"; + case PublicKeyType::kEd25519: + return "Ed25519"; + case PublicKeyType::kUnknown: + return "unknown"; + } + return ""; +} + +absl::optional<quic::QuicWallTime> ParseDerTime(unsigned tag, + absl::string_view payload) { + if (tag != CBS_ASN1_GENERALIZEDTIME && tag != CBS_ASN1_UTCTIME) { + QUIC_DLOG(WARNING) << "Invalid tag supplied for a DER timestamp"; + return absl::nullopt; + } + + const size_t year_length = tag == CBS_ASN1_GENERALIZEDTIME ? 4 : 2; + uint64_t year, month, day, hour, minute, second; + quiche::QuicheDataReader reader(payload); + if (!reader.ReadDecimal64(year_length, &year) || + !reader.ReadDecimal64(2, &month) || !reader.ReadDecimal64(2, &day) || + !reader.ReadDecimal64(2, &hour) || !reader.ReadDecimal64(2, &minute) || + !reader.ReadDecimal64(2, &second) || + reader.ReadRemainingPayload() != "Z") { + QUIC_DLOG(WARNING) << "Failed to parse the DER timestamp"; + return absl::nullopt; + } + + if (tag == CBS_ASN1_UTCTIME) { + QUICHE_DCHECK_LE(year, 100u); + year += (year >= 50) ? 1900 : 2000; + } + + const absl::optional<int64_t> unix_time = + quiche::QuicheUtcDateTimeToUnixSeconds(year, month, day, hour, minute, + second); + if (!unix_time.has_value() || *unix_time < 0) { + return absl::nullopt; + } + return QuicWallTime::FromUNIXSeconds(*unix_time); +} + +PemReadResult ReadNextPemMessage(std::istream* input) { + constexpr absl::string_view kPemBegin = "-----BEGIN "; + constexpr absl::string_view kPemEnd = "-----END "; + constexpr absl::string_view kPemDashes = "-----"; + + std::string line_buffer, encoded_message_contents, expected_end; + bool pending_message = false; + PemReadResult result; + while (std::getline(*input, line_buffer)) { + absl::string_view line(line_buffer); + QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&line); + + // Handle BEGIN lines. + if (!pending_message && absl::StartsWith(line, kPemBegin) && + absl::EndsWith(line, kPemDashes)) { + result.type = std::string( + line.substr(kPemBegin.size(), + line.size() - kPemDashes.size() - kPemBegin.size())); + expected_end = absl::StrCat(kPemEnd, result.type, kPemDashes); + pending_message = true; + continue; + } + + // Handle END lines. + if (pending_message && line == expected_end) { + absl::optional<std::string> data = + QuicheTextUtils::Base64Decode(encoded_message_contents); + if (data.has_value()) { + result.status = PemReadResult::kOk; + result.contents = data.value(); + } else { + result.status = PemReadResult::kError; + } + return result; + } + + if (pending_message) { + encoded_message_contents.append(std::string(line)); + } + } + bool eof_reached = input->eof() && !pending_message; + return PemReadResult{ + (eof_reached ? PemReadResult::kEof : PemReadResult::kError), "", ""}; +} + +std::unique_ptr<CertificateView> CertificateView::ParseSingleCertificate( + absl::string_view certificate) { + std::unique_ptr<CertificateView> result(new CertificateView()); + CBS top = StringPieceToCbs(certificate); + + CBS top_certificate, tbs_certificate, signature_algorithm, signature; + if (!CBS_get_asn1(&top, &top_certificate, CBS_ASN1_SEQUENCE) || + CBS_len(&top) != 0) { + return nullptr; + } + + // Certificate ::= SEQUENCE { + if ( + // tbsCertificate TBSCertificate, + !CBS_get_asn1(&top_certificate, &tbs_certificate, CBS_ASN1_SEQUENCE) || + + // signatureAlgorithm AlgorithmIdentifier, + !CBS_get_asn1(&top_certificate, &signature_algorithm, + CBS_ASN1_SEQUENCE) || + + // signature BIT STRING } + !CBS_get_asn1(&top_certificate, &signature, CBS_ASN1_BITSTRING) || + CBS_len(&top_certificate) != 0) { + return nullptr; + } + + int has_version, has_extensions; + CBS version, serial, signature_algorithm_inner, issuer, validity, subject, + spki, issuer_id, subject_id, extensions_outer; + // TBSCertificate ::= SEQUENCE { + if ( + // version [0] Version DEFAULT v1, + !CBS_get_optional_asn1( + &tbs_certificate, &version, &has_version, + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) || + + // serialNumber CertificateSerialNumber, + !CBS_get_asn1(&tbs_certificate, &serial, CBS_ASN1_INTEGER) || + + // signature AlgorithmIdentifier, + !CBS_get_asn1(&tbs_certificate, &signature_algorithm_inner, + CBS_ASN1_SEQUENCE) || + + // issuer Name, + !CBS_get_asn1(&tbs_certificate, &issuer, CBS_ASN1_SEQUENCE) || + + // validity Validity, + !CBS_get_asn1(&tbs_certificate, &validity, CBS_ASN1_SEQUENCE) || + + // subject Name, + !CBS_get_asn1(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE) || + + // subjectPublicKeyInfo SubjectPublicKeyInfo, + !CBS_get_asn1_element(&tbs_certificate, &spki, CBS_ASN1_SEQUENCE) || + + // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + // -- If present, version MUST be v2 or v3 + !CBS_get_optional_asn1(&tbs_certificate, &issuer_id, nullptr, + CBS_ASN1_CONTEXT_SPECIFIC | 1) || + + // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + // -- If present, version MUST be v2 or v3 + !CBS_get_optional_asn1(&tbs_certificate, &subject_id, nullptr, + CBS_ASN1_CONTEXT_SPECIFIC | 2) || + + // extensions [3] Extensions OPTIONAL + // -- If present, version MUST be v3 -- } + !CBS_get_optional_asn1( + &tbs_certificate, &extensions_outer, &has_extensions, + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 3) || + + CBS_len(&tbs_certificate) != 0) { + return nullptr; + } + + result->subject_der_ = CbsToStringPiece(subject); + + unsigned not_before_tag, not_after_tag; + CBS not_before, not_after; + if (!CBS_get_any_asn1(&validity, ¬_before, ¬_before_tag) || + !CBS_get_any_asn1(&validity, ¬_after, ¬_after_tag) || + CBS_len(&validity) != 0) { + QUIC_DLOG(WARNING) << "Failed to extract the validity dates"; + return nullptr; + } + absl::optional<QuicWallTime> not_before_parsed = + ParseDerTime(not_before_tag, CbsToStringPiece(not_before)); + absl::optional<QuicWallTime> not_after_parsed = + ParseDerTime(not_after_tag, CbsToStringPiece(not_after)); + if (!not_before_parsed.has_value() || !not_after_parsed.has_value()) { + QUIC_DLOG(WARNING) << "Failed to parse validity dates"; + return nullptr; + } + result->validity_start_ = *not_before_parsed; + result->validity_end_ = *not_after_parsed; + + result->public_key_.reset(EVP_parse_public_key(&spki)); + if (result->public_key_ == nullptr) { + QUIC_DLOG(WARNING) << "Failed to parse the public key"; + return nullptr; + } + if (!result->ValidatePublicKeyParameters()) { + QUIC_DLOG(WARNING) << "Public key has invalid parameters"; + return nullptr; + } + + // Only support X.509v3. + if (!has_version || + !CBS_mem_equal(&version, kX509Version, sizeof(kX509Version))) { + QUIC_DLOG(WARNING) << "Bad X.509 version"; + return nullptr; + } + + if (!has_extensions) { + return nullptr; + } + + CBS extensions; + if (!CBS_get_asn1(&extensions_outer, &extensions, CBS_ASN1_SEQUENCE) || + CBS_len(&extensions_outer) != 0) { + QUIC_DLOG(WARNING) << "Failed to extract the extension sequence"; + return nullptr; + } + if (!result->ParseExtensions(extensions)) { + QUIC_DLOG(WARNING) << "Failed to parse extensions"; + return nullptr; + } + + return result; +} + +bool CertificateView::ParseExtensions(CBS extensions) { + while (CBS_len(&extensions) != 0) { + CBS extension, oid, critical, payload; + if ( + // Extension ::= SEQUENCE { + !CBS_get_asn1(&extensions, &extension, CBS_ASN1_SEQUENCE) || + // extnID OBJECT IDENTIFIER, + !CBS_get_asn1(&extension, &oid, CBS_ASN1_OBJECT) || + // critical BOOLEAN DEFAULT FALSE, + !CBS_get_optional_asn1(&extension, &critical, nullptr, + CBS_ASN1_BOOLEAN) || + // extnValue OCTET STRING + // -- contains the DER encoding of an ASN.1 value + // -- corresponding to the extension type identified + // -- by extnID + !CBS_get_asn1(&extension, &payload, CBS_ASN1_OCTETSTRING) || + CBS_len(&extension) != 0) { + QUIC_DLOG(WARNING) << "Bad extension entry"; + return false; + } + + if (CBS_mem_equal(&oid, kSubjectAltNameOid, sizeof(kSubjectAltNameOid))) { + CBS alt_names; + if (!CBS_get_asn1(&payload, &alt_names, CBS_ASN1_SEQUENCE) || + CBS_len(&payload) != 0) { + QUIC_DLOG(WARNING) << "Failed to parse subjectAltName"; + return false; + } + while (CBS_len(&alt_names) != 0) { + CBS alt_name_cbs; + unsigned int alt_name_tag; + if (!CBS_get_any_asn1(&alt_names, &alt_name_cbs, &alt_name_tag)) { + QUIC_DLOG(WARNING) << "Failed to parse subjectAltName"; + return false; + } + + absl::string_view alt_name = CbsToStringPiece(alt_name_cbs); + QuicIpAddress ip_address; + // GeneralName ::= CHOICE { + switch (alt_name_tag) { + // dNSName [2] IA5String, + case CBS_ASN1_CONTEXT_SPECIFIC | 2: + subject_alt_name_domains_.push_back(alt_name); + break; + + // iPAddress [7] OCTET STRING, + case CBS_ASN1_CONTEXT_SPECIFIC | 7: + if (!ip_address.FromPackedString(alt_name.data(), + alt_name.size())) { + QUIC_DLOG(WARNING) << "Failed to parse subjectAltName IP address"; + return false; + } + subject_alt_name_ips_.push_back(ip_address); + break; + + default: + QUIC_DLOG(INFO) << "Unknown subjectAltName tag " << alt_name_tag; + continue; + } + } + } + } + + return true; +} + +std::vector<std::string> CertificateView::LoadPemFromStream( + std::istream* input) { + std::vector<std::string> result; + for (;;) { + PemReadResult read_result = ReadNextPemMessage(input); + if (read_result.status == PemReadResult::kEof) { + return result; + } + if (read_result.status != PemReadResult::kOk) { + return std::vector<std::string>(); + } + if (read_result.type != "CERTIFICATE") { + continue; + } + result.emplace_back(std::move(read_result.contents)); + } +} + +PublicKeyType CertificateView::public_key_type() const { + return PublicKeyTypeFromKey(public_key_.get()); +} + +bool CertificateView::ValidatePublicKeyParameters() { + // The profile here affects what certificates can be used when QUIC is used as + // a server library without any custom certificate provider logic. + // The goal is to allow at minimum any certificate that would be allowed on a + // regular Web session over TLS 1.3 while ensuring we do not expose any + // algorithms we don't want to support long-term. + PublicKeyType key_type = PublicKeyTypeFromKey(public_key_.get()); + switch (key_type) { + case PublicKeyType::kRsa: + return EVP_PKEY_bits(public_key_.get()) >= 2048; + case PublicKeyType::kP256: + case PublicKeyType::kP384: + case PublicKeyType::kEd25519: + return true; + default: + return false; + } +} + +bool CertificateView::VerifySignature(absl::string_view data, + absl::string_view signature, + uint16_t signature_algorithm) const { + if (PublicKeyTypeFromSignatureAlgorithm(signature_algorithm) != + PublicKeyTypeFromKey(public_key_.get())) { + QUIC_BUG(quic_bug_10640_1) + << "Mismatch between the requested signature algorithm and the " + "type of the public key."; + return false; + } + + bssl::ScopedEVP_MD_CTX md_ctx; + EVP_PKEY_CTX* pctx; + if (!EVP_DigestVerifyInit( + md_ctx.get(), &pctx, + SSL_get_signature_algorithm_digest(signature_algorithm), nullptr, + public_key_.get())) { + return false; + } + if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) { + if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) || + !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1)) { + return false; + } + } + return EVP_DigestVerify( + md_ctx.get(), reinterpret_cast<const uint8_t*>(signature.data()), + signature.size(), reinterpret_cast<const uint8_t*>(data.data()), + data.size()); +} + +absl::optional<std::string> CertificateView::GetHumanReadableSubject() const { + CBS input = StringPieceToCbs(subject_der_); + return DistinguishedNameToString(input); +} + +std::unique_ptr<CertificatePrivateKey> CertificatePrivateKey::LoadFromDer( + absl::string_view private_key) { + std::unique_ptr<CertificatePrivateKey> result(new CertificatePrivateKey()); + CBS private_key_cbs = StringPieceToCbs(private_key); + result->private_key_.reset(EVP_parse_private_key(&private_key_cbs)); + if (result->private_key_ == nullptr || CBS_len(&private_key_cbs) != 0) { + return nullptr; + } + return result; +} + +std::unique_ptr<CertificatePrivateKey> CertificatePrivateKey::LoadPemFromStream( + std::istream* input) { +skip: + PemReadResult result = ReadNextPemMessage(input); + if (result.status != PemReadResult::kOk) { + return nullptr; + } + // RFC 5958 OneAsymmetricKey message. + if (result.type == "PRIVATE KEY") { + return LoadFromDer(result.contents); + } + // Legacy OpenSSL format: PKCS#1 (RFC 8017) RSAPrivateKey message. + if (result.type == "RSA PRIVATE KEY") { + CBS private_key_cbs = StringPieceToCbs(result.contents); + bssl::UniquePtr<RSA> rsa(RSA_parse_private_key(&private_key_cbs)); + if (rsa == nullptr || CBS_len(&private_key_cbs) != 0) { + return nullptr; + } + + std::unique_ptr<CertificatePrivateKey> key(new CertificatePrivateKey()); + key->private_key_.reset(EVP_PKEY_new()); + EVP_PKEY_assign_RSA(key->private_key_.get(), rsa.release()); + return key; + } + // EC keys are sometimes generated with "openssl ecparam -genkey". If the user + // forgets -noout, OpenSSL will output a redundant copy of the EC parameters. + // Skip those. + if (result.type == "EC PARAMETERS") { + goto skip; + } + // Legacy OpenSSL format: RFC 5915 ECPrivateKey message. + if (result.type == "EC PRIVATE KEY") { + CBS private_key_cbs = StringPieceToCbs(result.contents); + bssl::UniquePtr<EC_KEY> ec_key( + EC_KEY_parse_private_key(&private_key_cbs, /*group=*/nullptr)); + if (ec_key == nullptr || CBS_len(&private_key_cbs) != 0) { + return nullptr; + } + + std::unique_ptr<CertificatePrivateKey> key(new CertificatePrivateKey()); + key->private_key_.reset(EVP_PKEY_new()); + EVP_PKEY_assign_EC_KEY(key->private_key_.get(), ec_key.release()); + return key; + } + // Unknown format. + return nullptr; +} + +std::string CertificatePrivateKey::Sign(absl::string_view input, + uint16_t signature_algorithm) const { + if (!ValidForSignatureAlgorithm(signature_algorithm)) { + QUIC_BUG(quic_bug_10640_2) + << "Mismatch between the requested signature algorithm and the " + "type of the private key."; + return ""; + } + + bssl::ScopedEVP_MD_CTX md_ctx; + EVP_PKEY_CTX* pctx; + if (!EVP_DigestSignInit( + md_ctx.get(), &pctx, + SSL_get_signature_algorithm_digest(signature_algorithm), + /*e=*/nullptr, private_key_.get())) { + return ""; + } + if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) { + if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) || + !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1)) { + return ""; + } + } + + std::string output; + size_t output_size; + if (!EVP_DigestSign(md_ctx.get(), /*out_sig=*/nullptr, &output_size, + reinterpret_cast<const uint8_t*>(input.data()), + input.size())) { + return ""; + } + output.resize(output_size); + if (!EVP_DigestSign( + md_ctx.get(), reinterpret_cast<uint8_t*>(&output[0]), &output_size, + reinterpret_cast<const uint8_t*>(input.data()), input.size())) { + return ""; + } + output.resize(output_size); + return output; +} + +bool CertificatePrivateKey::MatchesPublicKey( + const CertificateView& view) const { + return EVP_PKEY_cmp(view.public_key(), private_key_.get()) == 1; +} + +bool CertificatePrivateKey::ValidForSignatureAlgorithm( + uint16_t signature_algorithm) const { + return PublicKeyTypeFromSignatureAlgorithm(signature_algorithm) == + PublicKeyTypeFromKey(private_key_.get()); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/certificate_view.h b/quiche/quic/core/crypto/certificate_view.h new file mode 100644 index 0000000..347cee9 --- /dev/null +++ b/quiche/quic/core/crypto/certificate_view.h
@@ -0,0 +1,151 @@ +// Copyright 2020 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_VIEW_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_VIEW_H_ + +#include <istream> +#include <memory> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "third_party/boringssl/src/include/openssl/base.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quiche/quic/core/crypto/boring_utils.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/quic/platform/api/quic_ip_address.h" + +namespace quic { + +struct QUIC_EXPORT_PRIVATE PemReadResult { + enum Status { kOk, kEof, kError }; + Status status; + std::string contents; + // The type of the PEM message (e.g., if the message starts with + // "-----BEGIN CERTIFICATE-----", the |type| would be "CERTIFICATE"). + std::string type; +}; + +// Reads |input| line-by-line and returns the next available PEM message. +QUIC_EXPORT_PRIVATE PemReadResult ReadNextPemMessage(std::istream* input); + +// Cryptograhpic algorithms recognized in X.509. +enum class PublicKeyType { + kRsa, + kP256, + kP384, + kEd25519, + kUnknown, +}; +QUIC_EXPORT_PRIVATE std::string PublicKeyTypeToString(PublicKeyType type); + +// CertificateView represents a parsed version of a single X.509 certificate. As +// the word "view" implies, it does not take ownership of the underlying strings +// and consists primarily of pointers into the certificate that is passed into +// the parser. +class QUIC_EXPORT_PRIVATE CertificateView { + public: + // Parses a single DER-encoded X.509 certificate. Returns nullptr on parse + // error. + static std::unique_ptr<CertificateView> ParseSingleCertificate( + absl::string_view certificate); + + // Loads all PEM-encoded X.509 certificates found in the |input| stream + // without parsing them. Returns an empty vector if any parsing error occurs. + static std::vector<std::string> LoadPemFromStream(std::istream* input); + + QuicWallTime validity_start() const { return validity_start_; } + QuicWallTime validity_end() const { return validity_end_; } + const EVP_PKEY* public_key() const { return public_key_.get(); } + + const std::vector<absl::string_view>& subject_alt_name_domains() const { + return subject_alt_name_domains_; + } + const std::vector<QuicIpAddress>& subject_alt_name_ips() const { + return subject_alt_name_ips_; + } + + // Returns a human-readable representation of the Subject field. The format + // is similar to RFC 2253, but does not match it exactly. + absl::optional<std::string> GetHumanReadableSubject() const; + + // |signature_algorithm| is a TLS signature algorithm ID. + bool VerifySignature(absl::string_view data, + absl::string_view signature, + uint16_t signature_algorithm) const; + + // Returns the type of the key used in the certificate's SPKI. + PublicKeyType public_key_type() const; + + private: + CertificateView() = default; + + QuicWallTime validity_start_ = QuicWallTime::Zero(); + QuicWallTime validity_end_ = QuicWallTime::Zero(); + absl::string_view subject_der_; + + // Public key parsed from SPKI. + bssl::UniquePtr<EVP_PKEY> public_key_; + + // SubjectAltName, https://tools.ietf.org/html/rfc5280#section-4.2.1.6 + std::vector<absl::string_view> subject_alt_name_domains_; + std::vector<QuicIpAddress> subject_alt_name_ips_; + + // Called from ParseSingleCertificate(). + bool ParseExtensions(CBS extensions); + bool ValidatePublicKeyParameters(); +}; + +// CertificatePrivateKey represents a private key that can be used with an X.509 +// certificate. +class QUIC_EXPORT_PRIVATE CertificatePrivateKey { + public: + explicit CertificatePrivateKey(bssl::UniquePtr<EVP_PKEY> private_key) + : private_key_(std::move(private_key)) {} + + // Loads a DER-encoded PrivateKeyInfo structure (RFC 5958) as a private key. + static std::unique_ptr<CertificatePrivateKey> LoadFromDer( + absl::string_view private_key); + + // Loads a private key from a PEM file formatted according to RFC 7468. Also + // supports legacy OpenSSL RSA key format ("BEGIN RSA PRIVATE KEY"). + static std::unique_ptr<CertificatePrivateKey> LoadPemFromStream( + std::istream* input); + + // |signature_algorithm| is a TLS signature algorithm ID. + std::string Sign(absl::string_view input, uint16_t signature_algorithm) const; + + // Verifies that the private key in question matches the public key of the + // certificate |view|. + bool MatchesPublicKey(const CertificateView& view) const; + + // Verifies that the private key can be used with the specified TLS signature + // algorithm. + bool ValidForSignatureAlgorithm(uint16_t signature_algorithm) const; + + EVP_PKEY* private_key() const { return private_key_.get(); } + + private: + CertificatePrivateKey() = default; + + bssl::UniquePtr<EVP_PKEY> private_key_; +}; + +// Parses a DER-encoded X.509 NameAttribute. Exposed primarily for testing. +QUIC_EXPORT_PRIVATE absl::optional<std::string> X509NameAttributeToString( + CBS input); + +// Parses a DER time based on the specified ASN.1 tag. Exposed primarily for +// testing. +QUIC_EXPORT_PRIVATE absl::optional<quic::QuicWallTime> ParseDerTime( + unsigned tag, + absl::string_view payload); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_VIEW_H_
diff --git a/quiche/quic/core/crypto/certificate_view_der_fuzzer.cc b/quiche/quic/core/crypto/certificate_view_der_fuzzer.cc new file mode 100644 index 0000000..81c91eb --- /dev/null +++ b/quiche/quic/core/crypto/certificate_view_der_fuzzer.cc
@@ -0,0 +1,19 @@ +// Copyright 2020 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 <string> + +#include "quiche/quic/core/crypto/certificate_view.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::string input(reinterpret_cast<const char*>(data), size); + + std::unique_ptr<quic::CertificateView> view = + quic::CertificateView::ParseSingleCertificate(input); + if (view != nullptr) { + view->GetHumanReadableSubject(); + } + quic::CertificatePrivateKey::LoadFromDer(input); + return 0; +}
diff --git a/quiche/quic/core/crypto/certificate_view_pem_fuzzer.cc b/quiche/quic/core/crypto/certificate_view_pem_fuzzer.cc new file mode 100644 index 0000000..e6d70e5 --- /dev/null +++ b/quiche/quic/core/crypto/certificate_view_pem_fuzzer.cc
@@ -0,0 +1,18 @@ +// Copyright 2020 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 <sstream> +#include <string> + +#include "quiche/quic/core/crypto/certificate_view.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::string input(reinterpret_cast<const char*>(data), size); + std::stringstream stream(input); + + quic::CertificateView::LoadPemFromStream(&stream); + stream.seekg(0); + quic::CertificatePrivateKey::LoadPemFromStream(&stream); + return 0; +}
diff --git a/quiche/quic/core/crypto/certificate_view_test.cc b/quiche/quic/core/crypto/certificate_view_test.cc new file mode 100644 index 0000000..009fd8d --- /dev/null +++ b/quiche/quic/core/crypto/certificate_view_test.cc
@@ -0,0 +1,214 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/certificate_view.h" + +#include <memory> +#include <sstream> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/base.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/boring_utils.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/platform/api/quic_ip_address.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/test_certificates.h" +#include "quiche/common/platform/api/quiche_time_utils.h" + +namespace quic { +namespace test { +namespace { + +using ::testing::ElementsAre; +using ::testing::HasSubstr; +using ::testing::Optional; + +TEST(CertificateViewTest, PemParser) { + std::stringstream stream(kTestCertificatePem); + PemReadResult result = ReadNextPemMessage(&stream); + EXPECT_EQ(result.status, PemReadResult::kOk); + EXPECT_EQ(result.type, "CERTIFICATE"); + EXPECT_EQ(result.contents, kTestCertificate); + + result = ReadNextPemMessage(&stream); + EXPECT_EQ(result.status, PemReadResult::kEof); +} + +TEST(CertificateViewTest, Parse) { + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(kTestCertificate); + ASSERT_TRUE(view != nullptr); + + EXPECT_THAT(view->subject_alt_name_domains(), + ElementsAre(absl::string_view("www.example.org"), + absl::string_view("mail.example.org"), + absl::string_view("mail.example.com"))); + EXPECT_THAT(view->subject_alt_name_ips(), + ElementsAre(QuicIpAddress::Loopback4())); + EXPECT_EQ(EVP_PKEY_id(view->public_key()), EVP_PKEY_RSA); + + const QuicWallTime validity_start = QuicWallTime::FromUNIXSeconds( + *quiche::QuicheUtcDateTimeToUnixSeconds(2020, 1, 30, 18, 13, 59)); + EXPECT_EQ(view->validity_start(), validity_start); + const QuicWallTime validity_end = QuicWallTime::FromUNIXSeconds( + *quiche::QuicheUtcDateTimeToUnixSeconds(2020, 2, 2, 18, 13, 59)); + EXPECT_EQ(view->validity_end(), validity_end); + EXPECT_EQ(view->public_key_type(), PublicKeyType::kRsa); + EXPECT_EQ(PublicKeyTypeToString(view->public_key_type()), "RSA"); + + EXPECT_EQ("C=US,ST=California,L=Mountain View,O=QUIC Server,CN=127.0.0.1", + view->GetHumanReadableSubject()); +} + +TEST(CertificateViewTest, ParseCertWithUnknownSanType) { + std::stringstream stream(kTestCertWithUnknownSanTypePem); + PemReadResult result = ReadNextPemMessage(&stream); + EXPECT_EQ(result.status, PemReadResult::kOk); + EXPECT_EQ(result.type, "CERTIFICATE"); + + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(result.contents); + EXPECT_TRUE(view != nullptr); +} + +TEST(CertificateViewTest, PemSingleCertificate) { + std::stringstream pem_stream(kTestCertificatePem); + std::vector<std::string> chain = + CertificateView::LoadPemFromStream(&pem_stream); + EXPECT_THAT(chain, ElementsAre(kTestCertificate)); +} + +TEST(CertificateViewTest, PemMultipleCertificates) { + std::stringstream pem_stream(kTestCertificateChainPem); + std::vector<std::string> chain = + CertificateView::LoadPemFromStream(&pem_stream); + EXPECT_THAT(chain, + ElementsAre(kTestCertificate, HasSubstr("QUIC Server Root CA"))); +} + +TEST(CertificateViewTest, PemNoCertificates) { + std::stringstream pem_stream("one\ntwo\nthree\n"); + std::vector<std::string> chain = + CertificateView::LoadPemFromStream(&pem_stream); + EXPECT_TRUE(chain.empty()); +} + +TEST(CertificateViewTest, SignAndVerify) { + std::unique_ptr<CertificatePrivateKey> key = + CertificatePrivateKey::LoadFromDer(kTestCertificatePrivateKey); + ASSERT_TRUE(key != nullptr); + + std::string data = "A really important message"; + std::string signature = key->Sign(data, SSL_SIGN_RSA_PSS_RSAE_SHA256); + ASSERT_FALSE(signature.empty()); + + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(kTestCertificate); + ASSERT_TRUE(view != nullptr); + EXPECT_TRUE(key->MatchesPublicKey(*view)); + + EXPECT_TRUE( + view->VerifySignature(data, signature, SSL_SIGN_RSA_PSS_RSAE_SHA256)); + EXPECT_FALSE(view->VerifySignature("An unimportant message", signature, + SSL_SIGN_RSA_PSS_RSAE_SHA256)); + EXPECT_FALSE(view->VerifySignature(data, "Not a signature", + SSL_SIGN_RSA_PSS_RSAE_SHA256)); +} + +TEST(CertificateViewTest, PrivateKeyPem) { + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(kTestCertificate); + ASSERT_TRUE(view != nullptr); + + std::stringstream pem_stream(kTestCertificatePrivateKeyPem); + std::unique_ptr<CertificatePrivateKey> pem_key = + CertificatePrivateKey::LoadPemFromStream(&pem_stream); + ASSERT_TRUE(pem_key != nullptr); + EXPECT_TRUE(pem_key->MatchesPublicKey(*view)); + + std::stringstream legacy_stream(kTestCertificatePrivateKeyLegacyPem); + std::unique_ptr<CertificatePrivateKey> legacy_key = + CertificatePrivateKey::LoadPemFromStream(&legacy_stream); + ASSERT_TRUE(legacy_key != nullptr); + EXPECT_TRUE(legacy_key->MatchesPublicKey(*view)); +} + +TEST(CertificateViewTest, PrivateKeyEcdsaPem) { + std::stringstream pem_stream(kTestEcPrivateKeyLegacyPem); + std::unique_ptr<CertificatePrivateKey> key = + CertificatePrivateKey::LoadPemFromStream(&pem_stream); + ASSERT_TRUE(key != nullptr); + EXPECT_TRUE(key->ValidForSignatureAlgorithm(SSL_SIGN_ECDSA_SECP256R1_SHA256)); +} + +TEST(CertificateViewTest, DerTime) { + EXPECT_THAT(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024Z"), + Optional(QuicWallTime::FromUNIXSeconds(24))); + EXPECT_THAT(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19710101000024Z"), + Optional(QuicWallTime::FromUNIXSeconds(365 * 86400 + 24))); + EXPECT_THAT(ParseDerTime(CBS_ASN1_UTCTIME, "700101000024Z"), + Optional(QuicWallTime::FromUNIXSeconds(24))); + EXPECT_TRUE(ParseDerTime(CBS_ASN1_UTCTIME, "200101000024Z").has_value()); + + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, ""), absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.001Z"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024Q"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024-0500"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "700101000024ZZ"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.00Z"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.Z"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "197O0101000024Z"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.0O1Z"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "-9700101000024Z"), + absl::nullopt); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "1970-101000024Z"), + absl::nullopt); + + EXPECT_TRUE(ParseDerTime(CBS_ASN1_UTCTIME, "490101000024Z").has_value()); + // This should parse as 1950, which predates UNIX epoch. + EXPECT_FALSE(ParseDerTime(CBS_ASN1_UTCTIME, "500101000024Z").has_value()); + + EXPECT_THAT(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101230000Z"), + Optional(QuicWallTime::FromUNIXSeconds(23 * 3600))); + EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101240000Z"), + absl::nullopt); +} + +TEST(CertificateViewTest, NameAttribute) { + // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.112411 } + // UTF8String { "Test" } + std::string unknown_oid = + absl::HexStringToBytes("060b2a864886f712040186ee1b0c0454657374"); + EXPECT_EQ("1.2.840.113554.4.1.112411=Test", + X509NameAttributeToString(StringPieceToCbs(unknown_oid))); + + // OBJECT_IDENTIFIER { 2.5.4.3 } + // UTF8String { "Bell: \x07" } + std::string non_printable = + absl::HexStringToBytes("06035504030c0742656c6c3a2007"); + EXPECT_EQ(R"(CN=Bell: \x07)", + X509NameAttributeToString(StringPieceToCbs(non_printable))); + + // OBJECT_IDENTIFIER { "\x55\x80" } + // UTF8String { "Test" } + std::string invalid_oid = absl::HexStringToBytes("060255800c0454657374"); + EXPECT_EQ("(5580)=Test", + X509NameAttributeToString(StringPieceToCbs(invalid_oid))); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_decrypter.cc b/quiche/quic/core/crypto/chacha20_poly1305_decrypter.cc new file mode 100644 index 0000000..556998a --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_decrypter.cc
@@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_decrypter.h" + +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "third_party/boringssl/src/include/openssl/tls1.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 32; +const size_t kNonceSize = 12; + +} // namespace + +ChaCha20Poly1305Decrypter::ChaCha20Poly1305Decrypter() + : ChaChaBaseDecrypter(EVP_aead_chacha20_poly1305, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ false) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +ChaCha20Poly1305Decrypter::~ChaCha20Poly1305Decrypter() {} + +uint32_t ChaCha20Poly1305Decrypter::cipher_id() const { + return TLS1_CK_CHACHA20_POLY1305_SHA256; +} + +QuicPacketCount ChaCha20Poly1305Decrypter::GetIntegrityLimit() const { + // For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets. + // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage + static_assert(kMaxIncomingPacketSize < 16384, + "This key limit requires limits on decryption payload sizes"); + return 68719476736U; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_decrypter.h b/quiche/quic/core/crypto/chacha20_poly1305_decrypter.h new file mode 100644 index 0000000..6eb6c87 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_decrypter.h
@@ -0,0 +1,41 @@ +// Copyright 2014 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_ + +#include <cstdint> + +#include "quiche/quic/core/crypto/chacha_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// A ChaCha20Poly1305Decrypter is a QuicDecrypter that implements the +// AEAD_CHACHA20_POLY1305 algorithm specified in RFC 7539, except that +// it truncates the Poly1305 authenticator to 12 bytes. Create an instance +// by calling QuicDecrypter::Create(kCC20). +// +// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix of the +// nonce is four bytes. +class QUIC_EXPORT_PRIVATE ChaCha20Poly1305Decrypter + : public ChaChaBaseDecrypter { + public: + enum { + kAuthTagSize = 12, + }; + + ChaCha20Poly1305Decrypter(); + ChaCha20Poly1305Decrypter(const ChaCha20Poly1305Decrypter&) = delete; + ChaCha20Poly1305Decrypter& operator=(const ChaCha20Poly1305Decrypter&) = + delete; + ~ChaCha20Poly1305Decrypter() override; + + uint32_t cipher_id() const override; + QuicPacketCount GetIntegrityLimit() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_decrypter_test.cc b/quiche/quic/core/crypto/chacha20_poly1305_decrypter_test.cc new file mode 100644 index 0000000..019c56b --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_decrypter_test.cc
@@ -0,0 +1,178 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_decrypter.h" + +#include <memory> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The test vectors come from RFC 7539 Section 2.8.2. + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + // Input: + const char* key; + const char* iv; + const char* fixed; + const char* aad; + const char* ct; + + // Expected output: + const char* pt; // An empty string "" means decryption succeeded and + // the plaintext is zero-length. nullptr means decryption + // failed. +}; + +const TestVector test_vectors[] = { + {"808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4041424344454647", + + "07000000", + + "50515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecb", // "d0600691" truncated + + "4c616469657320616e642047656e746c" + "656d656e206f662074686520636c6173" + "73206f66202739393a20496620492063" + "6f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220" + "746865206675747572652c2073756e73" + "637265656e20776f756c642062652069" + "742e"}, + // Modify the ciphertext (Poly1305 authenticator). + {"808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4041424344454647", + + "07000000", + + "50515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecc", // "d0600691" truncated + + nullptr}, + // Modify the associated data. + {"808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4041424344454647", + + "07000000", + + "60515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecb", // "d0600691" truncated + + nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +} // namespace + +namespace quic { +namespace test { + +// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the plaintext. +QuicData* DecryptWithNonce(ChaCha20Poly1305Decrypter* decrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view ciphertext) { + uint64_t packet_number; + absl::string_view nonce_prefix(nonce.data(), + nonce.size() - sizeof(packet_number)); + decrypter->SetNoncePrefix(nonce_prefix); + memcpy(&packet_number, nonce.data() + nonce_prefix.size(), + sizeof(packet_number)); + std::unique_ptr<char[]> output(new char[ciphertext.length()]); + size_t output_length = 0; + const bool success = decrypter->DecryptPacket( + packet_number, associated_data, ciphertext, output.get(), &output_length, + ciphertext.length()); + if (!success) { + return nullptr; + } + return new QuicData(output.release(), output_length, true); +} + +class ChaCha20Poly1305DecrypterTest : public QuicTest {}; + +TEST_F(ChaCha20Poly1305DecrypterTest, Decrypt) { + for (size_t i = 0; test_vectors[i].key != nullptr; i++) { + // If not present then decryption is expected to fail. + bool has_pt = test_vectors[i].pt; + + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[i].key); + std::string iv = absl::HexStringToBytes(test_vectors[i].iv); + std::string fixed = absl::HexStringToBytes(test_vectors[i].fixed); + std::string aad = absl::HexStringToBytes(test_vectors[i].aad); + std::string ct = absl::HexStringToBytes(test_vectors[i].ct); + std::string pt; + if (has_pt) { + pt = absl::HexStringToBytes(test_vectors[i].pt); + } + + ChaCha20Poly1305Decrypter decrypter; + ASSERT_TRUE(decrypter.SetKey(key)); + std::unique_ptr<QuicData> decrypted(DecryptWithNonce( + &decrypter, fixed + iv, + // This deliberately tests that the decrypter can handle an AAD that + // is set to nullptr, as opposed to a zero-length, non-nullptr pointer. + absl::string_view(aad.length() ? aad.data() : nullptr, aad.length()), + ct)); + if (!decrypted) { + EXPECT_FALSE(has_pt); + continue; + } + EXPECT_TRUE(has_pt); + + EXPECT_EQ(12u, ct.size() - decrypted->length()); + ASSERT_EQ(pt.length(), decrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "plaintext", decrypted->data(), pt.length(), pt.data(), pt.length()); + } +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_encrypter.cc b/quiche/quic/core/crypto/chacha20_poly1305_encrypter.cc new file mode 100644 index 0000000..dcfb523 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_encrypter.cc
@@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_encrypter.h" + +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 32; +const size_t kNonceSize = 12; + +} // namespace + +ChaCha20Poly1305Encrypter::ChaCha20Poly1305Encrypter() + : ChaChaBaseEncrypter(EVP_aead_chacha20_poly1305, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ false) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +ChaCha20Poly1305Encrypter::~ChaCha20Poly1305Encrypter() {} + +QuicPacketCount ChaCha20Poly1305Encrypter::GetConfidentialityLimit() const { + // For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the + // number of possible packets (2^62) and so can be disregarded. + // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage + return std::numeric_limits<QuicPacketCount>::max(); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_encrypter.h b/quiche/quic/core/crypto/chacha20_poly1305_encrypter.h new file mode 100644 index 0000000..d37f26c --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_encrypter.h
@@ -0,0 +1,38 @@ +// Copyright 2014 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_ + +#include "quiche/quic/core/crypto/chacha_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// A ChaCha20Poly1305Encrypter is a QuicEncrypter that implements the +// AEAD_CHACHA20_POLY1305 algorithm specified in RFC 7539, except that +// it truncates the Poly1305 authenticator to 12 bytes. Create an instance +// by calling QuicEncrypter::Create(kCC20). +// +// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix of the +// nonce is four bytes. +class QUIC_EXPORT_PRIVATE ChaCha20Poly1305Encrypter + : public ChaChaBaseEncrypter { + public: + enum { + kAuthTagSize = 12, + }; + + ChaCha20Poly1305Encrypter(); + ChaCha20Poly1305Encrypter(const ChaCha20Poly1305Encrypter&) = delete; + ChaCha20Poly1305Encrypter& operator=(const ChaCha20Poly1305Encrypter&) = + delete; + ~ChaCha20Poly1305Encrypter() override; + + QuicPacketCount GetConfidentialityLimit() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_encrypter_test.cc b/quiche/quic/core/crypto/chacha20_poly1305_encrypter_test.cc new file mode 100644 index 0000000..9ae728a --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_encrypter_test.cc
@@ -0,0 +1,159 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_encrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_decrypter.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The test vectors come from RFC 7539 Section 2.8.2. + +// Each test vector consists of five strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + const char* key; + const char* pt; + const char* iv; + const char* fixed; + const char* aad; + const char* ct; +}; + +const TestVector test_vectors[] = { + { + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4c616469657320616e642047656e746c" + "656d656e206f662074686520636c6173" + "73206f66202739393a20496620492063" + "6f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220" + "746865206675747572652c2073756e73" + "637265656e20776f756c642062652069" + "742e", + + "4041424344454647", + + "07000000", + + "50515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecb", // "d0600691" truncated + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +} // namespace + +namespace quic { +namespace test { + +// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the ciphertext. +QuicData* EncryptWithNonce(ChaCha20Poly1305Encrypter* encrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext) { + size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length()); + std::unique_ptr<char[]> ciphertext(new char[ciphertext_size]); + + if (!encrypter->Encrypt(nonce, associated_data, plaintext, + reinterpret_cast<unsigned char*>(ciphertext.get()))) { + return nullptr; + } + + return new QuicData(ciphertext.release(), ciphertext_size, true); +} + +class ChaCha20Poly1305EncrypterTest : public QuicTest {}; + +TEST_F(ChaCha20Poly1305EncrypterTest, EncryptThenDecrypt) { + ChaCha20Poly1305Encrypter encrypter; + ChaCha20Poly1305Decrypter decrypter; + + std::string key = absl::HexStringToBytes(test_vectors[0].key); + ASSERT_TRUE(encrypter.SetKey(key)); + ASSERT_TRUE(decrypter.SetKey(key)); + ASSERT_TRUE(encrypter.SetNoncePrefix("abcd")); + ASSERT_TRUE(decrypter.SetNoncePrefix("abcd")); + + uint64_t packet_number = UINT64_C(0x123456789ABC); + std::string associated_data = "associated_data"; + std::string plaintext = "plaintext"; + char encrypted[1024]; + size_t len; + ASSERT_TRUE(encrypter.EncryptPacket(packet_number, associated_data, plaintext, + encrypted, &len, + ABSL_ARRAYSIZE(encrypted))); + absl::string_view ciphertext(encrypted, len); + char decrypted[1024]; + ASSERT_TRUE(decrypter.DecryptPacket(packet_number, associated_data, + ciphertext, decrypted, &len, + ABSL_ARRAYSIZE(decrypted))); +} + +TEST_F(ChaCha20Poly1305EncrypterTest, Encrypt) { + for (size_t i = 0; test_vectors[i].key != nullptr; i++) { + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[i].key); + std::string pt = absl::HexStringToBytes(test_vectors[i].pt); + std::string iv = absl::HexStringToBytes(test_vectors[i].iv); + std::string fixed = absl::HexStringToBytes(test_vectors[i].fixed); + std::string aad = absl::HexStringToBytes(test_vectors[i].aad); + std::string ct = absl::HexStringToBytes(test_vectors[i].ct); + + ChaCha20Poly1305Encrypter encrypter; + ASSERT_TRUE(encrypter.SetKey(key)); + std::unique_ptr<QuicData> encrypted(EncryptWithNonce( + &encrypter, fixed + iv, + // This deliberately tests that the encrypter can handle an AAD that + // is set to nullptr, as opposed to a zero-length, non-nullptr pointer. + absl::string_view(aad.length() ? aad.data() : nullptr, aad.length()), + pt)); + ASSERT_TRUE(encrypted.get()); + EXPECT_EQ(12u, ct.size() - pt.size()); + EXPECT_EQ(12u, encrypted->length() - pt.size()); + + quiche::test::CompareCharArraysWithHexError("ciphertext", encrypted->data(), + encrypted->length(), ct.data(), + ct.length()); + } +} + +TEST_F(ChaCha20Poly1305EncrypterTest, GetMaxPlaintextSize) { + ChaCha20Poly1305Encrypter encrypter; + EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012)); + EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112)); + EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22)); +} + +TEST_F(ChaCha20Poly1305EncrypterTest, GetCiphertextSize) { + ChaCha20Poly1305Encrypter encrypter; + EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000)); + EXPECT_EQ(112u, encrypter.GetCiphertextSize(100)); + EXPECT_EQ(22u, encrypter.GetCiphertextSize(10)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc b/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc new file mode 100644 index 0000000..57b5f48 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc
@@ -0,0 +1,45 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h" + +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "third_party/boringssl/src/include/openssl/tls1.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 32; +const size_t kNonceSize = 12; + +} // namespace + +ChaCha20Poly1305TlsDecrypter::ChaCha20Poly1305TlsDecrypter() + : ChaChaBaseDecrypter(EVP_aead_chacha20_poly1305, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ true) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +ChaCha20Poly1305TlsDecrypter::~ChaCha20Poly1305TlsDecrypter() {} + +uint32_t ChaCha20Poly1305TlsDecrypter::cipher_id() const { + return TLS1_CK_CHACHA20_POLY1305_SHA256; +} + +QuicPacketCount ChaCha20Poly1305TlsDecrypter::GetIntegrityLimit() const { + // For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets. + // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage + static_assert(kMaxIncomingPacketSize < 16384, + "This key limit requires limits on decryption payload sizes"); + return 68719476736U; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h b/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h new file mode 100644 index 0000000..f8108f2 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h
@@ -0,0 +1,39 @@ +// Copyright 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_TLS_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_TLS_DECRYPTER_H_ + +#include <cstdint> + +#include "quiche/quic/core/crypto/chacha_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// A ChaCha20Poly1305TlsDecrypter is a QuicDecrypter that implements the +// AEAD_CHACHA20_POLY1305 algorithm specified in RFC 7539 for use in IETF QUIC. +// +// It uses an authentication tag of 16 bytes (128 bits). It uses a 12 bytes IV +// that is XOR'd with the packet number to compute the nonce. +class QUIC_EXPORT_PRIVATE ChaCha20Poly1305TlsDecrypter + : public ChaChaBaseDecrypter { + public: + enum { + kAuthTagSize = 16, + }; + + ChaCha20Poly1305TlsDecrypter(); + ChaCha20Poly1305TlsDecrypter(const ChaCha20Poly1305TlsDecrypter&) = delete; + ChaCha20Poly1305TlsDecrypter& operator=(const ChaCha20Poly1305TlsDecrypter&) = + delete; + ~ChaCha20Poly1305TlsDecrypter() override; + + uint32_t cipher_id() const override; + QuicPacketCount GetIntegrityLimit() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_TLS_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc b/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc new file mode 100644 index 0000000..0068636 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc
@@ -0,0 +1,188 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h" + +#include <memory> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The test vectors come from RFC 7539 Section 2.8.2. + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + // Input: + const char* key; + const char* iv; + const char* fixed; + const char* aad; + const char* ct; + + // Expected output: + const char* pt; // An empty string "" means decryption succeeded and + // the plaintext is zero-length. nullptr means decryption + // failed. +}; + +const TestVector test_vectors[] = { + {"808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4041424344454647", + + "07000000", + + "50515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecbd0600691", + + "4c616469657320616e642047656e746c" + "656d656e206f662074686520636c6173" + "73206f66202739393a20496620492063" + "6f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220" + "746865206675747572652c2073756e73" + "637265656e20776f756c642062652069" + "742e"}, + // Modify the ciphertext (Poly1305 authenticator). + {"808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4041424344454647", + + "07000000", + + "50515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902eccd0600691", + + nullptr}, + // Modify the associated data. + {"808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4041424344454647", + + "07000000", + + "60515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecbd0600691", + + nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +} // namespace + +namespace quic { +namespace test { + +// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the plaintext. +QuicData* DecryptWithNonce(ChaCha20Poly1305TlsDecrypter* decrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view ciphertext) { + decrypter->SetIV(nonce); + std::unique_ptr<char[]> output(new char[ciphertext.length()]); + size_t output_length = 0; + const bool success = + decrypter->DecryptPacket(0, associated_data, ciphertext, output.get(), + &output_length, ciphertext.length()); + if (!success) { + return nullptr; + } + return new QuicData(output.release(), output_length, true); +} + +class ChaCha20Poly1305TlsDecrypterTest : public QuicTest {}; + +TEST_F(ChaCha20Poly1305TlsDecrypterTest, Decrypt) { + for (size_t i = 0; test_vectors[i].key != nullptr; i++) { + // If not present then decryption is expected to fail. + bool has_pt = test_vectors[i].pt; + + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[i].key); + std::string iv = absl::HexStringToBytes(test_vectors[i].iv); + std::string fixed = absl::HexStringToBytes(test_vectors[i].fixed); + std::string aad = absl::HexStringToBytes(test_vectors[i].aad); + std::string ct = absl::HexStringToBytes(test_vectors[i].ct); + std::string pt; + if (has_pt) { + pt = absl::HexStringToBytes(test_vectors[i].pt); + } + + ChaCha20Poly1305TlsDecrypter decrypter; + ASSERT_TRUE(decrypter.SetKey(key)); + std::unique_ptr<QuicData> decrypted(DecryptWithNonce( + &decrypter, fixed + iv, + // This deliberately tests that the decrypter can handle an AAD that + // is set to nullptr, as opposed to a zero-length, non-nullptr pointer. + absl::string_view(aad.length() ? aad.data() : nullptr, aad.length()), + ct)); + if (!decrypted) { + EXPECT_FALSE(has_pt); + continue; + } + EXPECT_TRUE(has_pt); + + EXPECT_EQ(16u, ct.size() - decrypted->length()); + ASSERT_EQ(pt.length(), decrypted->length()); + quiche::test::CompareCharArraysWithHexError( + "plaintext", decrypted->data(), pt.length(), pt.data(), pt.length()); + } +} + +TEST_F(ChaCha20Poly1305TlsDecrypterTest, GenerateHeaderProtectionMask) { + ChaCha20Poly1305TlsDecrypter decrypter; + std::string key = absl::HexStringToBytes( + "6a067f432787bd6034dd3f08f07fc9703a27e58c70e2d88d948b7f6489923cc7"); + std::string sample = + absl::HexStringToBytes("1210d91cceb45c716b023f492c29e612"); + QuicDataReader sample_reader(sample.data(), sample.size()); + ASSERT_TRUE(decrypter.SetHeaderProtectionKey(key)); + std::string mask = decrypter.GenerateHeaderProtectionMask(&sample_reader); + std::string expected_mask = absl::HexStringToBytes("1cc2cd98dc"); + quiche::test::CompareCharArraysWithHexError( + "header protection mask", mask.data(), mask.size(), expected_mask.data(), + expected_mask.size()); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc b/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc new file mode 100644 index 0000000..12e3153 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc
@@ -0,0 +1,37 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.h" + +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace quic { + +namespace { + +const size_t kKeySize = 32; +const size_t kNonceSize = 12; + +} // namespace + +ChaCha20Poly1305TlsEncrypter::ChaCha20Poly1305TlsEncrypter() + : ChaChaBaseEncrypter(EVP_aead_chacha20_poly1305, + kKeySize, + kAuthTagSize, + kNonceSize, + /* use_ietf_nonce_construction */ true) { + static_assert(kKeySize <= kMaxKeySize, "key size too big"); + static_assert(kNonceSize <= kMaxNonceSize, "nonce size too big"); +} + +ChaCha20Poly1305TlsEncrypter::~ChaCha20Poly1305TlsEncrypter() {} + +QuicPacketCount ChaCha20Poly1305TlsEncrypter::GetConfidentialityLimit() const { + // For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the + // number of possible packets (2^62) and so can be disregarded. + // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage + return std::numeric_limits<QuicPacketCount>::max(); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.h b/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.h new file mode 100644 index 0000000..e5d8f37 --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.h
@@ -0,0 +1,36 @@ +// Copyright 2017 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_TLS_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_TLS_ENCRYPTER_H_ + +#include "quiche/quic/core/crypto/chacha_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// A ChaCha20Poly1305Encrypter is a QuicEncrypter that implements the +// AEAD_CHACHA20_POLY1305 algorithm specified in RFC 7539 for use in IETF QUIC. +// +// It uses an authentication tag of 16 bytes (128 bits). It uses a 12 byte IV +// that is XOR'd with the packet number to compute the nonce. +class QUIC_EXPORT_PRIVATE ChaCha20Poly1305TlsEncrypter + : public ChaChaBaseEncrypter { + public: + enum { + kAuthTagSize = 16, + }; + + ChaCha20Poly1305TlsEncrypter(); + ChaCha20Poly1305TlsEncrypter(const ChaCha20Poly1305TlsEncrypter&) = delete; + ChaCha20Poly1305TlsEncrypter& operator=(const ChaCha20Poly1305TlsEncrypter&) = + delete; + ~ChaCha20Poly1305TlsEncrypter() override; + + QuicPacketCount GetConfidentialityLimit() const override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHACHA20_POLY1305_TLS_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc b/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc new file mode 100644 index 0000000..322651b --- /dev/null +++ b/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc
@@ -0,0 +1,173 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.h" + +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace { + +// The test vectors come from RFC 7539 Section 2.8.2. + +// Each test vector consists of five strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a nullptr |key| +// marks the end of an array of test vectors. +struct TestVector { + const char* key; + const char* pt; + const char* iv; + const char* fixed; + const char* aad; + const char* ct; +}; + +const TestVector test_vectors[] = { + { + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f", + + "4c616469657320616e642047656e746c" + "656d656e206f662074686520636c6173" + "73206f66202739393a20496620492063" + "6f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220" + "746865206675747572652c2073756e73" + "637265656e20776f756c642062652069" + "742e", + + "4041424344454647", + + "07000000", + + "50515253c0c1c2c3c4c5c6c7", + + "d31a8d34648e60db7b86afbc53ef7ec2" + "a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b" + "1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58" + "fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b" + "6116" + "1ae10b594f09e26a7e902ecbd0600691", + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}}; + +} // namespace + +namespace quic { +namespace test { + +// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing +// in an nonce and also to allocate the buffer needed for the ciphertext. +QuicData* EncryptWithNonce(ChaCha20Poly1305TlsEncrypter* encrypter, + absl::string_view nonce, + absl::string_view associated_data, + absl::string_view plaintext) { + size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length()); + std::unique_ptr<char[]> ciphertext(new char[ciphertext_size]); + + if (!encrypter->Encrypt(nonce, associated_data, plaintext, + reinterpret_cast<unsigned char*>(ciphertext.get()))) { + return nullptr; + } + + return new QuicData(ciphertext.release(), ciphertext_size, true); +} + +class ChaCha20Poly1305TlsEncrypterTest : public QuicTest {}; + +TEST_F(ChaCha20Poly1305TlsEncrypterTest, EncryptThenDecrypt) { + ChaCha20Poly1305TlsEncrypter encrypter; + ChaCha20Poly1305TlsDecrypter decrypter; + + std::string key = absl::HexStringToBytes(test_vectors[0].key); + ASSERT_TRUE(encrypter.SetKey(key)); + ASSERT_TRUE(decrypter.SetKey(key)); + ASSERT_TRUE(encrypter.SetIV("abcdefghijkl")); + ASSERT_TRUE(decrypter.SetIV("abcdefghijkl")); + + uint64_t packet_number = UINT64_C(0x123456789ABC); + std::string associated_data = "associated_data"; + std::string plaintext = "plaintext"; + char encrypted[1024]; + size_t len; + ASSERT_TRUE(encrypter.EncryptPacket(packet_number, associated_data, plaintext, + encrypted, &len, + ABSL_ARRAYSIZE(encrypted))); + absl::string_view ciphertext(encrypted, len); + char decrypted[1024]; + ASSERT_TRUE(decrypter.DecryptPacket(packet_number, associated_data, + ciphertext, decrypted, &len, + ABSL_ARRAYSIZE(decrypted))); +} + +TEST_F(ChaCha20Poly1305TlsEncrypterTest, Encrypt) { + for (size_t i = 0; test_vectors[i].key != nullptr; i++) { + // Decode the test vector. + std::string key = absl::HexStringToBytes(test_vectors[i].key); + std::string pt = absl::HexStringToBytes(test_vectors[i].pt); + std::string iv = absl::HexStringToBytes(test_vectors[i].iv); + std::string fixed = absl::HexStringToBytes(test_vectors[i].fixed); + std::string aad = absl::HexStringToBytes(test_vectors[i].aad); + std::string ct = absl::HexStringToBytes(test_vectors[i].ct); + + ChaCha20Poly1305TlsEncrypter encrypter; + ASSERT_TRUE(encrypter.SetKey(key)); + std::unique_ptr<QuicData> encrypted(EncryptWithNonce( + &encrypter, fixed + iv, + // This deliberately tests that the encrypter can handle an AAD that + // is set to nullptr, as opposed to a zero-length, non-nullptr pointer. + absl::string_view(aad.length() ? aad.data() : nullptr, aad.length()), + pt)); + ASSERT_TRUE(encrypted.get()); + EXPECT_EQ(16u, ct.size() - pt.size()); + EXPECT_EQ(16u, encrypted->length() - pt.size()); + + quiche::test::CompareCharArraysWithHexError("ciphertext", encrypted->data(), + encrypted->length(), ct.data(), + ct.length()); + } +} + +TEST_F(ChaCha20Poly1305TlsEncrypterTest, GetMaxPlaintextSize) { + ChaCha20Poly1305TlsEncrypter encrypter; + EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1016)); + EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(116)); + EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(26)); +} + +TEST_F(ChaCha20Poly1305TlsEncrypterTest, GetCiphertextSize) { + ChaCha20Poly1305TlsEncrypter encrypter; + EXPECT_EQ(1016u, encrypter.GetCiphertextSize(1000)); + EXPECT_EQ(116u, encrypter.GetCiphertextSize(100)); + EXPECT_EQ(26u, encrypter.GetCiphertextSize(10)); +} + +TEST_F(ChaCha20Poly1305TlsEncrypterTest, GenerateHeaderProtectionMask) { + ChaCha20Poly1305TlsEncrypter encrypter; + std::string key = absl::HexStringToBytes( + "6a067f432787bd6034dd3f08f07fc9703a27e58c70e2d88d948b7f6489923cc7"); + std::string sample = + absl::HexStringToBytes("1210d91cceb45c716b023f492c29e612"); + ASSERT_TRUE(encrypter.SetHeaderProtectionKey(key)); + std::string mask = encrypter.GenerateHeaderProtectionMask(sample); + std::string expected_mask = absl::HexStringToBytes("1cc2cd98dc"); + quiche::test::CompareCharArraysWithHexError( + "header protection mask", mask.data(), mask.size(), expected_mask.data(), + expected_mask.size()); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha_base_decrypter.cc b/quiche/quic/core/crypto/chacha_base_decrypter.cc new file mode 100644 index 0000000..ec8e8a9 --- /dev/null +++ b/quiche/quic/core/crypto/chacha_base_decrypter.cc
@@ -0,0 +1,44 @@ +// 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 "quiche/quic/core/crypto/chacha_base_decrypter.h" + +#include <cstdint> + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/chacha.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +bool ChaChaBaseDecrypter::SetHeaderProtectionKey(absl::string_view key) { + if (key.size() != GetKeySize()) { + QUIC_BUG(quic_bug_10620_1) << "Invalid key size for header protection"; + return false; + } + memcpy(pne_key_, key.data(), key.size()); + return true; +} + +std::string ChaChaBaseDecrypter::GenerateHeaderProtectionMask( + QuicDataReader* sample_reader) { + absl::string_view sample; + if (!sample_reader->ReadStringPiece(&sample, 16)) { + return std::string(); + } + const uint8_t* nonce = reinterpret_cast<const uint8_t*>(sample.data()) + 4; + uint32_t counter; + QuicDataReader(sample.data(), 4, quiche::HOST_BYTE_ORDER) + .ReadUInt32(&counter); + const uint8_t zeroes[] = {0, 0, 0, 0, 0}; + std::string out(ABSL_ARRAYSIZE(zeroes), 0); + CRYPTO_chacha_20(reinterpret_cast<uint8_t*>(const_cast<char*>(out.data())), + zeroes, ABSL_ARRAYSIZE(zeroes), pne_key_, nonce, counter); + return out; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha_base_decrypter.h b/quiche/quic/core/crypto/chacha_base_decrypter.h new file mode 100644 index 0000000..5cd08c7 --- /dev/null +++ b/quiche/quic/core/crypto/chacha_base_decrypter.h
@@ -0,0 +1,31 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHACHA_BASE_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHACHA_BASE_DECRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/aead_base_decrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE ChaChaBaseDecrypter : public AeadBaseDecrypter { + public: + using AeadBaseDecrypter::AeadBaseDecrypter; + + bool SetHeaderProtectionKey(absl::string_view key) override; + std::string GenerateHeaderProtectionMask( + QuicDataReader* sample_reader) override; + + private: + // The key used for packet number encryption. + unsigned char pne_key_[kMaxKeySize]; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHACHA_BASE_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/chacha_base_encrypter.cc b/quiche/quic/core/crypto/chacha_base_encrypter.cc new file mode 100644 index 0000000..12c1f55 --- /dev/null +++ b/quiche/quic/core/crypto/chacha_base_encrypter.cc
@@ -0,0 +1,41 @@ +// 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 "quiche/quic/core/crypto/chacha_base_encrypter.h" + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/chacha.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +bool ChaChaBaseEncrypter::SetHeaderProtectionKey(absl::string_view key) { + if (key.size() != GetKeySize()) { + QUIC_BUG(quic_bug_10656_1) << "Invalid key size for header protection"; + return false; + } + memcpy(pne_key_, key.data(), key.size()); + return true; +} + +std::string ChaChaBaseEncrypter::GenerateHeaderProtectionMask( + absl::string_view sample) { + if (sample.size() != 16) { + return std::string(); + } + const uint8_t* nonce = reinterpret_cast<const uint8_t*>(sample.data()) + 4; + uint32_t counter; + QuicDataReader(sample.data(), 4, quiche::HOST_BYTE_ORDER) + .ReadUInt32(&counter); + const uint8_t zeroes[] = {0, 0, 0, 0, 0}; + std::string out(ABSL_ARRAYSIZE(zeroes), 0); + CRYPTO_chacha_20(reinterpret_cast<uint8_t*>(const_cast<char*>(out.data())), + zeroes, ABSL_ARRAYSIZE(zeroes), pne_key_, nonce, counter); + return out; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/chacha_base_encrypter.h b/quiche/quic/core/crypto/chacha_base_encrypter.h new file mode 100644 index 0000000..14773ec --- /dev/null +++ b/quiche/quic/core/crypto/chacha_base_encrypter.h
@@ -0,0 +1,30 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHACHA_BASE_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHACHA_BASE_ENCRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/aead_base_encrypter.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE ChaChaBaseEncrypter : public AeadBaseEncrypter { + public: + using AeadBaseEncrypter::AeadBaseEncrypter; + + bool SetHeaderProtectionKey(absl::string_view key) override; + std::string GenerateHeaderProtectionMask(absl::string_view sample) override; + + private: + // The key used for packet number encryption. + unsigned char pne_key_[kMaxKeySize]; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHACHA_BASE_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/channel_id.cc b/quiche/quic/core/crypto/channel_id.cc new file mode 100644 index 0000000..5e222f1 --- /dev/null +++ b/quiche/quic/core/crypto/channel_id.cc
@@ -0,0 +1,90 @@ +// 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 "quiche/quic/core/crypto/channel_id.h" + +#include <cstdint> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/ec.h" +#include "third_party/boringssl/src/include/openssl/ecdsa.h" +#include "third_party/boringssl/src/include/openssl/nid.h" +#include "third_party/boringssl/src/include/openssl/sha.h" + +namespace quic { + +// static +const char ChannelIDVerifier::kContextStr[] = "QUIC ChannelID"; +// static +const char ChannelIDVerifier::kClientToServerStr[] = "client -> server"; + +// static +bool ChannelIDVerifier::Verify(absl::string_view key, + absl::string_view signed_data, + absl::string_view signature) { + return VerifyRaw(key, signed_data, signature, true); +} + +// static +bool ChannelIDVerifier::VerifyRaw(absl::string_view key, + absl::string_view signed_data, + absl::string_view signature, + bool is_channel_id_signature) { + if (key.size() != 32 * 2 || signature.size() != 32 * 2) { + return false; + } + + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + if (p256.get() == nullptr) { + return false; + } + + bssl::UniquePtr<BIGNUM> x(BN_new()), y(BN_new()), r(BN_new()), s(BN_new()); + + ECDSA_SIG sig; + sig.r = r.get(); + sig.s = s.get(); + + const uint8_t* key_bytes = reinterpret_cast<const uint8_t*>(key.data()); + const uint8_t* signature_bytes = + reinterpret_cast<const uint8_t*>(signature.data()); + + if (BN_bin2bn(key_bytes + 0, 32, x.get()) == nullptr || + BN_bin2bn(key_bytes + 32, 32, y.get()) == nullptr || + BN_bin2bn(signature_bytes + 0, 32, sig.r) == nullptr || + BN_bin2bn(signature_bytes + 32, 32, sig.s) == nullptr) { + return false; + } + + bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); + if (point.get() == nullptr || + !EC_POINT_set_affine_coordinates_GFp(p256.get(), point.get(), x.get(), + y.get(), nullptr)) { + return false; + } + + bssl::UniquePtr<EC_KEY> ecdsa_key(EC_KEY_new()); + if (ecdsa_key.get() == nullptr || + !EC_KEY_set_group(ecdsa_key.get(), p256.get()) || + !EC_KEY_set_public_key(ecdsa_key.get(), point.get())) { + return false; + } + + SHA256_CTX sha256; + SHA256_Init(&sha256); + if (is_channel_id_signature) { + SHA256_Update(&sha256, kContextStr, strlen(kContextStr) + 1); + SHA256_Update(&sha256, kClientToServerStr, strlen(kClientToServerStr) + 1); + } + SHA256_Update(&sha256, signed_data.data(), signed_data.size()); + + unsigned char digest[SHA256_DIGEST_LENGTH]; + SHA256_Final(digest, &sha256); + + return ECDSA_do_verify(digest, sizeof(digest), &sig, ecdsa_key.get()) == 1; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/channel_id.h b/quiche/quic/core/crypto/channel_id.h new file mode 100644 index 0000000..9d20215 --- /dev/null +++ b/quiche/quic/core/crypto/channel_id.h
@@ -0,0 +1,49 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CHANNEL_ID_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CHANNEL_ID_H_ + +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// ChannelIDVerifier verifies ChannelID signatures. +class QUIC_EXPORT_PRIVATE ChannelIDVerifier { + public: + ChannelIDVerifier() = delete; + + // kContextStr is prepended to the data to be signed in order to ensure that + // a ChannelID signature cannot be used in a different context. (The + // terminating NUL byte is inclued.) + static const char kContextStr[]; + // kClientToServerStr follows kContextStr to specify that the ChannelID is + // being used in the client to server direction. (The terminating NUL byte is + // included.) + static const char kClientToServerStr[]; + + // Verify returns true iff |signature| is a valid signature of |signed_data| + // by |key|. + static bool Verify(absl::string_view key, + absl::string_view signed_data, + absl::string_view signature); + + // FOR TESTING ONLY: VerifyRaw returns true iff |signature| is a valid + // signature of |signed_data| by |key|. |is_channel_id_signature| indicates + // whether |signature| is a ChannelID signature (with kContextStr prepended + // to the data to be signed). + static bool VerifyRaw(absl::string_view key, + absl::string_view signed_data, + absl::string_view signature, + bool is_channel_id_signature); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CHANNEL_ID_H_
diff --git a/quiche/quic/core/crypto/channel_id_test.cc b/quiche/quic/core/crypto/channel_id_test.cc new file mode 100644 index 0000000..40e5090 --- /dev/null +++ b/quiche/quic/core/crypto/channel_id_test.cc
@@ -0,0 +1,287 @@ +// 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 "quiche/quic/core/crypto/channel_id.h" + +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" + +namespace quic { +namespace test { + +namespace { + +// The following ECDSA signature verification test vectors for P-256,SHA-256 +// come from the SigVer.rsp file in +// http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3ecdsatestvectors.zip +// downloaded on 2013-06-11. +struct TestVector { + // Input: + const char* msg; + const char* qx; + const char* qy; + const char* r; + const char* s; + + // Expected output: + bool result; // true means "P", false means "F" +}; + +const TestVector test_vector[] = { + { + "e4796db5f785f207aa30d311693b3702821dff1168fd2e04c0836825aefd850d" + "9aa60326d88cde1a23c7745351392ca2288d632c264f197d05cd424a30336c19" + "fd09bb229654f0222fcb881a4b35c290a093ac159ce13409111ff0358411133c" + "24f5b8e2090d6db6558afc36f06ca1f6ef779785adba68db27a409859fc4c4a0", + "87f8f2b218f49845f6f10eec3877136269f5c1a54736dbdf69f89940cad41555", + "e15f369036f49842fac7a86c8a2b0557609776814448b8f5e84aa9f4395205e9", + "d19ff48b324915576416097d2544f7cbdf8768b1454ad20e0baac50e211f23b0", + "a3e81e59311cdfff2d4784949f7a2cb50ba6c3a91fa54710568e61aca3e847c6", + false // F (3 - S changed) + }, + { + "069a6e6b93dfee6df6ef6997cd80dd2182c36653cef10c655d524585655462d6" + "83877f95ecc6d6c81623d8fac4e900ed0019964094e7de91f1481989ae187300" + "4565789cbf5dc56c62aedc63f62f3b894c9c6f7788c8ecaadc9bd0e81ad91b2b" + "3569ea12260e93924fdddd3972af5273198f5efda0746219475017557616170e", + "5cf02a00d205bdfee2016f7421807fc38ae69e6b7ccd064ee689fc1a94a9f7d2", + "ec530ce3cc5c9d1af463f264d685afe2b4db4b5828d7e61b748930f3ce622a85", + "dc23d130c6117fb5751201455e99f36f59aba1a6a21cf2d0e7481a97451d6693", + "d6ce7708c18dbf35d4f8aa7240922dc6823f2e7058cbc1484fcad1599db5018c", + false // F (2 - R changed) + }, + { + "df04a346cf4d0e331a6db78cca2d456d31b0a000aa51441defdb97bbeb20b94d" + "8d746429a393ba88840d661615e07def615a342abedfa4ce912e562af7149598" + "96858af817317a840dcff85a057bb91a3c2bf90105500362754a6dd321cdd861" + "28cfc5f04667b57aa78c112411e42da304f1012d48cd6a7052d7de44ebcc01de", + "2ddfd145767883ffbb0ac003ab4a44346d08fa2570b3120dcce94562422244cb", + "5f70c7d11ac2b7a435ccfbbae02c3df1ea6b532cc0e9db74f93fffca7c6f9a64", + "9913111cff6f20c5bf453a99cd2c2019a4e749a49724a08774d14e4c113edda8", + "9467cd4cd21ecb56b0cab0a9a453b43386845459127a952421f5c6382866c5cc", + false // F (4 - Q changed) + }, + { + "e1130af6a38ccb412a9c8d13e15dbfc9e69a16385af3c3f1e5da954fd5e7c45f" + "d75e2b8c36699228e92840c0562fbf3772f07e17f1add56588dd45f7450e1217" + "ad239922dd9c32695dc71ff2424ca0dec1321aa47064a044b7fe3c2b97d03ce4" + "70a592304c5ef21eed9f93da56bb232d1eeb0035f9bf0dfafdcc4606272b20a3", + "e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c", + "970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927", + "bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f", + "17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c", + true // P (0 ) + }, + { + "73c5f6a67456ae48209b5f85d1e7de7758bf235300c6ae2bdceb1dcb27a7730f" + "b68c950b7fcada0ecc4661d3578230f225a875e69aaa17f1e71c6be5c831f226" + "63bac63d0c7a9635edb0043ff8c6f26470f02a7bc56556f1437f06dfa27b487a" + "6c4290d8bad38d4879b334e341ba092dde4e4ae694a9c09302e2dbf443581c08", + "e0fc6a6f50e1c57475673ee54e3a57f9a49f3328e743bf52f335e3eeaa3d2864", + "7f59d689c91e463607d9194d99faf316e25432870816dde63f5d4b373f12f22a", + "1d75830cd36f4c9aa181b2c4221e87f176b7f05b7c87824e82e396c88315c407", + "cb2acb01dac96efc53a32d4a0d85d0c2e48955214783ecf50a4f0414a319c05a", + true // P (0 ) + }, + { + "666036d9b4a2426ed6585a4e0fd931a8761451d29ab04bd7dc6d0c5b9e38e6c2" + "b263ff6cb837bd04399de3d757c6c7005f6d7a987063cf6d7e8cb38a4bf0d74a" + "282572bd01d0f41e3fd066e3021575f0fa04f27b700d5b7ddddf50965993c3f9" + "c7118ed78888da7cb221849b3260592b8e632d7c51e935a0ceae15207bedd548", + "a849bef575cac3c6920fbce675c3b787136209f855de19ffe2e8d29b31a5ad86", + "bf5fe4f7858f9b805bd8dcc05ad5e7fb889de2f822f3d8b41694e6c55c16b471", + "25acc3aa9d9e84c7abf08f73fa4195acc506491d6fc37cb9074528a7db87b9d6", + "9b21d5b5259ed3f2ef07dfec6cc90d3a37855d1ce122a85ba6a333f307d31537", + false // F (2 - R changed) + }, + { + "7e80436bce57339ce8da1b5660149a20240b146d108deef3ec5da4ae256f8f89" + "4edcbbc57b34ce37089c0daa17f0c46cd82b5a1599314fd79d2fd2f446bd5a25" + "b8e32fcf05b76d644573a6df4ad1dfea707b479d97237a346f1ec632ea5660ef" + "b57e8717a8628d7f82af50a4e84b11f21bdff6839196a880ae20b2a0918d58cd", + "3dfb6f40f2471b29b77fdccba72d37c21bba019efa40c1c8f91ec405d7dcc5df", + "f22f953f1e395a52ead7f3ae3fc47451b438117b1e04d613bc8555b7d6e6d1bb", + "548886278e5ec26bed811dbb72db1e154b6f17be70deb1b210107decb1ec2a5a", + "e93bfebd2f14f3d827ca32b464be6e69187f5edbd52def4f96599c37d58eee75", + false // F (4 - Q changed) + }, + { + "1669bfb657fdc62c3ddd63269787fc1c969f1850fb04c933dda063ef74a56ce1" + "3e3a649700820f0061efabf849a85d474326c8a541d99830eea8131eaea584f2" + "2d88c353965dabcdc4bf6b55949fd529507dfb803ab6b480cd73ca0ba00ca19c" + "438849e2cea262a1c57d8f81cd257fb58e19dec7904da97d8386e87b84948169", + "69b7667056e1e11d6caf6e45643f8b21e7a4bebda463c7fdbc13bc98efbd0214", + "d3f9b12eb46c7c6fda0da3fc85bc1fd831557f9abc902a3be3cb3e8be7d1aa2f", + "288f7a1cd391842cce21f00e6f15471c04dc182fe4b14d92dc18910879799790", + "247b3c4e89a3bcadfea73c7bfd361def43715fa382b8c3edf4ae15d6e55e9979", + false // F (1 - Message changed) + }, + { + "3fe60dd9ad6caccf5a6f583b3ae65953563446c4510b70da115ffaa0ba04c076" + "115c7043ab8733403cd69c7d14c212c655c07b43a7c71b9a4cffe22c2684788e" + "c6870dc2013f269172c822256f9e7cc674791bf2d8486c0f5684283e1649576e" + "fc982ede17c7b74b214754d70402fb4bb45ad086cf2cf76b3d63f7fce39ac970", + "bf02cbcf6d8cc26e91766d8af0b164fc5968535e84c158eb3bc4e2d79c3cc682", + "069ba6cb06b49d60812066afa16ecf7b51352f2c03bd93ec220822b1f3dfba03", + "f5acb06c59c2b4927fb852faa07faf4b1852bbb5d06840935e849c4d293d1bad", + "049dab79c89cc02f1484c437f523e080a75f134917fda752f2d5ca397addfe5d", + false // F (3 - S changed) + }, + { + "983a71b9994d95e876d84d28946a041f8f0a3f544cfcc055496580f1dfd4e312" + "a2ad418fe69dbc61db230cc0c0ed97e360abab7d6ff4b81ee970a7e97466acfd" + "9644f828ffec538abc383d0e92326d1c88c55e1f46a668a039beaa1be631a891" + "29938c00a81a3ae46d4aecbf9707f764dbaccea3ef7665e4c4307fa0b0a3075c", + "224a4d65b958f6d6afb2904863efd2a734b31798884801fcab5a590f4d6da9de", + "178d51fddada62806f097aa615d33b8f2404e6b1479f5fd4859d595734d6d2b9", + "87b93ee2fecfda54deb8dff8e426f3c72c8864991f8ec2b3205bb3b416de93d2", + "4044a24df85be0cc76f21a4430b75b8e77b932a87f51e4eccbc45c263ebf8f66", + false // F (2 - R changed) + }, + { + "4a8c071ac4fd0d52faa407b0fe5dab759f7394a5832127f2a3498f34aac28733" + "9e043b4ffa79528faf199dc917f7b066ad65505dab0e11e6948515052ce20cfd" + "b892ffb8aa9bf3f1aa5be30a5bbe85823bddf70b39fd7ebd4a93a2f75472c1d4" + "f606247a9821f1a8c45a6cb80545de2e0c6c0174e2392088c754e9c8443eb5af", + "43691c7795a57ead8c5c68536fe934538d46f12889680a9cb6d055a066228369", + "f8790110b3c3b281aa1eae037d4f1234aff587d903d93ba3af225c27ddc9ccac", + "8acd62e8c262fa50dd9840480969f4ef70f218ebf8ef9584f199031132c6b1ce", + "cfca7ed3d4347fb2a29e526b43c348ae1ce6c60d44f3191b6d8ea3a2d9c92154", + false // F (3 - S changed) + }, + { + "0a3a12c3084c865daf1d302c78215d39bfe0b8bf28272b3c0b74beb4b7409db0" + "718239de700785581514321c6440a4bbaea4c76fa47401e151e68cb6c29017f0" + "bce4631290af5ea5e2bf3ed742ae110b04ade83a5dbd7358f29a85938e23d87a" + "c8233072b79c94670ff0959f9c7f4517862ff829452096c78f5f2e9a7e4e9216", + "9157dbfcf8cf385f5bb1568ad5c6e2a8652ba6dfc63bc1753edf5268cb7eb596", + "972570f4313d47fc96f7c02d5594d77d46f91e949808825b3d31f029e8296405", + "dfaea6f297fa320b707866125c2a7d5d515b51a503bee817de9faa343cc48eeb", + "8f780ad713f9c3e5a4f7fa4c519833dfefc6a7432389b1e4af463961f09764f2", + false // F (1 - Message changed) + }, + { + "785d07a3c54f63dca11f5d1a5f496ee2c2f9288e55007e666c78b007d95cc285" + "81dce51f490b30fa73dc9e2d45d075d7e3a95fb8a9e1465ad191904124160b7c" + "60fa720ef4ef1c5d2998f40570ae2a870ef3e894c2bc617d8a1dc85c3c557749" + "28c38789b4e661349d3f84d2441a3b856a76949b9f1f80bc161648a1cad5588e", + "072b10c081a4c1713a294f248aef850e297991aca47fa96a7470abe3b8acfdda", + "9581145cca04a0fb94cedce752c8f0370861916d2a94e7c647c5373ce6a4c8f5", + "09f5483eccec80f9d104815a1be9cc1a8e5b12b6eb482a65c6907b7480cf4f19", + "a4f90e560c5e4eb8696cb276e5165b6a9d486345dedfb094a76e8442d026378d", + false // F (4 - Q changed) + }, + { + "76f987ec5448dd72219bd30bf6b66b0775c80b394851a43ff1f537f140a6e722" + "9ef8cd72ad58b1d2d20298539d6347dd5598812bc65323aceaf05228f738b5ad" + "3e8d9fe4100fd767c2f098c77cb99c2992843ba3eed91d32444f3b6db6cd212d" + "d4e5609548f4bb62812a920f6e2bf1581be1ebeebdd06ec4e971862cc42055ca", + "09308ea5bfad6e5adf408634b3d5ce9240d35442f7fe116452aaec0d25be8c24", + "f40c93e023ef494b1c3079b2d10ef67f3170740495ce2cc57f8ee4b0618b8ee5", + "5cc8aa7c35743ec0c23dde88dabd5e4fcd0192d2116f6926fef788cddb754e73", + "9c9c045ebaa1b828c32f82ace0d18daebf5e156eb7cbfdc1eff4399a8a900ae7", + false // F (1 - Message changed) + }, + { + "60cd64b2cd2be6c33859b94875120361a24085f3765cb8b2bf11e026fa9d8855" + "dbe435acf7882e84f3c7857f96e2baab4d9afe4588e4a82e17a78827bfdb5ddb" + "d1c211fbc2e6d884cddd7cb9d90d5bf4a7311b83f352508033812c776a0e00c0" + "03c7e0d628e50736c7512df0acfa9f2320bd102229f46495ae6d0857cc452a84", + "2d98ea01f754d34bbc3003df5050200abf445ec728556d7ed7d5c54c55552b6d", + "9b52672742d637a32add056dfd6d8792f2a33c2e69dafabea09b960bc61e230a", + "06108e525f845d0155bf60193222b3219c98e3d49424c2fb2a0987f825c17959", + "62b5cdd591e5b507e560167ba8f6f7cda74673eb315680cb89ccbc4eec477dce", + true // P (0 ) + }, + {nullptr, nullptr, nullptr, nullptr, nullptr, false}}; + +// Returns true if |ch| is a lowercase hexadecimal digit. +bool IsHexDigit(char ch) { + return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f'); +} + +// Converts a lowercase hexadecimal digit to its integer value. +int HexDigitToInt(char ch) { + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } + return ch - 'a' + 10; +} + +// |in| is a string consisting of lowercase hexadecimal digits, where +// every two digits represent one byte. |out| is a buffer of size |max_len|. +// Converts |in| to bytes and stores the bytes in the |out| buffer. The +// number of bytes converted is returned in |*out_len|. Returns true on +// success, false on failure. +bool DecodeHexString(const char* in, + char* out, + size_t* out_len, + size_t max_len) { + if (!in) { + *out_len = static_cast<size_t>(-1); + return true; + } + *out_len = 0; + while (*in != '\0') { + if (!IsHexDigit(*in) || !IsHexDigit(*(in + 1))) { + return false; + } + if (*out_len >= max_len) { + return false; + } + out[*out_len] = HexDigitToInt(*in) * 16 + HexDigitToInt(*(in + 1)); + (*out_len)++; + in += 2; + } + return true; +} + +} // namespace + +class ChannelIDTest : public QuicTest {}; + +// A known answer test for ChannelIDVerifier. +TEST_F(ChannelIDTest, VerifyKnownAnswerTest) { + char msg[1024]; + size_t msg_len; + char key[64]; + size_t qx_len; + size_t qy_len; + char signature[64]; + size_t r_len; + size_t s_len; + + for (size_t i = 0; test_vector[i].msg != nullptr; i++) { + SCOPED_TRACE(i); + // Decode the test vector. + ASSERT_TRUE( + DecodeHexString(test_vector[i].msg, msg, &msg_len, sizeof(msg))); + ASSERT_TRUE(DecodeHexString(test_vector[i].qx, key, &qx_len, sizeof(key))); + ASSERT_TRUE(DecodeHexString(test_vector[i].qy, key + qx_len, &qy_len, + sizeof(key) - qx_len)); + ASSERT_TRUE(DecodeHexString(test_vector[i].r, signature, &r_len, + sizeof(signature))); + ASSERT_TRUE(DecodeHexString(test_vector[i].s, signature + r_len, &s_len, + sizeof(signature) - r_len)); + + // The test vector's lengths should look sane. + EXPECT_EQ(sizeof(key) / 2, qx_len); + EXPECT_EQ(sizeof(key) / 2, qy_len); + EXPECT_EQ(sizeof(signature) / 2, r_len); + EXPECT_EQ(sizeof(signature) / 2, s_len); + + EXPECT_EQ(test_vector[i].result, + ChannelIDVerifier::VerifyRaw( + absl::string_view(key, sizeof(key)), + absl::string_view(msg, msg_len), + absl::string_view(signature, sizeof(signature)), false)); + } +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/client_proof_source.cc b/quiche/quic/core/crypto/client_proof_source.cc new file mode 100644 index 0000000..9d4795c --- /dev/null +++ b/quiche/quic/core/crypto/client_proof_source.cc
@@ -0,0 +1,62 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/client_proof_source.h" + +#include "absl/strings/match.h" + +namespace quic { + +bool DefaultClientProofSource::AddCertAndKey( + std::vector<std::string> server_hostnames, + quiche::QuicheReferenceCountedPointer<Chain> chain, + CertificatePrivateKey private_key) { + if (!ValidateCertAndKey(chain, private_key)) { + return false; + } + + auto cert_and_key = + std::make_shared<CertAndKey>(std::move(chain), std::move(private_key)); + for (const std::string& domain : server_hostnames) { + cert_and_keys_[domain] = cert_and_key; + } + return true; +} + +const ClientProofSource::CertAndKey* DefaultClientProofSource::GetCertAndKey( + absl::string_view hostname) const { + const CertAndKey* result = LookupExact(hostname); + if (result != nullptr || hostname == "*") { + return result; + } + + // Either a full or a wildcard domain lookup failed. In the former case, + // derive the wildcard domain and look it up. + if (hostname.size() > 1 && !absl::StartsWith(hostname, "*.")) { + auto dot_pos = hostname.find('.'); + if (dot_pos != std::string::npos) { + std::string wildcard = absl::StrCat("*", hostname.substr(dot_pos)); + const CertAndKey* result = LookupExact(wildcard); + if (result != nullptr) { + return result; + } + } + } + + // Return default cert, if any. + return LookupExact("*"); +} + +const ClientProofSource::CertAndKey* DefaultClientProofSource::LookupExact( + absl::string_view map_key) const { + const auto it = cert_and_keys_.find(map_key); + QUIC_DVLOG(1) << "LookupExact(" << map_key + << ") found:" << (it != cert_and_keys_.end()); + if (it != cert_and_keys_.end()) { + return it->second.get(); + } + return nullptr; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/client_proof_source.h b/quiche/quic/core/crypto/client_proof_source.h new file mode 100644 index 0000000..d1450f7 --- /dev/null +++ b/quiche/quic/core/crypto/client_proof_source.h
@@ -0,0 +1,70 @@ +// Copyright (c) 2021 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CLIENT_PROOF_SOURCE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CLIENT_PROOF_SOURCE_H_ + +#include <memory> + +#include "absl/container/flat_hash_map.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/proof_source.h" + +namespace quic { + +// ClientProofSource is the interface for a QUIC client to provide client certs +// and keys based on server hostname. It is only used by TLS handshakes. +class QUIC_EXPORT_PRIVATE ClientProofSource { + public: + using Chain = ProofSource::Chain; + + virtual ~ClientProofSource() {} + + struct QUIC_EXPORT_PRIVATE CertAndKey { + CertAndKey(quiche::QuicheReferenceCountedPointer<Chain> chain, + CertificatePrivateKey private_key) + : chain(std::move(chain)), private_key(std::move(private_key)) {} + + quiche::QuicheReferenceCountedPointer<Chain> chain; + CertificatePrivateKey private_key; + }; + + // Get the client certificate to be sent to the server with |server_hostname| + // and its corresponding private key. It returns nullptr if the cert and key + // can not be found. + // + // |server_hostname| is typically a full domain name(www.foo.com), but it + // could also be a wildcard domain(*.foo.com), or a "*" which will return the + // default cert. + virtual const CertAndKey* GetCertAndKey( + absl::string_view server_hostname) const = 0; +}; + +// DefaultClientProofSource is an implementation that simply keeps an in memory +// map of server hostnames to certs. +class QUIC_EXPORT_PRIVATE DefaultClientProofSource : public ClientProofSource { + public: + ~DefaultClientProofSource() override {} + + // Associate all hostnames in |server_hostnames| with {|chain|,|private_key|}. + // Elements of |server_hostnames| can be full domain names(www.foo.com), + // wildcard domains(*.foo.com), or "*" which means the given cert chain is the + // default one. + // If any element of |server_hostnames| is already associated with a cert + // chain, it will be updated to be associated with the new cert chain. + bool AddCertAndKey(std::vector<std::string> server_hostnames, + quiche::QuicheReferenceCountedPointer<Chain> chain, + CertificatePrivateKey private_key); + + // ClientProofSource implementation + const CertAndKey* GetCertAndKey(absl::string_view hostname) const override; + + private: + const CertAndKey* LookupExact(absl::string_view map_key) const; + absl::flat_hash_map<std::string, std::shared_ptr<CertAndKey>> cert_and_keys_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CLIENT_PROOF_SOURCE_H_
diff --git a/quiche/quic/core/crypto/client_proof_source_test.cc b/quiche/quic/core/crypto/client_proof_source_test.cc new file mode 100644 index 0000000..a35e0aa --- /dev/null +++ b/quiche/quic/core/crypto/client_proof_source_test.cc
@@ -0,0 +1,215 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/client_proof_source.h" + +#include "quiche/quic/platform/api/quic_expect_bug.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/test_certificates.h" + +namespace quic { +namespace test { + +quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain> +TestCertChain() { + return quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>( + new ClientProofSource::Chain({std::string(kTestCertificate)})); +} + +CertificatePrivateKey TestPrivateKey() { + CBS private_key_cbs; + CBS_init(&private_key_cbs, + reinterpret_cast<const uint8_t*>(kTestCertificatePrivateKey.data()), + kTestCertificatePrivateKey.size()); + + return CertificatePrivateKey( + bssl::UniquePtr<EVP_PKEY>(EVP_parse_private_key(&private_key_cbs))); +} + +const ClientProofSource::CertAndKey* TestCertAndKey() { + static const ClientProofSource::CertAndKey cert_and_key(TestCertChain(), + TestPrivateKey()); + return &cert_and_key; +} + +quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain> +NullCertChain() { + return quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(); +} + +quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain> +EmptyCertChain() { + return quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>( + new ClientProofSource::Chain(std::vector<std::string>())); +} + +quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain> BadCertChain() { + return quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>( + new ClientProofSource::Chain({"This is the content of a bad cert."})); +} + +CertificatePrivateKey EmptyPrivateKey() { + return CertificatePrivateKey(bssl::UniquePtr<EVP_PKEY>(EVP_PKEY_new())); +} + +#define VERIFY_CERT_AND_KEY_MATCHES(lhs, rhs) \ + do { \ + SCOPED_TRACE(testing::Message()); \ + VerifyCertAndKeyMatches(lhs, rhs); \ + } while (0) + +void VerifyCertAndKeyMatches(const ClientProofSource::CertAndKey* lhs, + const ClientProofSource::CertAndKey* rhs) { + if (lhs == rhs) { + return; + } + + if (lhs == nullptr) { + ADD_FAILURE() << "lhs is nullptr, but rhs is not"; + return; + } + + if (rhs == nullptr) { + ADD_FAILURE() << "rhs is nullptr, but lhs is not"; + return; + } + + if (1 != EVP_PKEY_cmp(lhs->private_key.private_key(), + rhs->private_key.private_key())) { + ADD_FAILURE() << "Private keys mismatch"; + return; + } + + const ClientProofSource::Chain* lhs_chain = lhs->chain.get(); + const ClientProofSource::Chain* rhs_chain = rhs->chain.get(); + + if (lhs_chain == rhs_chain) { + return; + } + + if (lhs_chain == nullptr) { + ADD_FAILURE() << "lhs->chain is nullptr, but rhs->chain is not"; + return; + } + + if (rhs_chain == nullptr) { + ADD_FAILURE() << "rhs->chain is nullptr, but lhs->chain is not"; + return; + } + + if (lhs_chain->certs.size() != rhs_chain->certs.size()) { + ADD_FAILURE() << "Cert chain length differ. lhs:" << lhs_chain->certs.size() + << ", rhs:" << rhs_chain->certs.size(); + return; + } + + for (size_t i = 0; i < lhs_chain->certs.size(); ++i) { + if (lhs_chain->certs[i] != rhs_chain->certs[i]) { + ADD_FAILURE() << "The " << i << "-th certs differ."; + return; + } + } + + // All good. +} + +TEST(DefaultClientProofSource, FullDomain) { + DefaultClientProofSource proof_source; + ASSERT_TRUE(proof_source.AddCertAndKey({"www.google.com"}, TestCertChain(), + TestPrivateKey())); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("www.google.com"), + TestCertAndKey()); + EXPECT_EQ(proof_source.GetCertAndKey("*.google.com"), nullptr); + EXPECT_EQ(proof_source.GetCertAndKey("*"), nullptr); +} + +TEST(DefaultClientProofSource, WildcardDomain) { + DefaultClientProofSource proof_source; + ASSERT_TRUE(proof_source.AddCertAndKey({"*.google.com"}, TestCertChain(), + TestPrivateKey())); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("www.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("*.google.com"), + TestCertAndKey()); + EXPECT_EQ(proof_source.GetCertAndKey("*"), nullptr); +} + +TEST(DefaultClientProofSource, DefaultDomain) { + DefaultClientProofSource proof_source; + ASSERT_TRUE( + proof_source.AddCertAndKey({"*"}, TestCertChain(), TestPrivateKey())); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("www.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("*.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("*"), + TestCertAndKey()); +} + +TEST(DefaultClientProofSource, FullAndWildcard) { + DefaultClientProofSource proof_source; + ASSERT_TRUE(proof_source.AddCertAndKey({"www.google.com", "*.google.com"}, + TestCertChain(), TestPrivateKey())); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("www.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("foo.google.com"), + TestCertAndKey()); + EXPECT_EQ(proof_source.GetCertAndKey("www.example.com"), nullptr); + EXPECT_EQ(proof_source.GetCertAndKey("*"), nullptr); +} + +TEST(DefaultClientProofSource, FullWildcardAndDefault) { + DefaultClientProofSource proof_source; + ASSERT_TRUE( + proof_source.AddCertAndKey({"www.google.com", "*.google.com", "*"}, + TestCertChain(), TestPrivateKey())); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("www.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("foo.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("www.example.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("*.google.com"), + TestCertAndKey()); + VERIFY_CERT_AND_KEY_MATCHES(proof_source.GetCertAndKey("*"), + TestCertAndKey()); +} + +TEST(DefaultClientProofSource, EmptyCerts) { + DefaultClientProofSource proof_source; + bool ok; + EXPECT_QUIC_BUG( + ok = proof_source.AddCertAndKey({"*"}, NullCertChain(), TestPrivateKey()), + "Certificate chain is empty"); + ASSERT_FALSE(ok); + + EXPECT_QUIC_BUG(ok = proof_source.AddCertAndKey({"*"}, EmptyCertChain(), + TestPrivateKey()), + "Certificate chain is empty"); + ASSERT_FALSE(ok); + EXPECT_EQ(proof_source.GetCertAndKey("*"), nullptr); +} + +TEST(DefaultClientProofSource, BadCerts) { + DefaultClientProofSource proof_source; + bool ok; + EXPECT_QUIC_BUG( + ok = proof_source.AddCertAndKey({"*"}, BadCertChain(), TestPrivateKey()), + "Unabled to parse leaf certificate"); + ASSERT_FALSE(ok); + EXPECT_EQ(proof_source.GetCertAndKey("*"), nullptr); +} + +TEST(DefaultClientProofSource, KeyMismatch) { + DefaultClientProofSource proof_source; + bool ok; + EXPECT_QUIC_BUG(ok = proof_source.AddCertAndKey( + {"www.google.com"}, TestCertChain(), EmptyPrivateKey()), + "Private key does not match the leaf certificate"); + ASSERT_FALSE(ok); + EXPECT_EQ(proof_source.GetCertAndKey("*"), nullptr); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_framer.cc b/quiche/quic/core/crypto/crypto_framer.cc new file mode 100644 index 0000000..db4e0b0 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_framer.cc
@@ -0,0 +1,358 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/crypto_framer.h" + +#include <string> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +namespace { + +const size_t kQuicTagSize = sizeof(QuicTag); +const size_t kCryptoEndOffsetSize = sizeof(uint32_t); +const size_t kNumEntriesSize = sizeof(uint16_t); + +// OneShotVisitor is a framer visitor that records a single handshake message. +class OneShotVisitor : public CryptoFramerVisitorInterface { + public: + OneShotVisitor() : error_(false) {} + + void OnError(CryptoFramer* /*framer*/) override { error_ = true; } + + void OnHandshakeMessage(const CryptoHandshakeMessage& message) override { + out_ = std::make_unique<CryptoHandshakeMessage>(message); + } + + bool error() const { return error_; } + + std::unique_ptr<CryptoHandshakeMessage> release() { return std::move(out_); } + + private: + std::unique_ptr<CryptoHandshakeMessage> out_; + bool error_; +}; + +} // namespace + +CryptoFramer::CryptoFramer() + : visitor_(nullptr), + error_detail_(""), + num_entries_(0), + values_len_(0), + process_truncated_messages_(false) { + Clear(); +} + +CryptoFramer::~CryptoFramer() {} + +// static +std::unique_ptr<CryptoHandshakeMessage> CryptoFramer::ParseMessage( + absl::string_view in) { + OneShotVisitor visitor; + CryptoFramer framer; + + framer.set_visitor(&visitor); + if (!framer.ProcessInput(in) || visitor.error() || + framer.InputBytesRemaining()) { + return nullptr; + } + + return visitor.release(); +} + +QuicErrorCode CryptoFramer::error() const { + return error_; +} + +const std::string& CryptoFramer::error_detail() const { + return error_detail_; +} + +bool CryptoFramer::ProcessInput(absl::string_view input, + EncryptionLevel /*level*/) { + return ProcessInput(input); +} + +bool CryptoFramer::ProcessInput(absl::string_view input) { + QUICHE_DCHECK_EQ(QUIC_NO_ERROR, error_); + if (error_ != QUIC_NO_ERROR) { + return false; + } + error_ = Process(input); + if (error_ != QUIC_NO_ERROR) { + QUICHE_DCHECK(!error_detail_.empty()); + visitor_->OnError(this); + return false; + } + + return true; +} + +size_t CryptoFramer::InputBytesRemaining() const { + return buffer_.length(); +} + +bool CryptoFramer::HasTag(QuicTag tag) const { + if (state_ != STATE_READING_VALUES) { + return false; + } + for (const auto& it : tags_and_lengths_) { + if (it.first == tag) { + return true; + } + } + return false; +} + +void CryptoFramer::ForceHandshake() { + QuicDataReader reader(buffer_.data(), buffer_.length(), + quiche::HOST_BYTE_ORDER); + for (const std::pair<QuicTag, size_t>& item : tags_and_lengths_) { + absl::string_view value; + if (reader.BytesRemaining() < item.second) { + break; + } + reader.ReadStringPiece(&value, item.second); + message_.SetStringPiece(item.first, value); + } + visitor_->OnHandshakeMessage(message_); +} + +// static +std::unique_ptr<QuicData> CryptoFramer::ConstructHandshakeMessage( + const CryptoHandshakeMessage& message) { + size_t num_entries = message.tag_value_map().size(); + size_t pad_length = 0; + bool need_pad_tag = false; + bool need_pad_value = false; + + size_t len = message.size(); + if (len < message.minimum_size()) { + need_pad_tag = true; + need_pad_value = true; + num_entries++; + + size_t delta = message.minimum_size() - len; + const size_t overhead = kQuicTagSize + kCryptoEndOffsetSize; + if (delta > overhead) { + pad_length = delta - overhead; + } + len += overhead + pad_length; + } + + if (num_entries > kMaxEntries) { + return nullptr; + } + + std::unique_ptr<char[]> buffer(new char[len]); + QuicDataWriter writer(len, buffer.get(), quiche::HOST_BYTE_ORDER); + if (!writer.WriteTag(message.tag())) { + QUICHE_DCHECK(false) << "Failed to write message tag."; + return nullptr; + } + if (!writer.WriteUInt16(static_cast<uint16_t>(num_entries))) { + QUICHE_DCHECK(false) << "Failed to write size."; + return nullptr; + } + if (!writer.WriteUInt16(0)) { + QUICHE_DCHECK(false) << "Failed to write padding."; + return nullptr; + } + + uint32_t end_offset = 0; + // Tags and offsets + for (auto it = message.tag_value_map().begin(); + it != message.tag_value_map().end(); ++it) { + if (it->first == kPAD && need_pad_tag) { + // Existing PAD tags are only checked when padding needs to be added + // because parts of the code may need to reserialize received messages + // and those messages may, legitimately include padding. + QUICHE_DCHECK(false) + << "Message needed padding but already contained a PAD tag"; + return nullptr; + } + + if (it->first > kPAD && need_pad_tag) { + need_pad_tag = false; + if (!WritePadTag(&writer, pad_length, &end_offset)) { + return nullptr; + } + } + + if (!writer.WriteTag(it->first)) { + QUICHE_DCHECK(false) << "Failed to write tag."; + return nullptr; + } + end_offset += it->second.length(); + if (!writer.WriteUInt32(end_offset)) { + QUICHE_DCHECK(false) << "Failed to write end offset."; + return nullptr; + } + } + + if (need_pad_tag) { + if (!WritePadTag(&writer, pad_length, &end_offset)) { + return nullptr; + } + } + + // Values + for (auto it = message.tag_value_map().begin(); + it != message.tag_value_map().end(); ++it) { + if (it->first > kPAD && need_pad_value) { + need_pad_value = false; + if (!writer.WriteRepeatedByte('-', pad_length)) { + QUICHE_DCHECK(false) << "Failed to write padding."; + return nullptr; + } + } + + if (!writer.WriteBytes(it->second.data(), it->second.length())) { + QUICHE_DCHECK(false) << "Failed to write value."; + return nullptr; + } + } + + if (need_pad_value) { + if (!writer.WriteRepeatedByte('-', pad_length)) { + QUICHE_DCHECK(false) << "Failed to write padding."; + return nullptr; + } + } + + return std::make_unique<QuicData>(buffer.release(), len, true); +} + +void CryptoFramer::Clear() { + message_.Clear(); + tags_and_lengths_.clear(); + error_ = QUIC_NO_ERROR; + error_detail_ = ""; + state_ = STATE_READING_TAG; +} + +QuicErrorCode CryptoFramer::Process(absl::string_view input) { + // Add this data to the buffer. + buffer_.append(input.data(), input.length()); + QuicDataReader reader(buffer_.data(), buffer_.length(), + quiche::HOST_BYTE_ORDER); + + switch (state_) { + case STATE_READING_TAG: + if (reader.BytesRemaining() < kQuicTagSize) { + break; + } + QuicTag message_tag; + reader.ReadTag(&message_tag); + message_.set_tag(message_tag); + state_ = STATE_READING_NUM_ENTRIES; + ABSL_FALLTHROUGH_INTENDED; + case STATE_READING_NUM_ENTRIES: + if (reader.BytesRemaining() < kNumEntriesSize + sizeof(uint16_t)) { + break; + } + reader.ReadUInt16(&num_entries_); + if (num_entries_ > kMaxEntries) { + error_detail_ = absl::StrCat(num_entries_, " entries"); + return QUIC_CRYPTO_TOO_MANY_ENTRIES; + } + uint16_t padding; + reader.ReadUInt16(&padding); + + tags_and_lengths_.reserve(num_entries_); + state_ = STATE_READING_TAGS_AND_LENGTHS; + values_len_ = 0; + ABSL_FALLTHROUGH_INTENDED; + case STATE_READING_TAGS_AND_LENGTHS: { + if (reader.BytesRemaining() < + num_entries_ * (kQuicTagSize + kCryptoEndOffsetSize)) { + break; + } + + uint32_t last_end_offset = 0; + for (unsigned i = 0; i < num_entries_; ++i) { + QuicTag tag; + reader.ReadTag(&tag); + if (i > 0 && tag <= tags_and_lengths_[i - 1].first) { + if (tag == tags_and_lengths_[i - 1].first) { + error_detail_ = absl::StrCat("Duplicate tag:", tag); + return QUIC_CRYPTO_DUPLICATE_TAG; + } + error_detail_ = absl::StrCat("Tag ", tag, " out of order"); + return QUIC_CRYPTO_TAGS_OUT_OF_ORDER; + } + + uint32_t end_offset; + reader.ReadUInt32(&end_offset); + + if (end_offset < last_end_offset) { + error_detail_ = + absl::StrCat("End offset: ", end_offset, " vs ", last_end_offset); + return QUIC_CRYPTO_TAGS_OUT_OF_ORDER; + } + tags_and_lengths_.push_back(std::make_pair( + tag, static_cast<size_t>(end_offset - last_end_offset))); + last_end_offset = end_offset; + } + values_len_ = last_end_offset; + state_ = STATE_READING_VALUES; + ABSL_FALLTHROUGH_INTENDED; + } + case STATE_READING_VALUES: + if (reader.BytesRemaining() < values_len_) { + if (!process_truncated_messages_) { + break; + } + QUIC_LOG(ERROR) << "Trunacted message. Missing " + << values_len_ - reader.BytesRemaining() << " bytes."; + } + for (const std::pair<QuicTag, size_t>& item : tags_and_lengths_) { + absl::string_view value; + if (!reader.ReadStringPiece(&value, item.second)) { + QUICHE_DCHECK(process_truncated_messages_); + // Store an empty value. + message_.SetStringPiece(item.first, ""); + continue; + } + message_.SetStringPiece(item.first, value); + } + visitor_->OnHandshakeMessage(message_); + Clear(); + state_ = STATE_READING_TAG; + break; + } + // Save any remaining data. + buffer_ = std::string(reader.PeekRemainingPayload()); + return QUIC_NO_ERROR; +} + +// static +bool CryptoFramer::WritePadTag(QuicDataWriter* writer, + size_t pad_length, + uint32_t* end_offset) { + if (!writer->WriteTag(kPAD)) { + QUICHE_DCHECK(false) << "Failed to write tag."; + return false; + } + *end_offset += pad_length; + if (!writer->WriteUInt32(*end_offset)) { + QUICHE_DCHECK(false) << "Failed to write end offset."; + return false; + } + return true; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_framer.h b/quiche/quic/core/crypto/crypto_framer.h new file mode 100644 index 0000000..919ec09 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_framer.h
@@ -0,0 +1,137 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_FRAMER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_FRAMER_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_handshake_message.h" +#include "quiche/quic/core/crypto/crypto_message_parser.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class CryptoFramer; +class QuicData; +class QuicDataWriter; + +class QUIC_EXPORT_PRIVATE CryptoFramerVisitorInterface { + public: + virtual ~CryptoFramerVisitorInterface() {} + + // Called if an error is detected. + virtual void OnError(CryptoFramer* framer) = 0; + + // Called when a complete handshake message has been parsed. + virtual void OnHandshakeMessage(const CryptoHandshakeMessage& message) = 0; +}; + +// A class for framing the crypto messages that are exchanged in a QUIC +// session. +class QUIC_EXPORT_PRIVATE CryptoFramer : public CryptoMessageParser { + public: + CryptoFramer(); + + ~CryptoFramer() override; + + // ParseMessage parses exactly one message from the given + // absl::string_view. If there is an error, the message is truncated, + // or the message has trailing garbage then nullptr will be returned. + static std::unique_ptr<CryptoHandshakeMessage> ParseMessage( + absl::string_view in); + + // Set callbacks to be called from the framer. A visitor must be set, or + // else the framer will crash. It is acceptable for the visitor to do + // nothing. If this is called multiple times, only the last visitor + // will be used. |visitor| will be owned by the framer. + void set_visitor(CryptoFramerVisitorInterface* visitor) { + visitor_ = visitor; + } + + QuicErrorCode error() const override; + const std::string& error_detail() const override; + + // Processes input data, which must be delivered in order. Returns + // false if there was an error, and true otherwise. ProcessInput optionally + // takes an EncryptionLevel, but it is ignored. The variant with the + // EncryptionLevel is provided to match the CryptoMessageParser interface. + bool ProcessInput(absl::string_view input, EncryptionLevel level) override; + bool ProcessInput(absl::string_view input); + + // Returns the number of bytes of buffered input data remaining to be + // parsed. + size_t InputBytesRemaining() const override; + + // Checks if the specified tag has been seen. Returns |true| if it + // has, and |false| if it has not or a CHLO has not been seen. + bool HasTag(QuicTag tag) const; + + // Even if the CHLO has not been fully received, force processing of + // the handshake message. This is dangerous and should not be used + // except as a mechanism of last resort. + void ForceHandshake(); + + // Returns a new QuicData owned by the caller that contains a serialized + // |message|, or nullptr if there was an error. + static std::unique_ptr<QuicData> ConstructHandshakeMessage( + const CryptoHandshakeMessage& message); + + // Debug only method which permits processing truncated messages. + void set_process_truncated_messages(bool process_truncated_messages) { + process_truncated_messages_ = process_truncated_messages; + } + + private: + // Clears per-message state. Does not clear the visitor. + void Clear(); + + // Process does does the work of |ProcessInput|, but returns an error code, + // doesn't set error_ and doesn't call |visitor_->OnError()|. + QuicErrorCode Process(absl::string_view input); + + static bool WritePadTag(QuicDataWriter* writer, + size_t pad_length, + uint32_t* end_offset); + + // Represents the current state of the parsing state machine. + enum CryptoFramerState { + STATE_READING_TAG, + STATE_READING_NUM_ENTRIES, + STATE_READING_TAGS_AND_LENGTHS, + STATE_READING_VALUES + }; + + // Visitor to invoke when messages are parsed. + CryptoFramerVisitorInterface* visitor_; + // Last error. + QuicErrorCode error_; + // Remaining unparsed data. + std::string buffer_; + // Current state of the parsing. + CryptoFramerState state_; + // The message currently being parsed. + CryptoHandshakeMessage message_; + // The issue which caused |error_| + std::string error_detail_; + // Number of entires in the message currently being parsed. + uint16_t num_entries_; + // tags_and_lengths_ contains the tags that are currently being parsed and + // their lengths. + std::vector<std::pair<QuicTag, size_t>> tags_and_lengths_; + // Cumulative length of all values in the message currently being parsed. + size_t values_len_; + // Set to true to allow of processing of truncated messages for debugging. + bool process_truncated_messages_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_FRAMER_H_
diff --git a/quiche/quic/core/crypto/crypto_framer_test.cc b/quiche/quic/core/crypto/crypto_framer_test.cc new file mode 100644 index 0000000..558d3e4 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_framer_test.cc
@@ -0,0 +1,466 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/crypto_framer.h" + +#include <map> +#include <memory> +#include <vector> + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace quic { +namespace test { +namespace { + +char* AsChars(unsigned char* data) { + return reinterpret_cast<char*>(data); +} + +class TestCryptoVisitor : public CryptoFramerVisitorInterface { + public: + TestCryptoVisitor() : error_count_(0) {} + + void OnError(CryptoFramer* framer) override { + QUIC_DLOG(ERROR) << "CryptoFramer Error: " << framer->error(); + ++error_count_; + } + + void OnHandshakeMessage(const CryptoHandshakeMessage& message) override { + messages_.push_back(message); + } + + // Counters from the visitor callbacks. + int error_count_; + + std::vector<CryptoHandshakeMessage> messages_; +}; + +TEST(CryptoFramerTest, ConstructHandshakeMessage) { + CryptoHandshakeMessage message; + message.set_tag(0xFFAA7733); + message.SetStringPiece(0x12345678, "abcdef"); + message.SetStringPiece(0x12345679, "ghijk"); + message.SetStringPiece(0x1234567A, "lmnopqr"); + + unsigned char packet[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x03, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x06, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x0b, 0x00, 0x00, 0x00, + // tag 3 + 0x7A, 0x56, 0x34, 0x12, + // end offset 3 + 0x12, 0x00, 0x00, 0x00, + // value 1 + 'a', 'b', 'c', 'd', 'e', 'f', + // value 2 + 'g', 'h', 'i', 'j', 'k', + // value 3 + 'l', 'm', 'n', 'o', 'p', 'q', 'r', + }; + + CryptoFramer framer; + std::unique_ptr<QuicData> data = framer.ConstructHandshakeMessage(message); + ASSERT_TRUE(data != nullptr); + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet), + ABSL_ARRAYSIZE(packet)); +} + +TEST(CryptoFramerTest, ConstructHandshakeMessageWithTwoKeys) { + CryptoHandshakeMessage message; + message.set_tag(0xFFAA7733); + message.SetStringPiece(0x12345678, "abcdef"); + message.SetStringPiece(0x12345679, "ghijk"); + + unsigned char packet[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x06, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x0b, 0x00, 0x00, 0x00, + // value 1 + 'a', 'b', 'c', 'd', 'e', 'f', + // value 2 + 'g', 'h', 'i', 'j', 'k', + }; + + CryptoFramer framer; + std::unique_ptr<QuicData> data = framer.ConstructHandshakeMessage(message); + ASSERT_TRUE(data != nullptr); + + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet), + ABSL_ARRAYSIZE(packet)); +} + +TEST(CryptoFramerTest, ConstructHandshakeMessageZeroLength) { + CryptoHandshakeMessage message; + message.set_tag(0xFFAA7733); + message.SetStringPiece(0x12345678, ""); + + unsigned char packet[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x01, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x00, 0x00, 0x00, 0x00, + }; + + CryptoFramer framer; + std::unique_ptr<QuicData> data = framer.ConstructHandshakeMessage(message); + ASSERT_TRUE(data != nullptr); + + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet), + ABSL_ARRAYSIZE(packet)); +} + +TEST(CryptoFramerTest, ConstructHandshakeMessageTooManyEntries) { + CryptoHandshakeMessage message; + message.set_tag(0xFFAA7733); + for (uint32_t key = 1; key <= kMaxEntries + 1; ++key) { + message.SetStringPiece(key, "abcdef"); + } + + CryptoFramer framer; + std::unique_ptr<QuicData> data = framer.ConstructHandshakeMessage(message); + EXPECT_TRUE(data == nullptr); +} + +TEST(CryptoFramerTest, ConstructHandshakeMessageMinimumSize) { + CryptoHandshakeMessage message; + message.set_tag(0xFFAA7733); + message.SetStringPiece(0x01020304, "test"); + message.set_minimum_size(64); + + unsigned char packet[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 'P', 'A', 'D', 0, + // end offset 1 + 0x24, 0x00, 0x00, 0x00, + // tag 2 + 0x04, 0x03, 0x02, 0x01, + // end offset 2 + 0x28, 0x00, 0x00, 0x00, + // 36 bytes of padding. + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', + '-', '-', '-', '-', '-', '-', + // value 2 + 't', 'e', 's', 't', + }; + + CryptoFramer framer; + std::unique_ptr<QuicData> data = framer.ConstructHandshakeMessage(message); + ASSERT_TRUE(data != nullptr); + + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet), + ABSL_ARRAYSIZE(packet)); +} + +TEST(CryptoFramerTest, ConstructHandshakeMessageMinimumSizePadLast) { + CryptoHandshakeMessage message; + message.set_tag(0xFFAA7733); + message.SetStringPiece(1, ""); + message.set_minimum_size(64); + + unsigned char packet[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x01, 0x00, 0x00, 0x00, + // end offset 1 + 0x00, 0x00, 0x00, 0x00, + // tag 2 + 'P', 'A', 'D', 0, + // end offset 2 + 0x28, 0x00, 0x00, 0x00, + // 40 bytes of padding. + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', + }; + + CryptoFramer framer; + std::unique_ptr<QuicData> data = framer.ConstructHandshakeMessage(message); + ASSERT_TRUE(data != nullptr); + + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet), + ABSL_ARRAYSIZE(packet)); +} + +TEST(CryptoFramerTest, ProcessInput) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x06, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x0b, 0x00, 0x00, 0x00, + // value 1 + 'a', 'b', 'c', 'd', 'e', 'f', + // value 2 + 'g', 'h', 'i', 'j', 'k', + }; + + EXPECT_TRUE(framer.ProcessInput( + absl::string_view(AsChars(input), ABSL_ARRAYSIZE(input)))); + EXPECT_EQ(0u, framer.InputBytesRemaining()); + EXPECT_EQ(0, visitor.error_count_); + ASSERT_EQ(1u, visitor.messages_.size()); + const CryptoHandshakeMessage& message = visitor.messages_[0]; + EXPECT_EQ(0xFFAA7733, message.tag()); + EXPECT_EQ(2u, message.tag_value_map().size()); + EXPECT_EQ("abcdef", crypto_test_utils::GetValueForTag(message, 0x12345678)); + EXPECT_EQ("ghijk", crypto_test_utils::GetValueForTag(message, 0x12345679)); +} + +TEST(CryptoFramerTest, ProcessInputWithThreeKeys) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x03, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x06, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x0b, 0x00, 0x00, 0x00, + // tag 3 + 0x7A, 0x56, 0x34, 0x12, + // end offset 3 + 0x12, 0x00, 0x00, 0x00, + // value 1 + 'a', 'b', 'c', 'd', 'e', 'f', + // value 2 + 'g', 'h', 'i', 'j', 'k', + // value 3 + 'l', 'm', 'n', 'o', 'p', 'q', 'r', + }; + + EXPECT_TRUE(framer.ProcessInput( + absl::string_view(AsChars(input), ABSL_ARRAYSIZE(input)))); + EXPECT_EQ(0u, framer.InputBytesRemaining()); + EXPECT_EQ(0, visitor.error_count_); + ASSERT_EQ(1u, visitor.messages_.size()); + const CryptoHandshakeMessage& message = visitor.messages_[0]; + EXPECT_EQ(0xFFAA7733, message.tag()); + EXPECT_EQ(3u, message.tag_value_map().size()); + EXPECT_EQ("abcdef", crypto_test_utils::GetValueForTag(message, 0x12345678)); + EXPECT_EQ("ghijk", crypto_test_utils::GetValueForTag(message, 0x12345679)); + EXPECT_EQ("lmnopqr", crypto_test_utils::GetValueForTag(message, 0x1234567A)); +} + +TEST(CryptoFramerTest, ProcessInputIncrementally) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x06, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x0b, 0x00, 0x00, 0x00, + // value 1 + 'a', 'b', 'c', 'd', 'e', 'f', + // value 2 + 'g', 'h', 'i', 'j', 'k', + }; + + for (size_t i = 0; i < ABSL_ARRAYSIZE(input); i++) { + EXPECT_TRUE(framer.ProcessInput(absl::string_view(AsChars(input) + i, 1))); + } + EXPECT_EQ(0u, framer.InputBytesRemaining()); + ASSERT_EQ(1u, visitor.messages_.size()); + const CryptoHandshakeMessage& message = visitor.messages_[0]; + EXPECT_EQ(0xFFAA7733, message.tag()); + EXPECT_EQ(2u, message.tag_value_map().size()); + EXPECT_EQ("abcdef", crypto_test_utils::GetValueForTag(message, 0x12345678)); + EXPECT_EQ("ghijk", crypto_test_utils::GetValueForTag(message, 0x12345679)); +} + +TEST(CryptoFramerTest, ProcessInputTagsOutOfOrder) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x13, + // end offset 1 + 0x01, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x02, 0x00, 0x00, 0x00, + }; + + EXPECT_FALSE(framer.ProcessInput( + absl::string_view(AsChars(input), ABSL_ARRAYSIZE(input)))); + EXPECT_THAT(framer.error(), IsError(QUIC_CRYPTO_TAGS_OUT_OF_ORDER)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST(CryptoFramerTest, ProcessEndOffsetsOutOfOrder) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x79, 0x56, 0x34, 0x12, + // end offset 1 + 0x01, 0x00, 0x00, 0x00, + // tag 2 + 0x78, 0x56, 0x34, 0x13, + // end offset 2 + 0x00, 0x00, 0x00, 0x00, + }; + + EXPECT_FALSE(framer.ProcessInput( + absl::string_view(AsChars(input), ABSL_ARRAYSIZE(input)))); + EXPECT_THAT(framer.error(), IsError(QUIC_CRYPTO_TAGS_OUT_OF_ORDER)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST(CryptoFramerTest, ProcessInputTooManyEntries) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0xA0, 0x00, + // padding + 0x00, 0x00, + }; + + EXPECT_FALSE(framer.ProcessInput( + absl::string_view(AsChars(input), ABSL_ARRAYSIZE(input)))); + EXPECT_THAT(framer.error(), IsError(QUIC_CRYPTO_TOO_MANY_ENTRIES)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST(CryptoFramerTest, ProcessInputZeroLength) { + test::TestCryptoVisitor visitor; + CryptoFramer framer; + framer.set_visitor(&visitor); + + unsigned char input[] = { + // tag + 0x33, 0x77, 0xAA, 0xFF, + // num entries + 0x02, 0x00, + // padding + 0x00, 0x00, + // tag 1 + 0x78, 0x56, 0x34, 0x12, + // end offset 1 + 0x00, 0x00, 0x00, 0x00, + // tag 2 + 0x79, 0x56, 0x34, 0x12, + // end offset 2 + 0x05, 0x00, 0x00, 0x00, + }; + + EXPECT_TRUE(framer.ProcessInput( + absl::string_view(AsChars(input), ABSL_ARRAYSIZE(input)))); + EXPECT_EQ(0, visitor.error_count_); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_handshake.cc b/quiche/quic/core/crypto/crypto_handshake.cc new file mode 100644 index 0000000..bc17a97 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_handshake.cc
@@ -0,0 +1,39 @@ +// 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 "quiche/quic/core/crypto/crypto_handshake.h" + +#include "quiche/quic/core/crypto/key_exchange.h" +#include "quiche/quic/core/crypto/quic_decrypter.h" +#include "quiche/quic/core/crypto/quic_encrypter.h" + +namespace quic { + +QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters() + : key_exchange(0), + aead(0), + token_binding_key_param(0), + sct_supported_by_client(false) {} + +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() = default; + +QuicCryptoConfig::~QuicCryptoConfig() = default; + +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_handshake.h b/quiche/quic/core/crypto/crypto_handshake.h new file mode 100644 index 0000000..d44f65e --- /dev/null +++ b/quiche/quic/core/crypto/crypto_handshake.h
@@ -0,0 +1,189 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class SynchronousKeyExchange; +class QuicDecrypter; +class QuicEncrypter; + +// HandshakeFailureReason enum values are uploaded to UMA, they cannot be +// changed. +enum HandshakeFailureReason { + HANDSHAKE_OK = 0, + + // Failure reasons for an invalid client nonce in CHLO. + // + // The default error value for nonce verification failures from strike + // register (covers old strike registers and unknown failures). + CLIENT_NONCE_UNKNOWN_FAILURE = 1, + // Client nonce had incorrect length. + CLIENT_NONCE_INVALID_FAILURE = 2, + // Client nonce is not unique. + CLIENT_NONCE_NOT_UNIQUE_FAILURE = 3, + // Client orbit is invalid or incorrect. + CLIENT_NONCE_INVALID_ORBIT_FAILURE = 4, + // Client nonce's timestamp is not in the strike register's valid time range. + CLIENT_NONCE_INVALID_TIME_FAILURE = 5, + // Strike register's RPC call timed out, client nonce couldn't be verified. + CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT = 6, + // Strike register is down, client nonce couldn't be verified. + CLIENT_NONCE_STRIKE_REGISTER_FAILURE = 7, + + // Failure reasons for an invalid server nonce in CHLO. + // + // Unbox of server nonce failed. + SERVER_NONCE_DECRYPTION_FAILURE = 8, + // Decrypted server nonce had incorrect length. + SERVER_NONCE_INVALID_FAILURE = 9, + // Server nonce is not unique. + SERVER_NONCE_NOT_UNIQUE_FAILURE = 10, + // Server nonce's timestamp is not in the strike register's valid time range. + SERVER_NONCE_INVALID_TIME_FAILURE = 11, + // The server requires handshake confirmation. + SERVER_NONCE_REQUIRED_FAILURE = 20, + + // Failure reasons for an invalid server config in CHLO. + // + // Missing Server config id (kSCID) tag. + SERVER_CONFIG_INCHOATE_HELLO_FAILURE = 12, + // Couldn't find the Server config id (kSCID). + SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE = 13, + + // Failure reasons for an invalid source-address token. + // + // Missing Source-address token (kSourceAddressTokenTag) tag. + SOURCE_ADDRESS_TOKEN_INVALID_FAILURE = 14, + // Unbox of Source-address token failed. + SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE = 15, + // Couldn't parse the unbox'ed Source-address token. + SOURCE_ADDRESS_TOKEN_PARSE_FAILURE = 16, + // Source-address token is for a different IP address. + SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE = 17, + // The source-address token has a timestamp in the future. + SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE = 18, + // The source-address token has expired. + SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE = 19, + + // The expected leaf certificate hash could not be validated. + INVALID_EXPECTED_LEAF_CERTIFICATE = 21, + + MAX_FAILURE_REASON = 22, +}; + +// These errors will be packed into an uint32_t and we don't want to set the +// most significant bit, which may be misinterpreted as the sign bit. +static_assert(MAX_FAILURE_REASON <= 32, "failure reason out of sync"); + +// A CrypterPair contains the encrypter and decrypter for an encryption level. +struct QUIC_EXPORT_PRIVATE CrypterPair { + CrypterPair(); + CrypterPair(CrypterPair&&) = default; + ~CrypterPair(); + + std::unique_ptr<QuicEncrypter> encrypter; + std::unique_ptr<QuicDecrypter> decrypter; +}; + +// Parameters negotiated by the crypto handshake. +struct QUIC_EXPORT_PRIVATE QuicCryptoNegotiatedParameters + : public quiche::QuicheReferenceCounted { + // Initializes the members to 0 or empty values. + QuicCryptoNegotiatedParameters(); + + QuicTag key_exchange; + QuicTag aead; + std::string initial_premaster_secret; + std::string forward_secure_premaster_secret; + // initial_subkey_secret is used as the PRK input to the HKDF used when + // performing key extraction that needs to happen before forward-secure keys + // are available. + std::string initial_subkey_secret; + // subkey_secret is used as the PRK input to the HKDF used for key extraction. + std::string subkey_secret; + CrypterPair initial_crypters; + CrypterPair forward_secure_crypters; + // Normalized SNI: converted to lower case and trailing '.' removed. + std::string sni; + std::string client_nonce; + std::string server_nonce; + // hkdf_input_suffix contains the HKDF input following the label: the + // ConnectionId, client hello and server config. This is only populated in the + // client because only the client needs to derive the forward secure keys at a + // later time from the initial keys. + std::string hkdf_input_suffix; + // cached_certs contains the cached certificates that a client used when + // sending a client hello. + std::vector<std::string> cached_certs; + // client_key_exchange is used by clients to store the ephemeral KeyExchange + // for the connection. + std::unique_ptr<SynchronousKeyExchange> client_key_exchange; + // channel_id is set by servers to a ChannelID key when the client correctly + // proves possession of the corresponding private key. It consists of 32 + // bytes of x coordinate, followed by 32 bytes of y coordinate. Both values + // are big-endian and the pair is a P-256 public key. + std::string channel_id; + QuicTag token_binding_key_param; + + // Used when generating proof signature when sending server config updates. + + // Used to generate cert chain when sending server config updates. + std::string client_cached_cert_hashes; + + // Default to false; set to true if the client indicates that it supports sct + // by sending CSCT tag with an empty value in client hello. + bool sct_supported_by_client; + + // Parameters only populated for TLS handshakes. These will be 0 for + // connections not using TLS, or if the TLS handshake is not finished yet. + uint16_t cipher_suite = 0; + uint16_t key_exchange_group = 0; + uint16_t peer_signature_algorithm = 0; + + protected: + ~QuicCryptoNegotiatedParameters() override; +}; + +// QuicCryptoConfig contains common configuration between clients and servers. +class QUIC_EXPORT_PRIVATE QuicCryptoConfig { + public: + // kInitialLabel is a constant that is used when deriving the initial + // (non-forward secure) keys for the connection in order to tie the resulting + // key to this protocol. + static const char kInitialLabel[]; + + // kCETVLabel is a constant that is used when deriving the keys for the + // encrypted tag/value block in the client hello. + static const char kCETVLabel[]; + + // kForwardSecureLabel is a constant that is used when deriving the forward + // secure keys for the connection in order to tie the resulting key to this + // protocol. + static const char kForwardSecureLabel[]; + + QuicCryptoConfig(); + QuicCryptoConfig(const QuicCryptoConfig&) = delete; + QuicCryptoConfig& operator=(const QuicCryptoConfig&) = delete; + ~QuicCryptoConfig(); + + // Key exchange methods. The following two members' values correspond by + // index. + QuicTagVector kexs; + // Authenticated encryption with associated data (AEAD) algorithms. + QuicTagVector aead; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_H_
diff --git a/quiche/quic/core/crypto/crypto_handshake_message.cc b/quiche/quic/core/crypto/crypto_handshake_message.cc new file mode 100644 index 0000000..474036e --- /dev/null +++ b/quiche/quic/core/crypto/crypto_handshake_message.cc
@@ -0,0 +1,382 @@ +// 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 "quiche/quic/core/crypto/crypto_handshake_message.h" + +#include <memory> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_framer.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/crypto_utils.h" +#include "quiche/quic/core/quic_socket_address_coder.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +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_. unique_ptr doesn't have a copy constructor. + // The new object can lazily reconstruct serialized_. +} + +CryptoHandshakeMessage::CryptoHandshakeMessage(CryptoHandshakeMessage&& other) = + default; + +CryptoHandshakeMessage::~CryptoHandshakeMessage() {} + +CryptoHandshakeMessage& CryptoHandshakeMessage::operator=( + const CryptoHandshakeMessage& other) { + tag_ = other.tag_; + tag_value_map_ = other.tag_value_map_; + // Don't copy serialized_. unique_ptr doesn't have an assignment operator. + // However, invalidate serialized_. + serialized_.reset(); + minimum_size_ = other.minimum_size_; + return *this; +} + +CryptoHandshakeMessage& CryptoHandshakeMessage::operator=( + CryptoHandshakeMessage&& other) = default; + +bool CryptoHandshakeMessage::operator==( + const CryptoHandshakeMessage& rhs) const { + return tag_ == rhs.tag_ && tag_value_map_ == rhs.tag_value_map_ && + minimum_size_ == rhs.minimum_size_; +} + +bool CryptoHandshakeMessage::operator!=( + const CryptoHandshakeMessage& rhs) const { + return !(*this == rhs); +} + +void CryptoHandshakeMessage::Clear() { + tag_ = 0; + tag_value_map_.clear(); + minimum_size_ = 0; + serialized_.reset(); +} + +const QuicData& CryptoHandshakeMessage::GetSerialized() const { + if (!serialized_) { + serialized_ = CryptoFramer::ConstructHandshakeMessage(*this); + } + return *serialized_; +} + +void CryptoHandshakeMessage::MarkDirty() { + serialized_.reset(); +} + +void CryptoHandshakeMessage::SetVersionVector( + QuicTag tag, + ParsedQuicVersionVector versions) { + QuicVersionLabelVector version_labels; + for (const ParsedQuicVersion& version : versions) { + version_labels.push_back( + quiche::QuicheEndian::HostToNet32(CreateQuicVersionLabel(version))); + } + SetVector(tag, version_labels); +} + +void CryptoHandshakeMessage::SetVersion(QuicTag tag, + ParsedQuicVersion version) { + SetValue(tag, + quiche::QuicheEndian::HostToNet32(CreateQuicVersionLabel(version))); +} + +void CryptoHandshakeMessage::SetStringPiece(QuicTag tag, + absl::string_view value) { + tag_value_map_[tag] = std::string(value); +} + +void CryptoHandshakeMessage::Erase(QuicTag tag) { + tag_value_map_.erase(tag); +} + +QuicErrorCode CryptoHandshakeMessage::GetTaglist( + QuicTag tag, + QuicTagVector* out_tags) const { + auto 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->clear(); + return ret; + } + + size_t num_tags = it->second.size() / sizeof(QuicTag); + out_tags->resize(num_tags); + for (size_t i = 0; i < num_tags; ++i) { + QuicTag tag; + memcpy(&tag, it->second.data() + i * sizeof(tag), sizeof(tag)); + (*out_tags)[i] = tag; + } + return ret; +} + +QuicErrorCode CryptoHandshakeMessage::GetVersionLabelList( + QuicTag tag, + QuicVersionLabelVector* out) const { + QuicErrorCode error = GetTaglist(tag, out); + if (error != QUIC_NO_ERROR) { + return error; + } + + for (size_t i = 0; i < out->size(); ++i) { + (*out)[i] = quiche::QuicheEndian::HostToNet32((*out)[i]); + } + + return QUIC_NO_ERROR; +} + +QuicErrorCode CryptoHandshakeMessage::GetVersionLabel( + QuicTag tag, + QuicVersionLabel* out) const { + QuicErrorCode error = GetUint32(tag, out); + if (error != QUIC_NO_ERROR) { + return error; + } + + *out = quiche::QuicheEndian::HostToNet32(*out); + return QUIC_NO_ERROR; +} + +bool CryptoHandshakeMessage::GetStringPiece(QuicTag tag, + absl::string_view* out) const { + auto it = tag_value_map_.find(tag); + if (it == tag_value_map_.end()) { + return false; + } + *out = it->second; + return true; +} + +bool CryptoHandshakeMessage::HasStringPiece(QuicTag tag) const { + return tag_value_map_.find(tag) != tag_value_map_.end(); +} + +QuicErrorCode CryptoHandshakeMessage::GetNthValue24( + QuicTag tag, + unsigned index, + absl::string_view* out) const { + absl::string_view 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 = absl::string_view(value.data(), size); + return QUIC_NO_ERROR; + } + + value.remove_prefix(size); + } +} + +QuicErrorCode CryptoHandshakeMessage::GetUint32(QuicTag tag, + uint32_t* out) const { + return GetPOD(tag, out, sizeof(uint32_t)); +} + +QuicErrorCode CryptoHandshakeMessage::GetUint64(QuicTag tag, + uint64_t* out) const { + return GetPOD(tag, out, sizeof(uint64_t)); +} + +QuicErrorCode CryptoHandshakeMessage::GetStatelessResetToken( + QuicTag tag, + StatelessResetToken* out) const { + return GetPOD(tag, out, kStatelessResetTokenLength); +} + +size_t CryptoHandshakeMessage::size() const { + size_t ret = sizeof(QuicTag) + sizeof(uint16_t) /* number of entries */ + + sizeof(uint16_t) /* padding */; + ret += (sizeof(QuicTag) + sizeof(uint32_t) /* end offset */) * + tag_value_map_.size(); + for (auto 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_; +} + +std::string CryptoHandshakeMessage::DebugString() const { + return DebugStringInternal(0); +} + +QuicErrorCode CryptoHandshakeMessage::GetPOD(QuicTag tag, + void* out, + size_t len) const { + auto 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; +} + +std::string CryptoHandshakeMessage::DebugStringInternal(size_t indent) const { + std::string ret = + std::string(2 * indent, ' ') + QuicTagToString(tag_) + "<\n"; + ++indent; + for (auto it = tag_value_map_.begin(); it != tag_value_map_.end(); ++it) { + ret += std::string(2 * indent, ' ') + QuicTagToString(it->first) + ": "; + + bool done = false; + switch (it->first) { + case kICSL: + case kCFCW: + case kSFCW: + case kIRTT: + case kMIUS: + case kMIBS: + case kTCID: + case kMAD: + // uint32_t value + if (it->second.size() == 4) { + uint32_t value; + memcpy(&value, it->second.data(), sizeof(value)); + absl::StrAppend(&ret, value); + done = true; + } + break; + case kKEXS: + case kAEAD: + case kCOPT: + case kPDMD: + case kVER: + // 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 += "'" + QuicTagToString(tag) + "'"; + } + done = true; + } + break; + case kRREJ: + // uint32_t lists + if (it->second.size() % sizeof(uint32_t) == 0) { + for (size_t j = 0; j < it->second.size(); j += sizeof(uint32_t)) { + uint32_t value; + memcpy(&value, it->second.data() + j, sizeof(value)); + if (j > 0) { + ret += ","; + } + ret += CryptoUtils::HandshakeFailureReasonToString( + static_cast<HandshakeFailureReason>(value)); + } + done = true; + } + break; + case kCADR: + // IP address and port + if (!it->second.empty()) { + QuicSocketAddressCoder decoder; + if (decoder.Decode(it->second.data(), it->second.size())) { + ret += QuicSocketAddress(decoder.ip(), decoder.port()).ToString(); + done = true; + } + } + break; + case kSCFG: + // nested messages. + if (!it->second.empty()) { + std::unique_ptr<CryptoHandshakeMessage> msg( + CryptoFramer::ParseMessage(it->second)); + if (msg) { + ret += "\n"; + ret += msg->DebugStringInternal(indent + 1); + + done = true; + } + } + break; + case kPAD: + ret += absl::StrFormat("(%d bytes of padding)", it->second.size()); + done = true; + break; + case kSNI: + case kUAID: + ret += "\"" + it->second + "\""; + done = true; + break; + } + + if (!done) { + // If there's no specific format for this tag, or the value is invalid, + // then just use hex. + ret += "0x" + absl::BytesToHexString(it->second); + } + ret += "\n"; + } + --indent; + ret += std::string(2 * indent, ' ') + ">"; + return ret; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_handshake_message.h b/quiche/quic/core/crypto/crypto_handshake_message.h new file mode 100644 index 0000000..4785fa8 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_handshake_message.h
@@ -0,0 +1,160 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// An intermediate format of a handshake message that's convenient for a +// CryptoFramer to serialize from or parse into. +class QUIC_EXPORT_PRIVATE CryptoHandshakeMessage { + public: + CryptoHandshakeMessage(); + CryptoHandshakeMessage(const CryptoHandshakeMessage& other); + CryptoHandshakeMessage(CryptoHandshakeMessage&& other); + ~CryptoHandshakeMessage(); + + CryptoHandshakeMessage& operator=(const CryptoHandshakeMessage& other); + CryptoHandshakeMessage& operator=(CryptoHandshakeMessage&& other); + + bool operator==(const CryptoHandshakeMessage& rhs) const; + bool operator!=(const CryptoHandshakeMessage& rhs) const; + + // Clears state. + void Clear(); + + // GetSerialized returns the serialized form of this message and caches the + // result. Subsequently altering the message does not invalidate the cache. + const QuicData& GetSerialized() const; + + // MarkDirty invalidates the cache created by |GetSerialized|. + void MarkDirty(); + + // SetValue sets an element with the given tag to the raw, memory contents of + // |v|. + template <class T> + void SetValue(QuicTag tag, const T& v) { + tag_value_map_[tag] = + std::string(reinterpret_cast<const char*>(&v), sizeof(v)); + } + + // SetVector sets an element with the given tag to the raw contents of an + // array of elements in |v|. + template <class T> + void SetVector(QuicTag tag, const std::vector<T>& v) { + if (v.empty()) { + tag_value_map_[tag] = std::string(); + } else { + tag_value_map_[tag] = std::string(reinterpret_cast<const char*>(&v[0]), + v.size() * sizeof(T)); + } + } + + // Sets an element with the given tag to the on-the-wire representation of + // |version|. + void SetVersion(QuicTag tag, ParsedQuicVersion version); + + // Sets an element with the given tag to the on-the-wire representation of + // the elements in |versions|. + void SetVersionVector(QuicTag tag, ParsedQuicVersionVector versions); + + // Returns the message tag. + QuicTag tag() const { return tag_; } + // Sets the message tag. + void set_tag(QuicTag tag) { tag_ = tag; } + + const QuicTagValueMap& tag_value_map() const { return tag_value_map_; } + + void SetStringPiece(QuicTag tag, absl::string_view value); + + // Erase removes a tag/value, if present, from the message. + void Erase(QuicTag tag); + + // GetTaglist finds an element with the given tag containing zero or more + // tags. If such a tag doesn't exist, it returns an error code. Otherwise it + // populates |out_tags| with the tags and returns QUIC_NO_ERROR. + QuicErrorCode GetTaglist(QuicTag tag, QuicTagVector* out_tags) const; + + // GetVersionLabelList finds an element with the given tag containing zero or + // more version labels. If such a tag doesn't exist, it returns an error code. + // Otherwise it populates |out| with the labels and returns QUIC_NO_ERROR. + QuicErrorCode GetVersionLabelList(QuicTag tag, + QuicVersionLabelVector* out) const; + + // GetVersionLabel finds an element with the given tag containing a single + // version label. If such a tag doesn't exist, it returns an error code. + // Otherwise it populates |out| with the label and returns QUIC_NO_ERROR. + QuicErrorCode GetVersionLabel(QuicTag tag, QuicVersionLabel* out) const; + + bool GetStringPiece(QuicTag tag, absl::string_view* out) const; + bool HasStringPiece(QuicTag tag) const; + + // GetNthValue24 interprets the value with the given tag to be a series of + // 24-bit, length prefixed values and it returns the subvalue with the given + // index. + QuicErrorCode GetNthValue24(QuicTag tag, + unsigned index, + absl::string_view* out) const; + QuicErrorCode GetUint32(QuicTag tag, uint32_t* out) const; + QuicErrorCode GetUint64(QuicTag tag, uint64_t* out) const; + + QuicErrorCode GetStatelessResetToken(QuicTag tag, + StatelessResetToken* out) const; + + // size returns 4 (message tag) + 2 (uint16_t, number of entries) + + // (4 (tag) + 4 (end offset))*tag_value_map_.size() + ∑ value sizes. + size_t size() const; + + // set_minimum_size sets the minimum number of bytes that the message should + // consume. The CryptoFramer will add a PAD tag as needed when serializing in + // order to ensure this. Setting a value of 0 disables padding. + // + // Padding is useful in order to ensure that messages are a minimum size. A + // QUIC server can require a minimum size in order to reduce the + // amplification factor of any mirror DoS attack. + void set_minimum_size(size_t min_bytes); + + size_t minimum_size() const; + + // DebugString returns a multi-line, string representation of the message + // suitable for including in debug output. + std::string DebugString() const; + + private: + // GetPOD is a utility function for extracting a plain-old-data value. If + // |tag| exists in the message, and has a value of exactly |len| bytes then + // it copies |len| bytes of data into |out|. Otherwise |len| bytes at |out| + // are zeroed out. + // + // If used to copy integers then this assumes that the machine is + // little-endian. + QuicErrorCode GetPOD(QuicTag tag, void* out, size_t len) const; + + std::string DebugStringInternal(size_t indent) const; + + QuicTag tag_; + QuicTagValueMap tag_value_map_; + + size_t minimum_size_; + + // The serialized form of the handshake message. This member is constructed + // lazily. + mutable std::unique_ptr<QuicData> serialized_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_
diff --git a/quiche/quic/core/crypto/crypto_handshake_message_test.cc b/quiche/quic/core/crypto/crypto_handshake_message_test.cc new file mode 100644 index 0000000..bdc051c --- /dev/null +++ b/quiche/quic/core/crypto/crypto_handshake_message_test.cc
@@ -0,0 +1,105 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/crypto_handshake_message.h" + +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { +namespace test { +namespace { + +TEST(CryptoHandshakeMessageTest, DebugString) { + const char* str = "SHLO<\n>"; + + CryptoHandshakeMessage message; + message.set_tag(kSHLO); + EXPECT_EQ(str, message.DebugString()); + + // Test copy + CryptoHandshakeMessage message2(message); + EXPECT_EQ(str, message2.DebugString()); + + // Test move + CryptoHandshakeMessage message3(std::move(message)); + EXPECT_EQ(str, message3.DebugString()); + + // Test assign + CryptoHandshakeMessage message4 = message3; + EXPECT_EQ(str, message4.DebugString()); + + // Test move-assign + CryptoHandshakeMessage message5 = std::move(message3); + EXPECT_EQ(str, message5.DebugString()); +} + +TEST(CryptoHandshakeMessageTest, DebugStringWithUintVector) { + const char* str = + "REJ <\n RREJ: " + "SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE," + "CLIENT_NONCE_NOT_UNIQUE_FAILURE\n>"; + + CryptoHandshakeMessage message; + message.set_tag(kREJ); + std::vector<uint32_t> reasons = { + SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, + CLIENT_NONCE_NOT_UNIQUE_FAILURE}; + message.SetVector(kRREJ, reasons); + EXPECT_EQ(str, message.DebugString()); + + // Test copy + CryptoHandshakeMessage message2(message); + EXPECT_EQ(str, message2.DebugString()); + + // Test move + CryptoHandshakeMessage message3(std::move(message)); + EXPECT_EQ(str, message3.DebugString()); + + // Test assign + CryptoHandshakeMessage message4 = message3; + EXPECT_EQ(str, message4.DebugString()); + + // Test move-assign + CryptoHandshakeMessage message5 = std::move(message3); + EXPECT_EQ(str, message5.DebugString()); +} + +TEST(CryptoHandshakeMessageTest, DebugStringWithTagVector) { + const char* str = "CHLO<\n COPT: 'TBBR','PAD ','BYTE'\n>"; + + CryptoHandshakeMessage message; + message.set_tag(kCHLO); + message.SetVector(kCOPT, QuicTagVector{kTBBR, kPAD, kBYTE}); + EXPECT_EQ(str, message.DebugString()); + + // Test copy + CryptoHandshakeMessage message2(message); + EXPECT_EQ(str, message2.DebugString()); + + // Test move + CryptoHandshakeMessage message3(std::move(message)); + EXPECT_EQ(str, message3.DebugString()); + + // Test assign + CryptoHandshakeMessage message4 = message3; + EXPECT_EQ(str, message4.DebugString()); + + // Test move-assign + CryptoHandshakeMessage message5 = std::move(message3); + EXPECT_EQ(str, message5.DebugString()); +} + +TEST(CryptoHandshakeMessageTest, HasStringPiece) { + CryptoHandshakeMessage message; + EXPECT_FALSE(message.HasStringPiece(kALPN)); + message.SetStringPiece(kALPN, "foo"); + EXPECT_TRUE(message.HasStringPiece(kALPN)); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_message_parser.h b/quiche/quic/core/crypto/crypto_message_parser.h new file mode 100644 index 0000000..f819209 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_message_parser.h
@@ -0,0 +1,35 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_MESSAGE_PARSER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_MESSAGE_PARSER_H_ + +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_error_codes.h" +#include "quiche/quic/core/quic_types.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE CryptoMessageParser { + public: + virtual ~CryptoMessageParser() {} + + virtual QuicErrorCode error() const = 0; + virtual const std::string& error_detail() const = 0; + + // Processes input data, which must be delivered in order. The input data + // being processed was received at encryption level |level|. Returns + // false if there was an error, and true otherwise. + virtual bool ProcessInput(absl::string_view input, EncryptionLevel level) = 0; + + // Returns the number of bytes of buffered input data remaining to be + // parsed. + virtual size_t InputBytesRemaining() const = 0; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_MESSAGE_PARSER_H_
diff --git a/quiche/quic/core/crypto/crypto_message_printer_bin.cc b/quiche/quic/core/crypto/crypto_message_printer_bin.cc new file mode 100644 index 0000000..eb7393d --- /dev/null +++ b/quiche/quic/core/crypto/crypto_message_printer_bin.cc
@@ -0,0 +1,61 @@ +// Copyright (c) 2019 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. + +// Dumps the contents of a QUIC crypto handshake message in a human readable +// format. +// +// Usage: crypto_message_printer_bin <hex of message> + +#include <iostream> +#include <string> + +#include "absl/strings/escaping.h" +#include "quiche/quic/core/crypto/crypto_framer.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/common/platform/api/quiche_command_line_flags.h" + +using std::cerr; +using std::cout; +using std::endl; + +namespace quic { + +class CryptoMessagePrinter : public ::quic::CryptoFramerVisitorInterface { + public: + void OnHandshakeMessage(const CryptoHandshakeMessage& message) override { + cout << message.DebugString() << endl; + } + + void OnError(CryptoFramer* framer) override { + cerr << "Error code: " << framer->error() << endl; + cerr << "Error details: " << framer->error_detail() << endl; + } +}; + +} // namespace quic + +int main(int argc, char* argv[]) { + const char* usage = "Usage: crypto_message_printer <hex>"; + std::vector<std::string> messages = + quiche::QuicheParseCommandLineFlags(usage, argc, argv); + if (messages.size() != 1) { + quiche::QuichePrintCommandLineFlagHelp(usage); + exit(0); + } + + quic::CryptoMessagePrinter printer; + quic::CryptoFramer framer; + framer.set_visitor(&printer); + framer.set_process_truncated_messages(true); + std::string input = absl::HexStringToBytes(messages[0]); + if (!framer.ProcessInput(input)) { + return 1; + } + if (framer.InputBytesRemaining() != 0) { + cerr << "Input partially consumed. " << framer.InputBytesRemaining() + << " bytes remaining." << endl; + return 2; + } + return 0; +}
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h new file mode 100644 index 0000000..ab21cb5 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -0,0 +1,512 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_PROTOCOL_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_PROTOCOL_H_ + +#include <cstddef> +#include <string> + +#include "quiche/quic/core/quic_tag.h" + +// Version and Crypto tags are written to the wire with a big-endian +// representation of the name of the tag. For example +// the client hello tag (CHLO) will be written as the +// following 4 bytes: 'C' 'H' 'L' 'O'. Since it is +// stored in memory as a little endian uint32_t, we need +// to reverse the order of the bytes. +// +// We use a macro to ensure that no static initialisers are created. Use the +// MakeQuicTag function in normal code. +#define TAG(a, b, c, d) \ + static_cast<QuicTag>((d << 24) + (c << 16) + (b << 8) + a) + +namespace quic { + +using ServerConfigID = std::string; + +// The following tags have been deprecated and should not be reused: +// "1CON", "BBQ4", "NCON", "RCID", "SREJ", "TBKP", "TB10", "SCLS", "SMHL", +// "QNZR", "B2HI", "H2PR", "FIFO", "LIFO", "RRWS", "QNSP", "B2CL", "CHSP", +// "BPTE", "ACKD", "AKD2", "AKD4", "MAD1", "MAD4", "MAD5", "ACD0", "ACKQ", +// "TLPR", "CCS\0" + +// clang-format off +const QuicTag kCHLO = TAG('C', 'H', 'L', 'O'); // Client hello +const QuicTag kSHLO = TAG('S', 'H', 'L', 'O'); // Server hello +const QuicTag kSCFG = TAG('S', 'C', 'F', 'G'); // Server config +const QuicTag kREJ = TAG('R', 'E', 'J', '\0'); // Reject +const QuicTag kCETV = TAG('C', 'E', 'T', 'V'); // Client encrypted tag-value + // pairs +const QuicTag kPRST = TAG('P', 'R', 'S', 'T'); // Public reset +const QuicTag kSCUP = TAG('S', 'C', 'U', 'P'); // Server config update +const QuicTag kALPN = TAG('A', 'L', 'P', 'N'); // Application-layer protocol + +// Key exchange methods +const QuicTag kP256 = TAG('P', '2', '5', '6'); // ECDH, Curve P-256 +const QuicTag kC255 = TAG('C', '2', '5', '5'); // ECDH, Curve25519 + +// AEAD algorithms +const QuicTag kAESG = TAG('A', 'E', 'S', 'G'); // AES128 + GCM-12 +const QuicTag kCC20 = TAG('C', 'C', '2', '0'); // ChaCha20 + Poly1305 RFC7539 + +// Congestion control feedback types +const QuicTag kQBIC = TAG('Q', 'B', 'I', 'C'); // TCP cubic + +// Connection options (COPT) values +const QuicTag kAFCW = TAG('A', 'F', 'C', 'W'); // Auto-tune flow control + // receive windows. +const QuicTag kIFW5 = TAG('I', 'F', 'W', '5'); // Set initial size + // of stream flow control + // receive window to + // 32KB. (2^5 KB). +const QuicTag kIFW6 = TAG('I', 'F', 'W', '6'); // Set initial size + // of stream flow control + // receive window to + // 64KB. (2^6 KB). +const QuicTag kIFW7 = TAG('I', 'F', 'W', '7'); // Set initial size + // of stream flow control + // receive window to + // 128KB. (2^7 KB). +const QuicTag kIFW8 = TAG('I', 'F', 'W', '8'); // Set initial size + // of stream flow control + // receive window to + // 256KB. (2^8 KB). +const QuicTag kIFW9 = TAG('I', 'F', 'W', '9'); // Set initial size + // of stream flow control + // receive window to + // 512KB. (2^9 KB). +const QuicTag kIFWA = TAG('I', 'F', 'W', 'a'); // Set initial size + // of stream flow control + // receive window to + // 1MB. (2^0xa KB). +const QuicTag kTBBR = TAG('T', 'B', 'B', 'R'); // Reduced Buffer Bloat TCP +const QuicTag k1RTT = TAG('1', 'R', 'T', 'T'); // STARTUP in BBR for 1 RTT +const QuicTag k2RTT = TAG('2', 'R', 'T', 'T'); // STARTUP in BBR for 2 RTTs +const QuicTag kLRTT = TAG('L', 'R', 'T', 'T'); // Exit STARTUP in BBR on loss +const QuicTag kBBS1 = TAG('B', 'B', 'S', '1'); // DEPRECATED +const QuicTag kBBS2 = TAG('B', 'B', 'S', '2'); // More aggressive packet + // conservation in BBR STARTUP +const QuicTag kBBS3 = TAG('B', 'B', 'S', '3'); // Slowstart packet + // conservation in BBR STARTUP +const QuicTag kBBS4 = TAG('B', 'B', 'S', '4'); // DEPRECATED +const QuicTag kBBS5 = TAG('B', 'B', 'S', '5'); // DEPRECATED +const QuicTag kBBRR = TAG('B', 'B', 'R', 'R'); // Rate-based recovery in BBR +const QuicTag kBBR1 = TAG('B', 'B', 'R', '1'); // DEPRECATED +const QuicTag kBBR2 = TAG('B', 'B', 'R', '2'); // DEPRECATED +const QuicTag kBBR3 = TAG('B', 'B', 'R', '3'); // Fully drain the queue once + // per cycle +const QuicTag kBBR4 = TAG('B', 'B', 'R', '4'); // 20 RTT ack aggregation +const QuicTag kBBR5 = TAG('B', 'B', 'R', '5'); // 40 RTT ack aggregation +const QuicTag kBBR9 = TAG('B', 'B', 'R', '9'); // DEPRECATED +const QuicTag kBBRA = TAG('B', 'B', 'R', 'A'); // Starts a new ack aggregation + // epoch if a full round has + // passed +const QuicTag kBBRB = TAG('B', 'B', 'R', 'B'); // Use send rate in BBR's + // MaxAckHeightTracker +const QuicTag kBBRS = TAG('B', 'B', 'R', 'S'); // DEPRECATED +const QuicTag kBBQ1 = TAG('B', 'B', 'Q', '1'); // BBR with lower 2.77 STARTUP + // pacing and CWND gain. +const QuicTag kBBQ2 = TAG('B', 'B', 'Q', '2'); // BBRv2 with 2.885 STARTUP and + // DRAIN CWND gain. +const QuicTag kBBQ3 = TAG('B', 'B', 'Q', '3'); // BBR with ack aggregation + // compensation in STARTUP. +const QuicTag kBBQ5 = TAG('B', 'B', 'Q', '5'); // Expire ack aggregation upon + // bandwidth increase in + // STARTUP. +const QuicTag kBBQ6 = TAG('B', 'B', 'Q', '6'); // Reduce STARTUP gain to 25% + // more than BW increase. +const QuicTag kBBQ7 = TAG('B', 'B', 'Q', '7'); // Reduce bw_lo by + // bytes_lost/min_rtt. +const QuicTag kBBQ8 = TAG('B', 'B', 'Q', '8'); // Reduce bw_lo by + // bw_lo * bytes_lost/inflight +const QuicTag kBBQ9 = TAG('B', 'B', 'Q', '9'); // Reduce bw_lo by + // bw_lo * bytes_lost/cwnd +const QuicTag kBBQ0 = TAG('B', 'B', 'Q', '0'); // Increase bytes_acked in + // PROBE_UP when app limited. +const QuicTag kRENO = TAG('R', 'E', 'N', 'O'); // Reno Congestion Control +const QuicTag kTPCC = TAG('P', 'C', 'C', '\0'); // Performance-Oriented + // Congestion Control +const QuicTag kBYTE = TAG('B', 'Y', 'T', 'E'); // TCP cubic or reno in bytes +const QuicTag kIW03 = TAG('I', 'W', '0', '3'); // Force ICWND to 3 +const QuicTag kIW10 = TAG('I', 'W', '1', '0'); // Force ICWND to 10 +const QuicTag kIW20 = TAG('I', 'W', '2', '0'); // Force ICWND to 20 +const QuicTag kIW50 = TAG('I', 'W', '5', '0'); // Force ICWND to 50 +const QuicTag kB2ON = TAG('B', '2', 'O', 'N'); // Enable BBRv2 +const QuicTag kB2NA = TAG('B', '2', 'N', 'A'); // For BBRv2, do not add ack + // height to queueing threshold +const QuicTag kB2NE = TAG('B', '2', 'N', 'E'); // For BBRv2, always exit + // STARTUP on loss, even if + // bandwidth growth exceeds + // threshold. +const QuicTag kB2RP = TAG('B', '2', 'R', 'P'); // For BBRv2, run PROBE_RTT on + // the regular schedule +const QuicTag kB2LO = TAG('B', '2', 'L', 'O'); // Ignore inflight_lo in BBR2 +const QuicTag kB2HR = TAG('B', '2', 'H', 'R'); // 15% inflight_hi headroom. +const QuicTag kB2SL = TAG('B', '2', 'S', 'L'); // When exiting STARTUP due to + // loss, set inflight_hi to the + // max of bdp and max bytes + // delivered in round. +const QuicTag kB2H2 = TAG('B', '2', 'H', '2'); // When exiting PROBE_UP due to + // loss, set inflight_hi to the + // max of inflight@send and max + // bytes delivered in round. +const QuicTag kB2RC = TAG('B', '2', 'R', 'C'); // Disable Reno-coexistence for + // BBR2. +const QuicTag kBSAO = TAG('B', 'S', 'A', 'O'); // Avoid Overestimation in + // Bandwidth Sampler with ack + // aggregation +const QuicTag kB2DL = TAG('B', '2', 'D', 'L'); // Increase inflight_hi based + // on delievered, not inflight. +const QuicTag kB201 = TAG('B', '2', '0', '1'); // In PROBE_UP, check if cwnd + // limited before aggregation + // epoch, instead of ack event. +const QuicTag kB202 = TAG('B', '2', '0', '2'); // Do not exit PROBE_UP if + // inflight dips below 1.25*BW. +const QuicTag kB203 = TAG('B', '2', '0', '3'); // Ignore inflight_hi until + // PROBE_UP is exited. +const QuicTag kB204 = TAG('B', '2', '0', '4'); // Reduce extra acked when + // MaxBW incrases. +const QuicTag kB205 = TAG('B', '2', '0', '5'); // Add extra acked to CWND in + // STARTUP. +const QuicTag kB206 = TAG('B', '2', '0', '6'); // Exit STARTUP after 2 losses. +const QuicTag kB207 = TAG('B', '2', '0', '7'); // Exit STARTUP on persistent + // queue +const QuicTag kNTLP = TAG('N', 'T', 'L', 'P'); // No tail loss probe +const QuicTag k1TLP = TAG('1', 'T', 'L', 'P'); // 1 tail loss probe +const QuicTag k1RTO = TAG('1', 'R', 'T', 'O'); // Send 1 packet upon RTO +const QuicTag kNRTO = TAG('N', 'R', 'T', 'O'); // CWND reduction on loss +const QuicTag kTIME = TAG('T', 'I', 'M', 'E'); // Time based loss detection +const QuicTag kATIM = TAG('A', 'T', 'I', 'M'); // Adaptive time loss detection +const QuicTag kMIN1 = TAG('M', 'I', 'N', '1'); // Min CWND of 1 packet +const QuicTag kMIN4 = TAG('M', 'I', 'N', '4'); // Min CWND of 4 packets, + // with a min rate of 1 BDP. +const QuicTag kMAD0 = TAG('M', 'A', 'D', '0'); // Ignore ack delay +const QuicTag kMAD2 = TAG('M', 'A', 'D', '2'); // No min TLP +const QuicTag kMAD3 = TAG('M', 'A', 'D', '3'); // No min RTO +const QuicTag k1ACK = TAG('1', 'A', 'C', 'K'); // 1 fast ack for reordering +const QuicTag kAKD3 = TAG('A', 'K', 'D', '3'); // Ack decimation style acking + // with 1/8 RTT acks. +const QuicTag kAKDU = TAG('A', 'K', 'D', 'U'); // Unlimited number of packets + // received before acking +const QuicTag kAFFE = TAG('A', 'F', 'F', 'E'); // Enable client receiving + // AckFrequencyFrame. +const QuicTag kAFF1 = TAG('A', 'F', 'F', '1'); // Use SRTT in building + // AckFrequencyFrame. +const QuicTag kAFF2 = TAG('A', 'F', 'F', '2'); // Send AckFrequencyFrame upon + // handshake completion. +const QuicTag kSSLR = TAG('S', 'S', 'L', 'R'); // Slow Start Large Reduction. +const QuicTag kNPRR = TAG('N', 'P', 'R', 'R'); // Pace at unity instead of PRR +const QuicTag k2RTO = TAG('2', 'R', 'T', 'O'); // Close connection on 2 RTOs +const QuicTag k3RTO = TAG('3', 'R', 'T', 'O'); // Close connection on 3 RTOs +const QuicTag k4RTO = TAG('4', 'R', 'T', 'O'); // Close connection on 4 RTOs +const QuicTag k5RTO = TAG('5', 'R', 'T', 'O'); // Close connection on 5 RTOs +const QuicTag k6RTO = TAG('6', 'R', 'T', 'O'); // Close connection on 6 RTOs +const QuicTag kCBHD = TAG('C', 'B', 'H', 'D'); // Client only blackhole + // detection. +const QuicTag kNBHD = TAG('N', 'B', 'H', 'D'); // No blackhole detection. +const QuicTag kCONH = TAG('C', 'O', 'N', 'H'); // Conservative Handshake + // Retransmissions. +const QuicTag kLFAK = TAG('L', 'F', 'A', 'K'); // Don't invoke FACK on the + // first ack. +const QuicTag kSTMP = TAG('S', 'T', 'M', 'P'); // DEPRECATED +const QuicTag kEACK = TAG('E', 'A', 'C', 'K'); // Bundle ack-eliciting frame + // with an ACK after PTO/RTO + +const QuicTag kILD0 = TAG('I', 'L', 'D', '0'); // IETF style loss detection + // (default with 1/8 RTT time + // threshold) +const QuicTag kILD1 = TAG('I', 'L', 'D', '1'); // IETF style loss detection + // with 1/4 RTT time threshold +const QuicTag kILD2 = TAG('I', 'L', 'D', '2'); // IETF style loss detection + // with adaptive packet + // threshold +const QuicTag kILD3 = TAG('I', 'L', 'D', '3'); // IETF style loss detection + // with 1/4 RTT time threshold + // and adaptive packet + // threshold +const QuicTag kILD4 = TAG('I', 'L', 'D', '4'); // IETF style loss detection + // with both adaptive time + // threshold (default 1/4 RTT) + // and adaptive packet + // threshold +const QuicTag kRUNT = TAG('R', 'U', 'N', 'T'); // No packet threshold loss + // detection for "runt" packet. +const QuicTag kNSTP = TAG('N', 'S', 'T', 'P'); // No stop waiting frames. +const QuicTag kNRTT = TAG('N', 'R', 'T', 'T'); // Ignore initial RTT + +const QuicTag k1PTO = TAG('1', 'P', 'T', 'O'); // Send 1 packet upon PTO. +const QuicTag k2PTO = TAG('2', 'P', 'T', 'O'); // Send 2 packets upon PTO. + +const QuicTag k6PTO = TAG('6', 'P', 'T', 'O'); // Closes connection on 6 + // consecutive PTOs. +const QuicTag k7PTO = TAG('7', 'P', 'T', 'O'); // Closes connection on 7 + // consecutive PTOs. +const QuicTag k8PTO = TAG('8', 'P', 'T', 'O'); // Closes connection on 8 + // consecutive PTOs. +const QuicTag kPTOS = TAG('P', 'T', 'O', 'S'); // Skip packet number before + // sending the last PTO. +const QuicTag kPTOA = TAG('P', 'T', 'O', 'A'); // Do not add max ack delay + // when computing PTO timeout + // if an immediate ACK is + // expected. +const QuicTag kPEB1 = TAG('P', 'E', 'B', '1'); // Start exponential backoff + // since 1st PTO. +const QuicTag kPEB2 = TAG('P', 'E', 'B', '2'); // Start exponential backoff + // since 2nd PTO. +const QuicTag kPVS1 = TAG('P', 'V', 'S', '1'); // Use 2 * rttvar when + // calculating PTO timeout. +const QuicTag kPAG1 = TAG('P', 'A', 'G', '1'); // Make 1st PTO more aggressive +const QuicTag kPAG2 = TAG('P', 'A', 'G', '2'); // Make first 2 PTOs more + // aggressive +const QuicTag kPSDA = TAG('P', 'S', 'D', 'A'); // Use standard deviation when + // calculating PTO timeout. +const QuicTag kPLE1 = TAG('P', 'L', 'E', '1'); // Arm the 1st PTO with + // earliest in flight sent time + // and at least 0.5*srtt from + // last sent packet. +const QuicTag kPLE2 = TAG('P', 'L', 'E', '2'); // Arm the 1st PTO with + // earliest in flight sent time + // and at least 1.5*srtt from + // last sent packet. +const QuicTag kAPTO = TAG('A', 'P', 'T', 'O'); // Use 1.5 * initial RTT before + // any RTT sample is available. + +const QuicTag kELDT = TAG('E', 'L', 'D', 'T'); // Enable Loss Detection Tuning + +// TODO(haoyuewang) Remove RVCM option once +// --quic_remove_connection_migration_connection_option is deprecated. +const QuicTag kRVCM = TAG('R', 'V', 'C', 'M'); // Validate the new address + // upon client address change. + +// Optional support of truncated Connection IDs. If sent by a peer, the value +// is the minimum number of bytes allowed for the connection ID sent to the +// peer. +const QuicTag kTCID = TAG('T', 'C', 'I', 'D'); // Connection ID truncation. + +// Multipath option. +const QuicTag kMPTH = TAG('M', 'P', 'T', 'H'); // Enable multipath. + +const QuicTag kNCMR = TAG('N', 'C', 'M', 'R'); // Do not attempt connection + // migration. + +// Allows disabling defer_send_in_response_to_packets in QuicConnection. +const QuicTag kDFER = TAG('D', 'F', 'E', 'R'); // Do not defer sending. + +// Disable Pacing offload option. +const QuicTag kNPCO = TAG('N', 'P', 'C', 'O'); // No pacing offload. + +// Enable bandwidth resumption experiment. +const QuicTag kBWRE = TAG('B', 'W', 'R', 'E'); // Bandwidth resumption. +const QuicTag kBWMX = TAG('B', 'W', 'M', 'X'); // Max bandwidth resumption. +const QuicTag kBWRS = TAG('B', 'W', 'R', 'S'); // Server bandwidth resumption. +const QuicTag kBWS2 = TAG('B', 'W', 'S', '2'); // Server bw resumption v2. +const QuicTag kBWS3 = TAG('B', 'W', 'S', '3'); // QUIC Initial CWND - Control. +const QuicTag kBWS4 = TAG('B', 'W', 'S', '4'); // QUIC Initial CWND - Enabled. +const QuicTag kBWS5 = TAG('B', 'W', 'S', '5'); // QUIC Initial CWND up and down +const QuicTag kBWS6 = TAG('B', 'W', 'S', '6'); // QUIC Initial CWND - Enabled + // with 0.5 * default + // multiplier. +const QuicTag kBWP0 = TAG('B', 'W', 'P', '0'); // QUIC Initial CWND - SPDY + // priority 0. +const QuicTag kBWP1 = TAG('B', 'W', 'P', '1'); // QUIC Initial CWND - SPDY + // priorities 0 and 1. +const QuicTag kBWP2 = TAG('B', 'W', 'P', '2'); // QUIC Initial CWND - SPDY + // priorities 0, 1 and 2. +const QuicTag kBWP3 = TAG('B', 'W', 'P', '3'); // QUIC Initial CWND - SPDY + // priorities 0, 1, 2 and 3. +const QuicTag kBWP4 = TAG('B', 'W', 'P', '4'); // QUIC Initial CWND - SPDY + // priorities >= 0, 1, 2, 3 and + // 4. +const QuicTag kBWG4 = TAG('B', 'W', 'G', '4'); // QUIC Initial CWND - + // Bandwidth model 1. +const QuicTag kBWG7 = TAG('B', 'W', 'G', '7'); // QUIC Initial CWND - + // Bandwidth model 2. +const QuicTag kBWG8 = TAG('B', 'W', 'G', '8'); // QUIC Initial CWND - + // Bandwidth model 3. +const QuicTag kBWS7 = TAG('B', 'W', 'S', '7'); // QUIC Initial CWND - Enabled + // with 0.75 * default + // multiplier. +const QuicTag kBWM3 = TAG('B', 'W', 'M', '3'); // Consider overshooting if + // bytes lost after bandwidth + // resumption * 3 > IW. +const QuicTag kBWM4 = TAG('B', 'W', 'M', '4'); // Consider overshooting if + // bytes lost after bandwidth + // resumption * 4 > IW. +const QuicTag kICW1 = TAG('I', 'C', 'W', '1'); // Max initial congestion window + // 100. +const QuicTag kDTOS = TAG('D', 'T', 'O', 'S'); // Enable overshooting + // detection. + +const QuicTag kFIDT = TAG('F', 'I', 'D', 'T'); // Extend idle timer by PTO + // instead of the whole idle + // timeout. + +const QuicTag k3AFF = TAG('3', 'A', 'F', 'F'); // 3 anti amplification factor. +const QuicTag k10AF = TAG('1', '0', 'A', 'F'); // 10 anti amplification factor. + +// Enable path MTU discovery experiment. +const QuicTag kMTUH = TAG('M', 'T', 'U', 'H'); // High-target MTU discovery. +const QuicTag kMTUL = TAG('M', 'T', 'U', 'L'); // Low-target MTU discovery. + +const QuicTag kNSLC = TAG('N', 'S', 'L', 'C'); // Always send connection close + // for idle timeout. +const QuicTag kNCHP = TAG('N', 'C', 'H', 'P'); // No chaos protection. +const QuicTag kNBPE = TAG('N', 'B', 'P', 'E'); // No BoringSSL Permutes + // TLS Extensions. + +// Proof types (i.e. certificate types) +// NOTE: although it would be silly to do so, specifying both kX509 and kX59R +// is allowed and is equivalent to specifying only kX509. +const QuicTag kX509 = TAG('X', '5', '0', '9'); // X.509 certificate, all key + // types +const QuicTag kX59R = TAG('X', '5', '9', 'R'); // X.509 certificate, RSA keys + // only +const QuicTag kCHID = TAG('C', 'H', 'I', 'D'); // Channel ID. + +// Client hello tags +const QuicTag kVER = TAG('V', 'E', 'R', '\0'); // Version +const QuicTag kNONC = TAG('N', 'O', 'N', 'C'); // The client's nonce +const QuicTag kNONP = TAG('N', 'O', 'N', 'P'); // The client's proof nonce +const QuicTag kKEXS = TAG('K', 'E', 'X', 'S'); // Key exchange methods +const QuicTag kAEAD = TAG('A', 'E', 'A', 'D'); // Authenticated + // encryption algorithms +const QuicTag kCOPT = TAG('C', 'O', 'P', 'T'); // Connection options +const QuicTag kCLOP = TAG('C', 'L', 'O', 'P'); // Client connection options +const QuicTag kICSL = TAG('I', 'C', 'S', 'L'); // Idle network timeout +const QuicTag kMIBS = TAG('M', 'I', 'D', 'S'); // Max incoming bidi streams +const QuicTag kMIUS = TAG('M', 'I', 'U', 'S'); // Max incoming unidi streams +const QuicTag kADE = TAG('A', 'D', 'E', 0); // Ack Delay Exponent (IETF + // QUIC ACK Frame Only). +const QuicTag kIRTT = TAG('I', 'R', 'T', 'T'); // Estimated initial RTT in us. +const QuicTag kTRTT = TAG('T', 'R', 'T', 'T'); // If server receives an rtt + // from an address token, set + // it as the initial rtt. +const QuicTag kSNI = TAG('S', 'N', 'I', '\0'); // Server name + // indication +const QuicTag kPUBS = TAG('P', 'U', 'B', 'S'); // Public key values +const QuicTag kSCID = TAG('S', 'C', 'I', 'D'); // Server config id +const QuicTag kORBT = TAG('O', 'B', 'I', 'T'); // Server orbit. +const QuicTag kPDMD = TAG('P', 'D', 'M', 'D'); // Proof demand. +const QuicTag kPROF = TAG('P', 'R', 'O', 'F'); // Proof (signature). +const QuicTag kCCRT = TAG('C', 'C', 'R', 'T'); // Cached certificate +const QuicTag kEXPY = TAG('E', 'X', 'P', 'Y'); // Expiry +const QuicTag kSTTL = TAG('S', 'T', 'T', 'L'); // Server Config TTL +const QuicTag kSFCW = TAG('S', 'F', 'C', 'W'); // Initial stream flow control + // receive window. +const QuicTag kCFCW = TAG('C', 'F', 'C', 'W'); // Initial session/connection + // flow control receive window. +const QuicTag kUAID = TAG('U', 'A', 'I', 'D'); // Client's User Agent ID. +const QuicTag kXLCT = TAG('X', 'L', 'C', 'T'); // Expected leaf certificate. +const QuicTag kQLVE = TAG('Q', 'L', 'V', 'E'); // Legacy Version + // Encapsulation. + +const QuicTag kPDP1 = TAG('P', 'D', 'P', '1'); // Path degrading triggered + // at 1PTO. + +const QuicTag kPDP2 = TAG('P', 'D', 'P', '2'); // Path degrading triggered + // at 2PTO. + +const QuicTag kPDP3 = TAG('P', 'D', 'P', '3'); // Path degrading triggered + // at 3PTO. + +const QuicTag kPDP4 = TAG('P', 'D', 'P', '4'); // Path degrading triggered + // at 4PTO. + +const QuicTag kPDP5 = TAG('P', 'D', 'P', '5'); // Path degrading triggered + // at 5PTO. + +const QuicTag kQNZ2 = TAG('Q', 'N', 'Z', '2'); // Turn off QUIC crypto 0-RTT. + +const QuicTag kMAD = TAG('M', 'A', 'D', 0); // Max Ack Delay (IETF QUIC) + +const QuicTag kIGNP = TAG('I', 'G', 'N', 'P'); // Do not use PING only packet + // for RTT measure or + // congestion control. + +const QuicTag kSRWP = TAG('S', 'R', 'W', 'P'); // Enable retransmittable on + // wire PING (ROWP) on the + // server side. +const QuicTag kGSR0 = TAG('G', 'S', 'R', '0'); // Selective Resumption + +const QuicTag kINVC = TAG('I', 'N', 'V', 'C'); // Send connection close for + // INVALID_VERSION + +// Client Hints triggers. +const QuicTag kGWCH = TAG('G', 'W', 'C', 'H'); +const QuicTag kYTCH = TAG('Y', 'T', 'C', 'H'); +const QuicTag kACH0 = TAG('A', 'C', 'H', '0'); + +// Rejection tags +const QuicTag kRREJ = TAG('R', 'R', 'E', 'J'); // Reasons for server sending + +// Server hello tags +const QuicTag kCADR = TAG('C', 'A', 'D', 'R'); // Client IP address and port +const QuicTag kASAD = TAG('A', 'S', 'A', 'D'); // Alternate Server IP address + // and port. +const QuicTag kSRST = TAG('S', 'R', 'S', 'T'); // Stateless reset token used + // in IETF public reset packet + +// CETV tags +const QuicTag kCIDK = TAG('C', 'I', 'D', 'K'); // ChannelID key +const QuicTag kCIDS = TAG('C', 'I', 'D', 'S'); // ChannelID signature + +// Public reset tags +const QuicTag kRNON = TAG('R', 'N', 'O', 'N'); // Public reset nonce proof +const QuicTag kRSEQ = TAG('R', 'S', 'E', 'Q'); // Rejected packet number + +// Universal tags +const QuicTag kPAD = TAG('P', 'A', 'D', '\0'); // Padding + +// Stats collection tags +const QuicTag kEPID = TAG('E', 'P', 'I', 'D'); // Endpoint identifier. + +// clang-format on + +// These tags have a special form so that they appear either at the beginning +// or the end of a handshake message. Since handshake messages are sorted by +// tag value, the tags with 0 at the end will sort first and those with 255 at +// the end will sort last. +// +// The certificate chain should have a tag that will cause it to be sorted at +// the end of any handshake messages because it's likely to be large and the +// client might be able to get everything that it needs from the small values at +// the beginning. +// +// Likewise tags with random values should be towards the beginning of the +// message because the server mightn't hold state for a rejected client hello +// and therefore the client may have issues reassembling the rejection message +// in the event that it sent two client hellos. +const QuicTag kServerNonceTag = TAG('S', 'N', 'O', 0); // The server's nonce +const QuicTag kSourceAddressTokenTag = + TAG('S', 'T', 'K', 0); // Source-address token +const QuicTag kCertificateTag = TAG('C', 'R', 'T', 255); // Certificate chain +const QuicTag kCertificateSCTTag = + TAG('C', 'S', 'C', 'T'); // Signed cert timestamp (RFC6962) of leaf cert. + +#undef TAG + +const size_t kMaxEntries = 128; // Max number of entries in a message. + +const size_t kNonceSize = 32; // Size in bytes of the connection nonce. + +const size_t kOrbitSize = 8; // Number of bytes in an orbit value. + +// kProofSignatureLabel is prepended to the CHLO hash and server configs before +// signing to avoid any cross-protocol attacks on the signature. +const char kProofSignatureLabel[] = "QUIC CHLO and server config signature"; + +// kClientHelloMinimumSize is the minimum size of a client hello. Client hellos +// will have PAD tags added in order to ensure this minimum is met and client +// hellos smaller than this will be an error. This minimum size reduces the +// amplification factor of any mirror DoS attack. +// +// A client may pad an inchoate client hello to a size larger than +// kClientHelloMinimumSize to make it more likely to receive a complete +// rejection message. +const size_t kClientHelloMinimumSize = 1024; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_PROTOCOL_H_
diff --git a/quiche/quic/core/crypto/crypto_secret_boxer.cc b/quiche/quic/core/crypto/crypto_secret_boxer.cc new file mode 100644 index 0000000..f61a3fc --- /dev/null +++ b/quiche/quic/core/crypto/crypto_secret_boxer.cc
@@ -0,0 +1,148 @@ +// 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 <cstdint> +#include <string> + +#include "quiche/quic/core/crypto/crypto_secret_boxer.h" + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/aead.h" +#include "third_party/boringssl/src/include/openssl/err.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +// kSIVNonceSize contains the number of bytes of nonce in each AES-GCM-SIV box. +// AES-GCM-SIV takes a 12-byte nonce and, since the messages are so small, each +// key is good for more than 2^64 source-address tokens. See table 1 of +// https://eprint.iacr.org/2017/168.pdf +static const size_t kSIVNonceSize = 12; + +// AES-GCM-SIV comes in AES-128 and AES-256 flavours. The AES-256 version is +// used here so that the key size matches the 256-bit XSalsa20 keys that we +// used to use. +static const size_t kBoxKeySize = 32; + +struct CryptoSecretBoxer::State { + // ctxs are the initialised AEAD contexts. These objects contain the + // scheduled AES state for each of the keys. + std::vector<bssl::UniquePtr<EVP_AEAD_CTX>> ctxs; +}; + +CryptoSecretBoxer::CryptoSecretBoxer() {} + +CryptoSecretBoxer::~CryptoSecretBoxer() {} + +// static +size_t CryptoSecretBoxer::GetKeySize() { + return kBoxKeySize; +} + +// kAEAD is the AEAD used for boxing: AES-256-GCM-SIV. +static const EVP_AEAD* (*const kAEAD)() = EVP_aead_aes_256_gcm_siv; + +bool CryptoSecretBoxer::SetKeys(const std::vector<std::string>& keys) { + if (keys.empty()) { + QUIC_LOG(DFATAL) << "No keys supplied!"; + return false; + } + const EVP_AEAD* const aead = kAEAD(); + std::unique_ptr<State> new_state(new State); + + for (const std::string& key : keys) { + QUICHE_DCHECK_EQ(kBoxKeySize, key.size()); + bssl::UniquePtr<EVP_AEAD_CTX> ctx( + EVP_AEAD_CTX_new(aead, reinterpret_cast<const uint8_t*>(key.data()), + key.size(), EVP_AEAD_DEFAULT_TAG_LENGTH)); + if (!ctx) { + ERR_clear_error(); + QUIC_LOG(DFATAL) << "EVP_AEAD_CTX_init failed"; + return false; + } + + new_state->ctxs.push_back(std::move(ctx)); + } + + QuicWriterMutexLock l(&lock_); + state_ = std::move(new_state); + return true; +} + +std::string CryptoSecretBoxer::Box(QuicRandom* rand, + absl::string_view plaintext) const { + // The box is formatted as: + // 12 bytes of random nonce + // n bytes of ciphertext + // 16 bytes of authenticator + size_t out_len = + kSIVNonceSize + plaintext.size() + EVP_AEAD_max_overhead(kAEAD()); + + std::string ret; + ret.resize(out_len); + uint8_t* out = reinterpret_cast<uint8_t*>(const_cast<char*>(ret.data())); + + // Write kSIVNonceSize bytes of random nonce to the beginning of the output + // buffer. + rand->RandBytes(out, kSIVNonceSize); + const uint8_t* const nonce = out; + out += kSIVNonceSize; + out_len -= kSIVNonceSize; + + size_t bytes_written; + { + QuicReaderMutexLock l(&lock_); + if (!EVP_AEAD_CTX_seal(state_->ctxs[0].get(), out, &bytes_written, out_len, + nonce, kSIVNonceSize, + reinterpret_cast<const uint8_t*>(plaintext.data()), + plaintext.size(), nullptr, 0)) { + ERR_clear_error(); + QUIC_LOG(DFATAL) << "EVP_AEAD_CTX_seal failed"; + return ""; + } + } + + QUICHE_DCHECK_EQ(out_len, bytes_written); + return ret; +} + +bool CryptoSecretBoxer::Unbox(absl::string_view in_ciphertext, + std::string* out_storage, + absl::string_view* out) const { + if (in_ciphertext.size() < kSIVNonceSize) { + return false; + } + + const uint8_t* const nonce = + reinterpret_cast<const uint8_t*>(in_ciphertext.data()); + const uint8_t* const ciphertext = nonce + kSIVNonceSize; + const size_t ciphertext_len = in_ciphertext.size() - kSIVNonceSize; + + out_storage->resize(ciphertext_len); + + bool ok = false; + { + QuicReaderMutexLock l(&lock_); + for (const bssl::UniquePtr<EVP_AEAD_CTX>& ctx : state_->ctxs) { + size_t bytes_written; + if (EVP_AEAD_CTX_open(ctx.get(), + reinterpret_cast<uint8_t*>( + const_cast<char*>(out_storage->data())), + &bytes_written, ciphertext_len, nonce, + kSIVNonceSize, ciphertext, ciphertext_len, nullptr, + 0)) { + ok = true; + *out = absl::string_view(out_storage->data(), bytes_written); + break; + } + + ERR_clear_error(); + } + } + + return ok; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_secret_boxer.h b/quiche/quic/core/crypto/crypto_secret_boxer.h new file mode 100644 index 0000000..3010e14 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_secret_boxer.h
@@ -0,0 +1,69 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_SECRET_BOXER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_SECRET_BOXER_H_ + +#include <cstddef> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/quic/platform/api/quic_mutex.h" + +namespace quic { + +class QuicRandom; + +// CryptoSecretBoxer encrypts small chunks of plaintext (called 'boxing') and +// then, later, can authenticate+decrypt the resulting boxes. This object is +// thread-safe. +class QUIC_EXPORT_PRIVATE CryptoSecretBoxer { + public: + CryptoSecretBoxer(); + CryptoSecretBoxer(const CryptoSecretBoxer&) = delete; + CryptoSecretBoxer& operator=(const CryptoSecretBoxer&) = delete; + ~CryptoSecretBoxer(); + + // GetKeySize returns the number of bytes in a key. + static size_t GetKeySize(); + + // SetKeys sets a list of encryption keys. The first key in the list will be + // used by |Box|, but all supplied keys will be tried by |Unbox|, to handle + // key skew across the fleet. This must be called before |Box| or |Unbox|. + // Keys must be |GetKeySize()| bytes long. No change is made if any key is + // invalid, or if there are no keys supplied. + bool SetKeys(const std::vector<std::string>& keys); + + // Box encrypts |plaintext| using a random nonce generated from |rand| and + // returns the resulting ciphertext. Since an authenticator and nonce are + // included, the result will be slightly larger than |plaintext|. The first + // key in the vector supplied to |SetKeys| will be used. |SetKeys| must be + // called before calling this method. + std::string Box(QuicRandom* rand, absl::string_view plaintext) const; + + // Unbox takes the result of a previous call to |Box| in |ciphertext| and + // authenticates+decrypts it. If |ciphertext| cannot be decrypted with any of + // the supplied keys, the function returns false. Otherwise, |out_storage| is + // used to store the result and |out| is set to point into |out_storage| and + // contains the original plaintext. + bool Unbox(absl::string_view ciphertext, + std::string* out_storage, + absl::string_view* out) const; + + private: + struct State; + + mutable QuicMutex lock_; + + // state_ is an opaque pointer to whatever additional state the concrete + // implementation of CryptoSecretBoxer requires. + std::unique_ptr<State> state_ QUIC_GUARDED_BY(lock_); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_SECRET_BOXER_H_
diff --git a/quiche/quic/core/crypto/crypto_secret_boxer_test.cc b/quiche/quic/core/crypto/crypto_secret_boxer_test.cc new file mode 100644 index 0000000..2c499fc --- /dev/null +++ b/quiche/quic/core/crypto/crypto_secret_boxer_test.cc
@@ -0,0 +1,82 @@ +// 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 "quiche/quic/core/crypto/crypto_secret_boxer.h" + +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { + +class CryptoSecretBoxerTest : public QuicTest {}; + +TEST_F(CryptoSecretBoxerTest, BoxAndUnbox) { + absl::string_view message("hello world"); + + CryptoSecretBoxer boxer; + boxer.SetKeys({std::string(CryptoSecretBoxer::GetKeySize(), 0x11)}); + + const std::string box = boxer.Box(QuicRandom::GetInstance(), message); + + std::string storage; + absl::string_view result; + EXPECT_TRUE(boxer.Unbox(box, &storage, &result)); + EXPECT_EQ(result, message); + + EXPECT_FALSE(boxer.Unbox(std::string(1, 'X') + box, &storage, &result)); + EXPECT_FALSE( + boxer.Unbox(box.substr(1, std::string::npos), &storage, &result)); + EXPECT_FALSE(boxer.Unbox(std::string(), &storage, &result)); + EXPECT_FALSE(boxer.Unbox( + std::string(1, box[0] ^ 0x80) + box.substr(1, std::string::npos), + &storage, &result)); +} + +// Helper function to test whether one boxer can decode the output of another. +static bool CanDecode(const CryptoSecretBoxer& decoder, + const CryptoSecretBoxer& encoder) { + absl::string_view message("hello world"); + const std::string boxed = encoder.Box(QuicRandom::GetInstance(), message); + std::string storage; + absl::string_view result; + bool ok = decoder.Unbox(boxed, &storage, &result); + if (ok) { + EXPECT_EQ(result, message); + } + return ok; +} + +TEST_F(CryptoSecretBoxerTest, MultipleKeys) { + std::string key_11(CryptoSecretBoxer::GetKeySize(), 0x11); + std::string key_12(CryptoSecretBoxer::GetKeySize(), 0x12); + + CryptoSecretBoxer boxer_11, boxer_12, boxer; + EXPECT_TRUE(boxer_11.SetKeys({key_11})); + EXPECT_TRUE(boxer_12.SetKeys({key_12})); + EXPECT_TRUE(boxer.SetKeys({key_12, key_11})); + + // Neither single-key boxer can decode the other's tokens. + EXPECT_FALSE(CanDecode(boxer_11, boxer_12)); + EXPECT_FALSE(CanDecode(boxer_12, boxer_11)); + + // |boxer| encodes with the first key, which is key_12. + EXPECT_TRUE(CanDecode(boxer_12, boxer)); + EXPECT_FALSE(CanDecode(boxer_11, boxer)); + + // The boxer with both keys can decode tokens from either single-key boxer. + EXPECT_TRUE(CanDecode(boxer, boxer_11)); + EXPECT_TRUE(CanDecode(boxer, boxer_12)); + + // After we flush key_11 from |boxer|, it can no longer decode tokens from + // |boxer_11|. + EXPECT_TRUE(boxer.SetKeys({key_12})); + EXPECT_FALSE(CanDecode(boxer, boxer_11)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_server_test.cc b/quiche/quic/core/crypto/crypto_server_test.cc new file mode 100644 index 0000000..3dcf18a --- /dev/null +++ b/quiche/quic/core/crypto/crypto_server_test.cc
@@ -0,0 +1,1128 @@ +// 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 <algorithm> +#include <cstdint> +#include <memory> +#include <ostream> +#include <string> +#include <utility> +#include <vector> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/sha.h" +#include "quiche/quic/core/crypto/cert_compressor.h" +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_utils.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/crypto/quic_crypto_server_config.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/proto/crypto_server_config_proto.h" +#include "quiche/quic/core/quic_socket_address_coder.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/failing_proof_source.h" +#include "quiche/quic/test_tools/mock_clock.h" +#include "quiche/quic/test_tools/mock_random.h" +#include "quiche/quic/test_tools/quic_crypto_server_config_peer.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { +namespace test { + +namespace { + +class DummyProofVerifierCallback : public ProofVerifierCallback { + public: + DummyProofVerifierCallback() {} + ~DummyProofVerifierCallback() override {} + + void Run(bool /*ok*/, + const std::string& /*error_details*/, + std::unique_ptr<ProofVerifyDetails>* /*details*/) override { + QUICHE_DCHECK(false); + } +}; + +const char kOldConfigId[] = "old-config-id"; + +} // namespace + +struct TestParams { + friend std::ostream& operator<<(std::ostream& os, const TestParams& p) { + os << " versions: " + << ParsedQuicVersionVectorToString(p.supported_versions) << " }"; + return os; + } + + // Versions supported by client and server. + ParsedQuicVersionVector supported_versions; +}; + +// Used by ::testing::PrintToStringParamName(). +std::string PrintToString(const TestParams& p) { + std::string rv = ParsedQuicVersionVectorToString(p.supported_versions); + std::replace(rv.begin(), rv.end(), ',', '_'); + return rv; +} + +// Constructs various test permutations. +std::vector<TestParams> GetTestParams() { + std::vector<TestParams> params; + + // Start with all versions, remove highest on each iteration. + ParsedQuicVersionVector supported_versions = AllSupportedVersions(); + while (!supported_versions.empty()) { + params.push_back({supported_versions}); + supported_versions.erase(supported_versions.begin()); + } + + return params; +} + +class CryptoServerTest : public QuicTestWithParam<TestParams> { + public: + CryptoServerTest() + : rand_(QuicRandom::GetInstance()), + client_address_(QuicIpAddress::Loopback4(), 1234), + client_version_(UnsupportedQuicVersion()), + config_(QuicCryptoServerConfig::TESTING, + rand_, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()), + peer_(&config_), + compressed_certs_cache_( + QuicCompressedCertsCache::kQuicCompressedCertsCacheSize), + params_(new QuicCryptoNegotiatedParameters), + signed_config_(new QuicSignedServerConfig), + chlo_packet_size_(kDefaultMaxPacketSize) { + supported_versions_ = GetParam().supported_versions; + config_.set_enable_serving_sct(true); + + client_version_ = supported_versions_.front(); + client_version_label_ = CreateQuicVersionLabel(client_version_); + client_version_string_ = + std::string(reinterpret_cast<const char*>(&client_version_label_), + sizeof(client_version_label_)); + } + + void SetUp() override { + QuicCryptoServerConfig::ConfigOptions old_config_options; + old_config_options.id = kOldConfigId; + config_.AddDefaultConfig(rand_, &clock_, old_config_options); + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1000)); + QuicServerConfigProtobuf primary_config = + config_.GenerateConfig(rand_, &clock_, config_options_); + primary_config.set_primary_time(clock_.WallNow().ToUNIXSeconds()); + std::unique_ptr<CryptoHandshakeMessage> msg( + config_.AddConfig(primary_config, clock_.WallNow())); + + absl::string_view orbit; + QUICHE_CHECK(msg->GetStringPiece(kORBT, &orbit)); + QUICHE_CHECK_EQ(sizeof(orbit_), orbit.size()); + memcpy(orbit_, orbit.data(), orbit.size()); + + char public_value[32]; + memset(public_value, 42, sizeof(public_value)); + + nonce_hex_ = "#" + absl::BytesToHexString(GenerateNonce()); + pub_hex_ = "#" + absl::BytesToHexString( + absl::string_view(public_value, sizeof(public_value))); + + CryptoHandshakeMessage client_hello = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"CSCT", ""}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + ShouldSucceed(client_hello); + // The message should be rejected because the source-address token is + // missing. + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + + absl::string_view srct; + ASSERT_TRUE(out_.GetStringPiece(kSourceAddressTokenTag, &srct)); + srct_hex_ = "#" + absl::BytesToHexString(srct); + + absl::string_view scfg; + ASSERT_TRUE(out_.GetStringPiece(kSCFG, &scfg)); + server_config_ = CryptoFramer::ParseMessage(scfg); + + absl::string_view scid; + ASSERT_TRUE(server_config_->GetStringPiece(kSCID, &scid)); + scid_hex_ = "#" + absl::BytesToHexString(scid); + + signed_config_ = + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig>( + new QuicSignedServerConfig()); + QUICHE_DCHECK(signed_config_->chain.get() == nullptr); + } + + // Helper used to accept the result of ValidateClientHello and pass + // it on to ProcessClientHello. + class ValidateCallback : public ValidateClientHelloResultCallback { + public: + ValidateCallback(CryptoServerTest* test, + bool should_succeed, + const char* error_substr, + bool* called) + : test_(test), + should_succeed_(should_succeed), + error_substr_(error_substr), + called_(called) { + *called_ = false; + } + + void Run(quiche::QuicheReferenceCountedPointer<Result> result, + std::unique_ptr<ProofSource::Details> /* details */) override { + ASSERT_FALSE(*called_); + test_->ProcessValidationResult(std::move(result), should_succeed_, + error_substr_); + *called_ = true; + } + + private: + CryptoServerTest* test_; + const bool should_succeed_; + const char* const error_substr_; + bool* called_; + }; + + void CheckServerHello(const CryptoHandshakeMessage& server_hello) { + QuicVersionLabelVector versions; + server_hello.GetVersionLabelList(kVER, &versions); + ASSERT_EQ(supported_versions_.size(), versions.size()); + for (size_t i = 0; i < versions.size(); ++i) { + EXPECT_EQ(CreateQuicVersionLabel(supported_versions_[i]), versions[i]); + } + + absl::string_view address; + ASSERT_TRUE(server_hello.GetStringPiece(kCADR, &address)); + QuicSocketAddressCoder decoder; + ASSERT_TRUE(decoder.Decode(address.data(), address.size())); + EXPECT_EQ(client_address_.host(), decoder.ip()); + EXPECT_EQ(client_address_.port(), decoder.port()); + } + + void ShouldSucceed(const CryptoHandshakeMessage& message) { + bool called = false; + QuicSocketAddress server_address(QuicIpAddress::Any4(), 5); + config_.ValidateClientHello( + message, client_address_, server_address, + supported_versions_.front().transport_version, &clock_, signed_config_, + std::make_unique<ValidateCallback>(this, true, "", &called)); + EXPECT_TRUE(called); + } + + void ShouldFailMentioning(const char* error_substr, + const CryptoHandshakeMessage& message) { + bool called = false; + ShouldFailMentioning(error_substr, message, &called); + EXPECT_TRUE(called); + } + + void ShouldFailMentioning(const char* error_substr, + const CryptoHandshakeMessage& message, + bool* called) { + QuicSocketAddress server_address(QuicIpAddress::Any4(), 5); + config_.ValidateClientHello( + message, client_address_, server_address, + supported_versions_.front().transport_version, &clock_, signed_config_, + std::make_unique<ValidateCallback>(this, false, error_substr, called)); + } + + class ProcessCallback : public ProcessClientHelloResultCallback { + public: + ProcessCallback( + quiche::QuicheReferenceCountedPointer<ValidateCallback::Result> result, + bool should_succeed, const char* error_substr, bool* called, + CryptoHandshakeMessage* out) + : result_(std::move(result)), + should_succeed_(should_succeed), + error_substr_(error_substr), + called_(called), + out_(out) { + *called_ = false; + } + + void Run(QuicErrorCode error, + const std::string& error_details, + std::unique_ptr<CryptoHandshakeMessage> message, + std::unique_ptr<DiversificationNonce> /*diversification_nonce*/, + std::unique_ptr<ProofSource::Details> /*proof_source_details*/) + override { + if (should_succeed_) { + ASSERT_EQ(error, QUIC_NO_ERROR) + << "Message failed with error " << error_details << ": " + << result_->client_hello.DebugString(); + } else { + ASSERT_NE(error, QUIC_NO_ERROR) + << "Message didn't fail: " << result_->client_hello.DebugString(); + EXPECT_TRUE(absl::StrContains(error_details, error_substr_)) + << error_substr_ << " not in " << error_details; + } + if (message != nullptr) { + *out_ = *message; + } + *called_ = true; + } + + private: + const quiche::QuicheReferenceCountedPointer<ValidateCallback::Result> + result_; + const bool should_succeed_; + const char* const error_substr_; + bool* called_; + CryptoHandshakeMessage* out_; + }; + + void ProcessValidationResult( + quiche::QuicheReferenceCountedPointer<ValidateCallback::Result> result, + bool should_succeed, const char* error_substr) { + QuicSocketAddress server_address(QuicIpAddress::Any4(), 5); + bool called; + config_.ProcessClientHello( + result, /*reject_only=*/false, + /*connection_id=*/TestConnectionId(1), server_address, client_address_, + supported_versions_.front(), supported_versions_, &clock_, rand_, + &compressed_certs_cache_, params_, signed_config_, + /*total_framing_overhead=*/50, chlo_packet_size_, + std::make_unique<ProcessCallback>(result, should_succeed, error_substr, + &called, &out_)); + EXPECT_TRUE(called); + } + + std::string GenerateNonce() { + std::string nonce; + CryptoUtils::GenerateNonce( + clock_.WallNow(), rand_, + absl::string_view(reinterpret_cast<const char*>(orbit_), + sizeof(orbit_)), + &nonce); + return nonce; + } + + void CheckRejectReasons( + const HandshakeFailureReason* expected_handshake_failures, + size_t expected_count) { + QuicTagVector reject_reasons; + static_assert(sizeof(QuicTag) == sizeof(uint32_t), "header out of sync"); + QuicErrorCode error_code = out_.GetTaglist(kRREJ, &reject_reasons); + ASSERT_THAT(error_code, IsQuicNoError()); + + EXPECT_EQ(expected_count, reject_reasons.size()); + for (size_t i = 0; i < reject_reasons.size(); ++i) { + EXPECT_EQ(static_cast<QuicTag>(expected_handshake_failures[i]), + reject_reasons[i]); + } + } + + void CheckRejectTag() { + ASSERT_EQ(kREJ, out_.tag()) << QuicTagToString(out_.tag()); + } + + std::string XlctHexString() { + uint64_t xlct = crypto_test_utils::LeafCertHashForTesting(); + return "#" + absl::BytesToHexString(absl::string_view( + reinterpret_cast<char*>(&xlct), sizeof(xlct))); + } + + protected: + QuicRandom* const rand_; + MockRandom rand_for_id_generation_; + MockClock clock_; + QuicSocketAddress client_address_; + ParsedQuicVersionVector supported_versions_; + ParsedQuicVersion client_version_; + QuicVersionLabel client_version_label_; + std::string client_version_string_; + QuicCryptoServerConfig config_; + QuicCryptoServerConfigPeer peer_; + QuicCompressedCertsCache compressed_certs_cache_; + QuicCryptoServerConfig::ConfigOptions config_options_; + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_; + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config_; + CryptoHandshakeMessage out_; + uint8_t orbit_[kOrbitSize]; + size_t chlo_packet_size_; + + // These strings contain hex escaped values from the server suitable for using + // when constructing client hello messages. + std::string nonce_hex_, pub_hex_, srct_hex_, scid_hex_; + std::unique_ptr<CryptoHandshakeMessage> server_config_; +}; + +INSTANTIATE_TEST_SUITE_P(CryptoServerTests, + CryptoServerTest, + ::testing::ValuesIn(GetTestParams()), + ::testing::PrintToStringParamName()); + +TEST_P(CryptoServerTest, BadSNI) { + // clang-format off + std::vector<std::string> badSNIs = { + "", + "#00", + "#ff00", + "127.0.0.1", + "ffee::1", + }; + // clang-format on + + for (const std::string& bad_sni : badSNIs) { + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"SNI", bad_sni}, {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + ShouldFailMentioning("SNI", msg); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + } + + // Check that SNIs without dots are allowed + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"SNI", "foo"}, {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + ShouldSucceed(msg); +} + +TEST_P(CryptoServerTest, DefaultCert) { + // Check that the server replies with a default certificate when no SNI is + // specified. The CHLO is constructed to generate a REJ with certs, so must + // not contain a valid STK, and must include PDMD. + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"PDMD", "X509"}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + absl::string_view cert, proof, cert_sct; + EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert)); + EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof)); + EXPECT_TRUE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct)); + EXPECT_NE(0u, cert.size()); + EXPECT_NE(0u, proof.size()); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + EXPECT_LT(0u, cert_sct.size()); +} + +TEST_P(CryptoServerTest, RejectTooLarge) { + // Check that the server replies with no certificate when a CHLO is + // constructed with a PDMD but no SKT when the REJ would be too large. + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"PDMD", "X509"}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + // The REJ will be larger than the CHLO so no PROF or CRT will be sent. + config_.set_chlo_multiplier(1); + + ShouldSucceed(msg); + absl::string_view cert, proof, cert_sct; + EXPECT_FALSE(out_.GetStringPiece(kCertificateTag, &cert)); + EXPECT_FALSE(out_.GetStringPiece(kPROF, &proof)); + EXPECT_FALSE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct)); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, RejectNotTooLarge) { + // When the CHLO packet is large enough, ensure that a full REJ is sent. + chlo_packet_size_ *= 5; + + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"PDMD", "X509"}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + // The REJ will be larger than the CHLO so no PROF or CRT will be sent. + config_.set_chlo_multiplier(1); + + ShouldSucceed(msg); + absl::string_view cert, proof, cert_sct; + EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert)); + EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof)); + EXPECT_TRUE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct)); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, RejectTooLargeButValidSTK) { + // Check that the server replies with no certificate when a CHLO is + // constructed with a PDMD but no SKT when the REJ would be too large. + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"#004b5453", srct_hex_}, + {"PDMD", "X509"}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + // The REJ will be larger than the CHLO so no PROF or CRT will be sent. + config_.set_chlo_multiplier(1); + + ShouldSucceed(msg); + absl::string_view cert, proof, cert_sct; + EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert)); + EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof)); + EXPECT_TRUE(out_.GetStringPiece(kCertificateSCTTag, &cert_sct)); + EXPECT_NE(0u, cert.size()); + EXPECT_NE(0u, proof.size()); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, BadSourceAddressToken) { + // Invalid source-address tokens should be ignored. + // clang-format off + static const char* const kBadSourceAddressTokens[] = { + "", + "foo", + "#0000", + "#0000000000000000000000000000000000000000", + }; + // clang-format on + + for (size_t i = 0; i < ABSL_ARRAYSIZE(kBadSourceAddressTokens); i++) { + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"STK", kBadSourceAddressTokens[i]}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + ShouldSucceed(msg); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + } +} + +TEST_P(CryptoServerTest, BadClientNonce) { + // clang-format off + static const char* const kBadNonces[] = { + "", + "#0000", + "#0000000000000000000000000000000000000000", + }; + // clang-format on + + for (size_t i = 0; i < ABSL_ARRAYSIZE(kBadNonces); i++) { + // Invalid nonces should be ignored, in an inchoate CHLO. + + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"NONC", kBadNonces[i]}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + + // Invalid nonces should result in CLIENT_NONCE_INVALID_FAILURE. + CryptoHandshakeMessage msg1 = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", kBadNonces[i]}, + {"NONP", kBadNonces[i]}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg1); + + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons1[] = { + CLIENT_NONCE_INVALID_FAILURE}; + CheckRejectReasons(kRejectReasons1, ABSL_ARRAYSIZE(kRejectReasons1)); + } +} + +TEST_P(CryptoServerTest, NoClientNonce) { + // No client nonces should result in INCHOATE_HELLO_FAILURE. + + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + + CryptoHandshakeMessage msg1 = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg1); + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons1[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons1, ABSL_ARRAYSIZE(kRejectReasons1)); +} + +TEST_P(CryptoServerTest, DowngradeAttack) { + if (supported_versions_.size() == 1) { + // No downgrade attack is possible if the server only supports one version. + return; + } + // Set the client's preferred version to a supported version that + // is not the "current" version (supported_versions_.front()). + std::string bad_version = + ParsedQuicVersionToString(supported_versions_.back()); + + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"VER\0", bad_version}}, kClientHelloMinimumSize); + + ShouldFailMentioning("Downgrade", msg); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, CorruptServerConfig) { + // This tests corrupted server config. + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", (std::string(1, 'X') + scid_hex_)}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, CorruptSourceAddressToken) { + // This tests corrupted source address token. + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", (std::string(1, 'X') + srct_hex_)}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons[] = { + SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, CorruptSourceAddressTokenIsStillAccepted) { + // This tests corrupted source address token. + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", (std::string(1, 'X') + srct_hex_)}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + config_.set_validate_source_address_token(false); + + ShouldSucceed(msg); + EXPECT_EQ(kSHLO, out_.tag()); +} + +TEST_P(CryptoServerTest, CorruptClientNonceAndSourceAddressToken) { + // This test corrupts client nonce and source address token. + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", (std::string(1, 'X') + srct_hex_)}, + {"PUBS", pub_hex_}, + {"NONC", (std::string(1, 'X') + nonce_hex_)}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons[] = { + SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, CLIENT_NONCE_INVALID_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, CorruptMultipleTags) { + // This test corrupts client nonce, server nonce and source address token. + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", (std::string(1, 'X') + srct_hex_)}, + {"PUBS", pub_hex_}, + {"NONC", (std::string(1, 'X') + nonce_hex_)}, + {"NONP", (std::string(1, 'X') + nonce_hex_)}, + {"SNO\0", (std::string(1, 'X') + nonce_hex_)}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + CheckRejectTag(); + + const HandshakeFailureReason kRejectReasons[] = { + SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, CLIENT_NONCE_INVALID_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, NoServerNonce) { + // When no server nonce is present and no strike register is configured, + // the CHLO should be rejected. + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"NONP", nonce_hex_}, + {"XLCT", XlctHexString()}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + + // Even without a server nonce, this ClientHello should be accepted in + // version 33. + ASSERT_EQ(kSHLO, out_.tag()); + CheckServerHello(out_); +} + +TEST_P(CryptoServerTest, ProofForSuppliedServerConfig) { + client_address_ = QuicSocketAddress(QuicIpAddress::Loopback6(), 1234); + + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"PDMD", "X509"}, + {"SCID", kOldConfigId}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"NONP", "123456789012345678901234567890"}, + {"VER\0", client_version_string_}, + {"XLCT", XlctHexString()}}, + kClientHelloMinimumSize); + + ShouldSucceed(msg); + // The message should be rejected because the source-address token is no + // longer valid. + CheckRejectTag(); + const HandshakeFailureReason kRejectReasons[] = { + SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); + + absl::string_view cert, proof, scfg_str; + EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert)); + EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof)); + EXPECT_TRUE(out_.GetStringPiece(kSCFG, &scfg_str)); + std::unique_ptr<CryptoHandshakeMessage> scfg( + CryptoFramer::ParseMessage(scfg_str)); + absl::string_view scid; + EXPECT_TRUE(scfg->GetStringPiece(kSCID, &scid)); + EXPECT_NE(scid, kOldConfigId); + + // Get certs from compressed certs. + std::vector<std::string> cached_certs; + + std::vector<std::string> certs; + ASSERT_TRUE(CertCompressor::DecompressChain(cert, cached_certs, &certs)); + + // Check that the proof in the REJ message is valid. + std::unique_ptr<ProofVerifier> proof_verifier( + crypto_test_utils::ProofVerifierForTesting()); + std::unique_ptr<ProofVerifyContext> verify_context( + crypto_test_utils::ProofVerifyContextForTesting()); + std::unique_ptr<ProofVerifyDetails> details; + std::string error_details; + std::unique_ptr<ProofVerifierCallback> callback( + new DummyProofVerifierCallback()); + const std::string chlo_hash = + CryptoUtils::HashHandshakeMessage(msg, Perspective::IS_SERVER); + EXPECT_EQ(QUIC_SUCCESS, + proof_verifier->VerifyProof( + "test.example.com", 443, (std::string(scfg_str)), + client_version_.transport_version, chlo_hash, certs, "", + (std::string(proof)), verify_context.get(), &error_details, + &details, std::move(callback))); +} + +TEST_P(CryptoServerTest, RejectInvalidXlct) { + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"VER\0", client_version_string_}, + {"XLCT", "#0102030405060708"}}, + kClientHelloMinimumSize); + + // If replay protection isn't disabled, then + // QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false + // and cause ProcessClientHello to exit early (and generate a REJ message). + config_.set_replay_protection(false); + + ShouldSucceed(msg); + + const HandshakeFailureReason kRejectReasons[] = { + INVALID_EXPECTED_LEAF_CERTIFICATE}; + + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +TEST_P(CryptoServerTest, ValidXlct) { + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"VER\0", client_version_string_}, + {"XLCT", XlctHexString()}}, + kClientHelloMinimumSize); + + // If replay protection isn't disabled, then + // QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false + // and cause ProcessClientHello to exit early (and generate a REJ message). + config_.set_replay_protection(false); + + ShouldSucceed(msg); + EXPECT_EQ(kSHLO, out_.tag()); +} + +TEST_P(CryptoServerTest, NonceInSHLO) { + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"VER\0", client_version_string_}, + {"XLCT", XlctHexString()}}, + kClientHelloMinimumSize); + + // If replay protection isn't disabled, then + // QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false + // and cause ProcessClientHello to exit early (and generate a REJ message). + config_.set_replay_protection(false); + + ShouldSucceed(msg); + EXPECT_EQ(kSHLO, out_.tag()); + + absl::string_view nonce; + EXPECT_TRUE(out_.GetStringPiece(kServerNonceTag, &nonce)); +} + +TEST_P(CryptoServerTest, ProofSourceFailure) { + // Install a ProofSource which will unconditionally fail + peer_.ResetProofSource(std::unique_ptr<ProofSource>(new FailingProofSource)); + + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"PDMD", "X509"}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + // Just ensure that we don't crash as occurred in b/33916924. + ShouldFailMentioning("", msg); +} + +// Regression test for crbug.com/723604 +// For 2RTT, if the first CHLO from the client contains hashes of cached +// certs (stored in CCRT tag) but the second CHLO does not, then the second REJ +// from the server should not contain hashes of cached certs. +TEST_P(CryptoServerTest, TwoRttServerDropCachedCerts) { + // Send inchoate CHLO to get cert chain from server. This CHLO is only for + // the purpose of getting the server's certs; it is not part of the 2RTT + // handshake. + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + ShouldSucceed(msg); + + // Decompress cert chain from server to individual certs. + absl::string_view certs_compressed; + ASSERT_TRUE(out_.GetStringPiece(kCertificateTag, &certs_compressed)); + ASSERT_NE(0u, certs_compressed.size()); + std::vector<std::string> certs; + ASSERT_TRUE(CertCompressor::DecompressChain(certs_compressed, + /*cached_certs=*/{}, &certs)); + + // Start 2-RTT. Client sends CHLO with bad source-address token and hashes of + // the certs, which tells the server that the client has cached those certs. + config_.set_chlo_multiplier(1); + const char kBadSourceAddressToken[] = ""; + msg.SetStringPiece(kSourceAddressTokenTag, kBadSourceAddressToken); + std::vector<uint64_t> hashes(certs.size()); + for (size_t i = 0; i < certs.size(); ++i) { + hashes[i] = QuicUtils::QuicUtils::FNV1a_64_Hash(certs[i]); + } + msg.SetVector(kCCRT, hashes); + ShouldSucceed(msg); + + // Server responds with inchoate REJ containing valid source-address token. + absl::string_view srct; + ASSERT_TRUE(out_.GetStringPiece(kSourceAddressTokenTag, &srct)); + + // Client now drops cached certs; sends CHLO with updated source-address + // token but no hashes of certs. + msg.SetStringPiece(kSourceAddressTokenTag, srct); + msg.Erase(kCCRT); + ShouldSucceed(msg); + + // Server response's cert chain should not contain hashes of + // previously-cached certs. + ASSERT_TRUE(out_.GetStringPiece(kCertificateTag, &certs_compressed)); + ASSERT_NE(0u, certs_compressed.size()); + ASSERT_TRUE(CertCompressor::DecompressChain(certs_compressed, + /*cached_certs=*/{}, &certs)); +} + +class CryptoServerConfigGenerationTest : public QuicTest {}; + +TEST_F(CryptoServerConfigGenerationTest, Determinism) { + // Test that using a deterministic PRNG causes the server-config to be + // deterministic. + + MockRandom rand_a, rand_b; + const QuicCryptoServerConfig::ConfigOptions options; + MockClock clock; + + QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + QuicCryptoServerConfig b(QuicCryptoServerConfig::TESTING, &rand_b, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + std::unique_ptr<CryptoHandshakeMessage> scfg_a( + a.AddDefaultConfig(&rand_a, &clock, options)); + std::unique_ptr<CryptoHandshakeMessage> scfg_b( + b.AddDefaultConfig(&rand_b, &clock, options)); + + ASSERT_EQ(scfg_a->DebugString(), scfg_b->DebugString()); +} + +TEST_F(CryptoServerConfigGenerationTest, SCIDVaries) { + // This test ensures that the server config ID varies for different server + // configs. + + MockRandom rand_a, rand_b; + const QuicCryptoServerConfig::ConfigOptions options; + MockClock clock; + + QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + rand_b.ChangeValue(); + QuicCryptoServerConfig b(QuicCryptoServerConfig::TESTING, &rand_b, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + std::unique_ptr<CryptoHandshakeMessage> scfg_a( + a.AddDefaultConfig(&rand_a, &clock, options)); + std::unique_ptr<CryptoHandshakeMessage> scfg_b( + b.AddDefaultConfig(&rand_b, &clock, options)); + + absl::string_view scid_a, scid_b; + EXPECT_TRUE(scfg_a->GetStringPiece(kSCID, &scid_a)); + EXPECT_TRUE(scfg_b->GetStringPiece(kSCID, &scid_b)); + + EXPECT_NE(scid_a, scid_b); +} + +TEST_F(CryptoServerConfigGenerationTest, SCIDIsHashOfServerConfig) { + MockRandom rand_a; + const QuicCryptoServerConfig::ConfigOptions options; + MockClock clock; + + QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + std::unique_ptr<CryptoHandshakeMessage> scfg( + a.AddDefaultConfig(&rand_a, &clock, options)); + + absl::string_view scid; + EXPECT_TRUE(scfg->GetStringPiece(kSCID, &scid)); + // Need to take a copy of |scid| has we're about to call |Erase|. + const std::string scid_str(scid); + + scfg->Erase(kSCID); + scfg->MarkDirty(); + const QuicData& serialized(scfg->GetSerialized()); + + uint8_t digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast<const uint8_t*>(serialized.data()), + serialized.length(), digest); + + // scid is a SHA-256 hash, truncated to 16 bytes. + ASSERT_EQ(scid.size(), 16u); + EXPECT_EQ(0, memcmp(digest, scid_str.c_str(), scid.size())); +} + +// Those tests were declared incorrectly and thus never ran in first place. +// TODO(b/147891553): figure out if we should fix or delete those. +#if 0 + +class CryptoServerTestNoConfig : public CryptoServerTest { + public: + void SetUp() override { + // Deliberately don't add a config so that we can test this situation. + } +}; + +INSTANTIATE_TEST_SUITE_P(CryptoServerTestsNoConfig, + CryptoServerTestNoConfig, + ::testing::ValuesIn(GetTestParams()), + ::testing::PrintToStringParamName()); + +TEST_P(CryptoServerTestNoConfig, DontCrash) { + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + ShouldFailMentioning("No config", msg); + + const HandshakeFailureReason kRejectReasons[] = { + SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; + CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); +} + +class CryptoServerTestOldVersion : public CryptoServerTest { + public: + void SetUp() override { + client_version_ = supported_versions_.back(); + client_version_string_ = ParsedQuicVersionToString(client_version_); + CryptoServerTest::SetUp(); + } +}; + +INSTANTIATE_TEST_SUITE_P(CryptoServerTestsOldVersion, + CryptoServerTestOldVersion, + ::testing::ValuesIn(GetTestParams()), + ::testing::PrintToStringParamName()); + +TEST_P(CryptoServerTestOldVersion, ServerIgnoresXlct) { + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"VER\0", client_version_string_}, + {"XLCT", "#0100000000000000"}}, + kClientHelloMinimumSize); + + // If replay protection isn't disabled, then + // QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false + // and cause ProcessClientHello to exit early (and generate a REJ message). + config_.set_replay_protection(false); + + ShouldSucceed(msg); + EXPECT_EQ(kSHLO, out_.tag()); +} + +TEST_P(CryptoServerTestOldVersion, XlctNotRequired) { + CryptoHandshakeMessage msg = + crypto_test_utils::CreateCHLO({{"PDMD", "X509"}, + {"AEAD", "AESG"}, + {"KEXS", "C255"}, + {"SCID", scid_hex_}, + {"#004b5453", srct_hex_}, + {"PUBS", pub_hex_}, + {"NONC", nonce_hex_}, + {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + + // If replay protection isn't disabled, then + // QuicCryptoServerConfig::EvaluateClientHello will leave info.unique as false + // and cause ProcessClientHello to exit early (and generate a REJ message). + config_.set_replay_protection(false); + + ShouldSucceed(msg); + EXPECT_EQ(kSHLO, out_.tag()); +} + +#endif // 0 + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_utils.cc b/quiche/quic/core/crypto/crypto_utils.cc new file mode 100644 index 0000000..d40c734 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_utils.cc
@@ -0,0 +1,790 @@ +// 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 "quiche/quic/core/crypto/crypto_utils.h" + +#include <memory> +#include <string> +#include <utility> + +#include "absl/base/macros.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/sha.h" +#include "quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h" +#include "quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h" +#include "quiche/quic/core/crypto/aes_128_gcm_decrypter.h" +#include "quiche/quic/core/crypto/aes_128_gcm_encrypter.h" +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/null_decrypter.h" +#include "quiche/quic/core/crypto/null_encrypter.h" +#include "quiche/quic/core/crypto/quic_decrypter.h" +#include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/crypto/quic_hkdf.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_constants.h" +#include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +namespace { + +// Implements the HKDF-Expand-Label function as defined in section 7.1 of RFC +// 8446. The HKDF-Expand-Label function takes 4 explicit arguments (Secret, +// Label, Context, and Length), as well as implicit PRF which is the hash +// function negotiated by TLS. Its use in QUIC (as needed by the QUIC stack, +// instead of as used internally by the TLS stack) is only for deriving initial +// secrets for obfuscation, for calculating packet protection keys and IVs from +// the corresponding packet protection secret and key update in the same quic +// session. None of these uses need a Context (a zero-length context is +// provided), so this argument is omitted here. +// +// The implicit PRF is explicitly passed into HkdfExpandLabel as |prf|; the +// Secret, Label, and Length are passed in as |secret|, |label|, and +// |out_len|, respectively. The resulting expanded secret is returned. +std::vector<uint8_t> HkdfExpandLabel(const EVP_MD* prf, + const std::vector<uint8_t>& secret, + const std::string& label, size_t out_len) { + bssl::ScopedCBB quic_hkdf_label; + CBB inner_label; + const char label_prefix[] = "tls13 "; + // 20 = size(u16) + size(u8) + len("tls13 ") + + // max_len("client in", "server in", "quicv2 key", ... ) + + // size(u8); + static const size_t max_quic_hkdf_label_length = 20; + if (!CBB_init(quic_hkdf_label.get(), max_quic_hkdf_label_length) || + !CBB_add_u16(quic_hkdf_label.get(), out_len) || + !CBB_add_u8_length_prefixed(quic_hkdf_label.get(), &inner_label) || + !CBB_add_bytes(&inner_label, + reinterpret_cast<const uint8_t*>(label_prefix), + ABSL_ARRAYSIZE(label_prefix) - 1) || + !CBB_add_bytes(&inner_label, + reinterpret_cast<const uint8_t*>(label.data()), + label.size()) || + // Zero length |Context|. + !CBB_add_u8(quic_hkdf_label.get(), 0) || + !CBB_flush(quic_hkdf_label.get())) { + QUIC_LOG(ERROR) << "Building HKDF label failed"; + return std::vector<uint8_t>(); + } + std::vector<uint8_t> out; + out.resize(out_len); + if (!HKDF_expand(out.data(), out_len, prf, secret.data(), secret.size(), + CBB_data(quic_hkdf_label.get()), + CBB_len(quic_hkdf_label.get()))) { + QUIC_LOG(ERROR) << "Running HKDF-Expand-Label failed"; + return std::vector<uint8_t>(); + } + return out; +} + +} // namespace + +const std::string getLabelForVersion(const ParsedQuicVersion& version, + const absl::string_view& predicate) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync with HKDF labels"); + if (version == ParsedQuicVersion::V2Draft01()) { + return absl::StrCat("quicv2 ", predicate); + } else { + return absl::StrCat("quic ", predicate); + } +} + +void CryptoUtils::InitializeCrypterSecrets( + const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, QuicCrypter* crypter) { + SetKeyAndIV(prf, pp_secret, version, crypter); + std::vector<uint8_t> header_protection_key = GenerateHeaderProtectionKey( + prf, pp_secret, version, crypter->GetKeySize()); + crypter->SetHeaderProtectionKey( + absl::string_view(reinterpret_cast<char*>(header_protection_key.data()), + header_protection_key.size())); +} + +void CryptoUtils::SetKeyAndIV(const EVP_MD* prf, + const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, + QuicCrypter* crypter) { + std::vector<uint8_t> key = + HkdfExpandLabel(prf, pp_secret, getLabelForVersion(version, "key"), + crypter->GetKeySize()); + std::vector<uint8_t> iv = HkdfExpandLabel( + prf, pp_secret, getLabelForVersion(version, "iv"), crypter->GetIVSize()); + crypter->SetKey( + absl::string_view(reinterpret_cast<char*>(key.data()), key.size())); + crypter->SetIV( + absl::string_view(reinterpret_cast<char*>(iv.data()), iv.size())); +} + +std::vector<uint8_t> CryptoUtils::GenerateHeaderProtectionKey( + const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, size_t out_len) { + return HkdfExpandLabel(prf, pp_secret, getLabelForVersion(version, "hp"), + out_len); +} + +std::vector<uint8_t> CryptoUtils::GenerateNextKeyPhaseSecret( + const EVP_MD* prf, const ParsedQuicVersion& version, + const std::vector<uint8_t>& current_secret) { + return HkdfExpandLabel(prf, current_secret, getLabelForVersion(version, "ku"), + current_secret.size()); +} + +namespace { + +// Salt from https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 +const uint8_t kDraft29InitialSalt[] = {0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, + 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, + 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}; +const uint8_t kRFCv1InitialSalt[] = {0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, + 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, + 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}; +const uint8_t kV2Draft01InitialSalt[] = { + 0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, + 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}; + +// Salts used by deployed versions of QUIC. When introducing a new version, +// generate a new salt by running `openssl rand -hex 20`. + +// Salt to use for initial obfuscators in version Q050. +const uint8_t kQ050Salt[] = {0x50, 0x45, 0x74, 0xef, 0xd0, 0x66, 0xfe, + 0x2f, 0x9d, 0x94, 0x5c, 0xfc, 0xdb, 0xd3, + 0xa7, 0xf0, 0xd3, 0xb5, 0x6b, 0x45}; +// Salt to use for initial obfuscators in +// ParsedQuicVersion::ReservedForNegotiation(). +const uint8_t kReservedForNegotiationSalt[] = { + 0xf9, 0x64, 0xbf, 0x45, 0x3a, 0x1f, 0x1b, 0x80, 0xa5, 0xf8, + 0x82, 0x03, 0x77, 0xd4, 0xaf, 0xca, 0x58, 0x0e, 0xe7, 0x43}; + +const uint8_t* InitialSaltForVersion(const ParsedQuicVersion& version, + size_t* out_len) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync with initial encryption salts"); + if (version == ParsedQuicVersion::V2Draft01()) { + *out_len = ABSL_ARRAYSIZE(kV2Draft01InitialSalt); + return kV2Draft01InitialSalt; + } else if (version == ParsedQuicVersion::RFCv1()) { + *out_len = ABSL_ARRAYSIZE(kRFCv1InitialSalt); + return kRFCv1InitialSalt; + } else if (version == ParsedQuicVersion::Draft29()) { + *out_len = ABSL_ARRAYSIZE(kDraft29InitialSalt); + return kDraft29InitialSalt; + } else if (version == ParsedQuicVersion::Q050()) { + *out_len = ABSL_ARRAYSIZE(kQ050Salt); + return kQ050Salt; + } else if (version == ParsedQuicVersion::ReservedForNegotiation()) { + *out_len = ABSL_ARRAYSIZE(kReservedForNegotiationSalt); + return kReservedForNegotiationSalt; + } + QUIC_BUG(quic_bug_10699_1) + << "No initial obfuscation salt for version " << version; + *out_len = ABSL_ARRAYSIZE(kReservedForNegotiationSalt); + return kReservedForNegotiationSalt; +} + +const char kPreSharedKeyLabel[] = "QUIC PSK"; + +// Retry Integrity Protection Keys and Nonces. +// https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.8 +// When introducing a new Google version, generate a new key by running +// `openssl rand -hex 16`. +const uint8_t kDraft29RetryIntegrityKey[] = {0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, + 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, + 0x6c, 0xb9, 0x6b, 0xe1}; +const uint8_t kDraft29RetryIntegrityNonce[] = { + 0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c}; +const uint8_t kRFCv1RetryIntegrityKey[] = {0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, + 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, + 0xe3, 0x68, 0xc8, 0x4e}; +const uint8_t kRFCv1RetryIntegrityNonce[] = { + 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb}; +const uint8_t kV2Draft01RetryIntegrityKey[] = { + 0xba, 0x85, 0x8d, 0xc7, 0xb4, 0x3d, 0xe5, 0xdb, + 0xf8, 0x76, 0x17, 0xff, 0x4a, 0xb2, 0x53, 0xdb}; +const uint8_t kV2Draft01RetryIntegrityNonce[] = { + 0x14, 0x1b, 0x99, 0xc2, 0x39, 0xb0, 0x3e, 0x78, 0x5d, 0x6a, 0x2e, 0x9f}; +// Retry integrity key used by ParsedQuicVersion::ReservedForNegotiation(). +const uint8_t kReservedForNegotiationRetryIntegrityKey[] = { + 0xf2, 0xcd, 0x8f, 0xe0, 0x36, 0xd0, 0x25, 0x35, + 0x03, 0xe6, 0x7c, 0x7b, 0xd2, 0x44, 0xca, 0xd9}; +// When introducing a new Google version, generate a new nonce by running +// `openssl rand -hex 12`. +// Retry integrity nonce used by ParsedQuicVersion::ReservedForNegotiation(). +const uint8_t kReservedForNegotiationRetryIntegrityNonce[] = { + 0x35, 0x9f, 0x16, 0xd1, 0xed, 0x80, 0x90, 0x8e, 0xec, 0x85, 0xc4, 0xd6}; + +bool RetryIntegrityKeysForVersion(const ParsedQuicVersion& version, + absl::string_view* key, + absl::string_view* nonce) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync with retry integrity keys"); + if (!version.UsesTls()) { + QUIC_BUG(quic_bug_10699_2) + << "Attempted to get retry integrity keys for invalid version " + << version; + return false; + } else if (version == ParsedQuicVersion::V2Draft01()) { + *key = absl::string_view( + reinterpret_cast<const char*>(kV2Draft01RetryIntegrityKey), + ABSL_ARRAYSIZE(kV2Draft01RetryIntegrityKey)); + *nonce = absl::string_view( + reinterpret_cast<const char*>(kV2Draft01RetryIntegrityNonce), + ABSL_ARRAYSIZE(kV2Draft01RetryIntegrityNonce)); + return true; + } else if (version == ParsedQuicVersion::RFCv1()) { + *key = absl::string_view( + reinterpret_cast<const char*>(kRFCv1RetryIntegrityKey), + ABSL_ARRAYSIZE(kRFCv1RetryIntegrityKey)); + *nonce = absl::string_view( + reinterpret_cast<const char*>(kRFCv1RetryIntegrityNonce), + ABSL_ARRAYSIZE(kRFCv1RetryIntegrityNonce)); + return true; + } else if (version == ParsedQuicVersion::Draft29()) { + *key = absl::string_view( + reinterpret_cast<const char*>(kDraft29RetryIntegrityKey), + ABSL_ARRAYSIZE(kDraft29RetryIntegrityKey)); + *nonce = absl::string_view( + reinterpret_cast<const char*>(kDraft29RetryIntegrityNonce), + ABSL_ARRAYSIZE(kDraft29RetryIntegrityNonce)); + return true; + } else if (version == ParsedQuicVersion::ReservedForNegotiation()) { + *key = absl::string_view( + reinterpret_cast<const char*>(kReservedForNegotiationRetryIntegrityKey), + ABSL_ARRAYSIZE(kReservedForNegotiationRetryIntegrityKey)); + *nonce = absl::string_view( + reinterpret_cast<const char*>( + kReservedForNegotiationRetryIntegrityNonce), + ABSL_ARRAYSIZE(kReservedForNegotiationRetryIntegrityNonce)); + return true; + } + QUIC_BUG(quic_bug_10699_3) + << "Attempted to get retry integrity keys for version " << version; + return false; +} + +} // namespace + +// static +void CryptoUtils::CreateInitialObfuscators(Perspective perspective, + ParsedQuicVersion version, + QuicConnectionId connection_id, + CrypterPair* crypters) { + QUIC_DLOG(INFO) << "Creating " + << (perspective == Perspective::IS_CLIENT ? "client" + : "server") + << " crypters for version " << version << " with CID " + << connection_id; + if (!version.UsesInitialObfuscators()) { + crypters->encrypter = std::make_unique<NullEncrypter>(perspective); + crypters->decrypter = std::make_unique<NullDecrypter>(perspective); + return; + } + QUIC_BUG_IF(quic_bug_12871_1, !QuicUtils::IsConnectionIdValidForVersion( + connection_id, version.transport_version)) + << "CreateTlsInitialCrypters: attempted to use connection ID " + << connection_id << " which is invalid with version " << version; + const EVP_MD* hash = EVP_sha256(); + + size_t salt_len; + const uint8_t* salt = InitialSaltForVersion(version, &salt_len); + std::vector<uint8_t> handshake_secret; + handshake_secret.resize(EVP_MAX_MD_SIZE); + size_t handshake_secret_len; + const bool hkdf_extract_success = + HKDF_extract(handshake_secret.data(), &handshake_secret_len, hash, + reinterpret_cast<const uint8_t*>(connection_id.data()), + connection_id.length(), salt, salt_len); + QUIC_BUG_IF(quic_bug_12871_2, !hkdf_extract_success) + << "HKDF_extract failed when creating initial crypters"; + handshake_secret.resize(handshake_secret_len); + + const std::string client_label = "client in"; + const std::string server_label = "server in"; + std::string encryption_label, decryption_label; + if (perspective == Perspective::IS_CLIENT) { + encryption_label = client_label; + decryption_label = server_label; + } else { + encryption_label = server_label; + decryption_label = client_label; + } + std::vector<uint8_t> encryption_secret = HkdfExpandLabel( + hash, handshake_secret, encryption_label, EVP_MD_size(hash)); + crypters->encrypter = std::make_unique<Aes128GcmEncrypter>(); + InitializeCrypterSecrets(hash, encryption_secret, version, + crypters->encrypter.get()); + + std::vector<uint8_t> decryption_secret = HkdfExpandLabel( + hash, handshake_secret, decryption_label, EVP_MD_size(hash)); + crypters->decrypter = std::make_unique<Aes128GcmDecrypter>(); + InitializeCrypterSecrets(hash, decryption_secret, version, + crypters->decrypter.get()); +} + +// static +bool CryptoUtils::ValidateRetryIntegrityTag( + ParsedQuicVersion version, QuicConnectionId original_connection_id, + absl::string_view retry_without_tag, absl::string_view integrity_tag) { + unsigned char computed_integrity_tag[kRetryIntegrityTagLength]; + if (integrity_tag.length() != ABSL_ARRAYSIZE(computed_integrity_tag)) { + QUIC_BUG(quic_bug_10699_4) + << "Invalid retry integrity tag length " << integrity_tag.length(); + return false; + } + char retry_pseudo_packet[kMaxIncomingPacketSize + 256]; + QuicDataWriter writer(ABSL_ARRAYSIZE(retry_pseudo_packet), + retry_pseudo_packet); + if (!writer.WriteLengthPrefixedConnectionId(original_connection_id)) { + QUIC_BUG(quic_bug_10699_5) + << "Failed to write original connection ID in retry pseudo packet"; + return false; + } + if (!writer.WriteStringPiece(retry_without_tag)) { + QUIC_BUG(quic_bug_10699_6) + << "Failed to write retry without tag in retry pseudo packet"; + return false; + } + absl::string_view key; + absl::string_view nonce; + if (!RetryIntegrityKeysForVersion(version, &key, &nonce)) { + // RetryIntegrityKeysForVersion already logs failures. + return false; + } + Aes128GcmEncrypter crypter; + crypter.SetKey(key); + absl::string_view associated_data(writer.data(), writer.length()); + absl::string_view plaintext; // Plaintext is empty. + if (!crypter.Encrypt(nonce, associated_data, plaintext, + computed_integrity_tag)) { + QUIC_BUG(quic_bug_10699_7) << "Failed to compute retry integrity tag"; + return false; + } + if (CRYPTO_memcmp(computed_integrity_tag, integrity_tag.data(), + ABSL_ARRAYSIZE(computed_integrity_tag)) != 0) { + QUIC_DLOG(ERROR) << "Failed to validate retry integrity tag"; + return false; + } + return true; +} + +// static +void CryptoUtils::GenerateNonce(QuicWallTime now, QuicRandom* random_generator, + absl::string_view orbit, std::string* nonce) { + // a 4-byte timestamp + 28 random bytes. + nonce->reserve(kNonceSize); + nonce->resize(kNonceSize); + + uint32_t gmt_unix_time = static_cast<uint32_t>(now.ToUNIXSeconds()); + // The time in the nonce must be encoded in big-endian because the + // strike-register depends on the nonces being ordered by time. + (*nonce)[0] = static_cast<char>(gmt_unix_time >> 24); + (*nonce)[1] = static_cast<char>(gmt_unix_time >> 16); + (*nonce)[2] = static_cast<char>(gmt_unix_time >> 8); + (*nonce)[3] = static_cast<char>(gmt_unix_time); + size_t bytes_written = 4; + + if (orbit.size() == 8) { + memcpy(&(*nonce)[bytes_written], orbit.data(), orbit.size()); + bytes_written += orbit.size(); + } + + random_generator->RandBytes(&(*nonce)[bytes_written], + kNonceSize - bytes_written); +} + +// static +bool CryptoUtils::DeriveKeys( + const ParsedQuicVersion& version, absl::string_view premaster_secret, + QuicTag aead, absl::string_view client_nonce, + absl::string_view server_nonce, absl::string_view pre_shared_key, + const std::string& hkdf_input, Perspective perspective, + Diversification diversification, CrypterPair* crypters, + std::string* subkey_secret) { + // If the connection is using PSK, concatenate it with the pre-master secret. + std::unique_ptr<char[]> psk_premaster_secret; + if (!pre_shared_key.empty()) { + const absl::string_view label(kPreSharedKeyLabel); + const size_t psk_premaster_secret_size = label.size() + 1 + + pre_shared_key.size() + 8 + + premaster_secret.size() + 8; + + psk_premaster_secret = std::make_unique<char[]>(psk_premaster_secret_size); + QuicDataWriter writer(psk_premaster_secret_size, psk_premaster_secret.get(), + quiche::HOST_BYTE_ORDER); + + if (!writer.WriteStringPiece(label) || !writer.WriteUInt8(0) || + !writer.WriteStringPiece(pre_shared_key) || + !writer.WriteUInt64(pre_shared_key.size()) || + !writer.WriteStringPiece(premaster_secret) || + !writer.WriteUInt64(premaster_secret.size()) || + writer.remaining() != 0) { + return false; + } + + premaster_secret = absl::string_view(psk_premaster_secret.get(), + psk_premaster_secret_size); + } + + crypters->encrypter = QuicEncrypter::Create(version, aead); + crypters->decrypter = QuicDecrypter::Create(version, aead); + + size_t key_bytes = crypters->encrypter->GetKeySize(); + size_t nonce_prefix_bytes = crypters->encrypter->GetNoncePrefixSize(); + if (version.UsesInitialObfuscators()) { + nonce_prefix_bytes = crypters->encrypter->GetIVSize(); + } + size_t subkey_secret_bytes = + subkey_secret == nullptr ? 0 : premaster_secret.length(); + + absl::string_view nonce = client_nonce; + std::string nonce_storage; + if (!server_nonce.empty()) { + nonce_storage = std::string(client_nonce) + std::string(server_nonce); + nonce = nonce_storage; + } + + QuicHKDF hkdf(premaster_secret, nonce, hkdf_input, key_bytes, + nonce_prefix_bytes, subkey_secret_bytes); + + // Key derivation depends on the key diversification method being employed. + // both the client and the server support never doing key diversification. + // The server also supports immediate diversification, and the client + // supports pending diversification. + switch (diversification.mode()) { + case Diversification::NEVER: { + if (perspective == Perspective::IS_SERVER) { + if (!crypters->encrypter->SetKey(hkdf.server_write_key()) || + !crypters->encrypter->SetNoncePrefixOrIV(version, + hkdf.server_write_iv()) || + !crypters->encrypter->SetHeaderProtectionKey( + hkdf.server_hp_key()) || + !crypters->decrypter->SetKey(hkdf.client_write_key()) || + !crypters->decrypter->SetNoncePrefixOrIV(version, + hkdf.client_write_iv()) || + !crypters->decrypter->SetHeaderProtectionKey( + hkdf.client_hp_key())) { + return false; + } + } else { + if (!crypters->encrypter->SetKey(hkdf.client_write_key()) || + !crypters->encrypter->SetNoncePrefixOrIV(version, + hkdf.client_write_iv()) || + !crypters->encrypter->SetHeaderProtectionKey( + hkdf.client_hp_key()) || + !crypters->decrypter->SetKey(hkdf.server_write_key()) || + !crypters->decrypter->SetNoncePrefixOrIV(version, + hkdf.server_write_iv()) || + !crypters->decrypter->SetHeaderProtectionKey( + hkdf.server_hp_key())) { + return false; + } + } + break; + } + case Diversification::PENDING: { + if (perspective == Perspective::IS_SERVER) { + QUIC_BUG(quic_bug_10699_8) + << "Pending diversification is only for clients."; + return false; + } + + if (!crypters->encrypter->SetKey(hkdf.client_write_key()) || + !crypters->encrypter->SetNoncePrefixOrIV(version, + hkdf.client_write_iv()) || + !crypters->encrypter->SetHeaderProtectionKey(hkdf.client_hp_key()) || + !crypters->decrypter->SetPreliminaryKey(hkdf.server_write_key()) || + !crypters->decrypter->SetNoncePrefixOrIV(version, + hkdf.server_write_iv()) || + !crypters->decrypter->SetHeaderProtectionKey(hkdf.server_hp_key())) { + return false; + } + break; + } + case Diversification::NOW: { + if (perspective == Perspective::IS_CLIENT) { + QUIC_BUG(quic_bug_10699_9) + << "Immediate diversification is only for servers."; + return false; + } + + std::string key, nonce_prefix; + QuicDecrypter::DiversifyPreliminaryKey( + hkdf.server_write_key(), hkdf.server_write_iv(), + *diversification.nonce(), key_bytes, nonce_prefix_bytes, &key, + &nonce_prefix); + if (!crypters->decrypter->SetKey(hkdf.client_write_key()) || + !crypters->decrypter->SetNoncePrefixOrIV(version, + hkdf.client_write_iv()) || + !crypters->decrypter->SetHeaderProtectionKey(hkdf.client_hp_key()) || + !crypters->encrypter->SetKey(key) || + !crypters->encrypter->SetNoncePrefixOrIV(version, nonce_prefix) || + !crypters->encrypter->SetHeaderProtectionKey(hkdf.server_hp_key())) { + return false; + } + break; + } + default: + QUICHE_DCHECK(false); + } + + if (subkey_secret != nullptr) { + *subkey_secret = std::string(hkdf.subkey_secret()); + } + + return true; +} + +// static +uint64_t CryptoUtils::ComputeLeafCertHash(absl::string_view cert) { + return QuicUtils::FNV1a_64_Hash(cert); +} + +QuicErrorCode CryptoUtils::ValidateServerHello( + const CryptoHandshakeMessage& server_hello, + const ParsedQuicVersionVector& negotiated_versions, + std::string* error_details) { + QUICHE_DCHECK(error_details != nullptr); + + if (server_hello.tag() != kSHLO) { + *error_details = "Bad tag"; + return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; + } + + QuicVersionLabelVector supported_version_labels; + if (server_hello.GetVersionLabelList(kVER, &supported_version_labels) != + QUIC_NO_ERROR) { + *error_details = "server hello missing version list"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + return ValidateServerHelloVersions(supported_version_labels, + negotiated_versions, error_details); +} + +QuicErrorCode CryptoUtils::ValidateServerHelloVersions( + const QuicVersionLabelVector& server_versions, + const ParsedQuicVersionVector& negotiated_versions, + std::string* error_details) { + if (!negotiated_versions.empty()) { + bool mismatch = server_versions.size() != negotiated_versions.size(); + for (size_t i = 0; i < server_versions.size() && !mismatch; ++i) { + mismatch = + server_versions[i] != CreateQuicVersionLabel(negotiated_versions[i]); + } + // The server sent a list of supported versions, and the connection + // reports that there was a version negotiation during the handshake. + // Ensure that these two lists are identical. + if (mismatch) { + *error_details = absl::StrCat( + "Downgrade attack detected: ServerVersions(", server_versions.size(), + ")[", QuicVersionLabelVectorToString(server_versions, ",", 30), + "] NegotiatedVersions(", negotiated_versions.size(), ")[", + ParsedQuicVersionVectorToString(negotiated_versions, ",", 30), "]"); + return QUIC_VERSION_NEGOTIATION_MISMATCH; + } + } + return QUIC_NO_ERROR; +} + +QuicErrorCode CryptoUtils::ValidateClientHello( + const CryptoHandshakeMessage& client_hello, ParsedQuicVersion version, + const ParsedQuicVersionVector& supported_versions, + std::string* error_details) { + if (client_hello.tag() != kCHLO) { + *error_details = "Bad tag"; + return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; + } + + // 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. + QuicVersionLabel client_version_label; + if (client_hello.GetVersionLabel(kVER, &client_version_label) != + QUIC_NO_ERROR) { + *error_details = "client hello missing version list"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + return ValidateClientHelloVersion(client_version_label, version, + supported_versions, error_details); +} + +QuicErrorCode CryptoUtils::ValidateClientHelloVersion( + QuicVersionLabel client_version, ParsedQuicVersion connection_version, + const ParsedQuicVersionVector& supported_versions, + std::string* error_details) { + if (client_version != CreateQuicVersionLabel(connection_version)) { + // Check to see if |client_version| is actually on the supported versions + // list. If not, the server doesn't support that version and it's not a + // downgrade attack. + for (size_t i = 0; i < supported_versions.size(); ++i) { + if (client_version == CreateQuicVersionLabel(supported_versions[i])) { + *error_details = absl::StrCat( + "Downgrade attack detected: ClientVersion[", + QuicVersionLabelToString(client_version), "] ConnectionVersion[", + ParsedQuicVersionToString(connection_version), + "] SupportedVersions(", supported_versions.size(), ")[", + ParsedQuicVersionVectorToString(supported_versions, ",", 30), "]"); + return QUIC_VERSION_NEGOTIATION_MISMATCH; + } + } + } + return QUIC_NO_ERROR; +} + +// static +bool CryptoUtils::ValidateChosenVersion( + const QuicVersionLabel& version_information_chosen_version, + const ParsedQuicVersion& session_version, std::string* error_details) { + if (version_information_chosen_version != + CreateQuicVersionLabel(session_version)) { + *error_details = absl::StrCat( + "Detected version mismatch: version_information contained ", + QuicVersionLabelToString(version_information_chosen_version), + " instead of ", ParsedQuicVersionToString(session_version)); + return false; + } + return true; +} + +// static +bool CryptoUtils::ValidateServerVersions( + const QuicVersionLabelVector& version_information_other_versions, + const ParsedQuicVersion& session_version, + const ParsedQuicVersionVector& client_original_supported_versions, + std::string* error_details) { + if (client_original_supported_versions.empty()) { + // We did not receive a version negotiation packet. + return true; + } + // Parse the server's other versions. + ParsedQuicVersionVector parsed_other_versions = + ParseQuicVersionLabelVector(version_information_other_versions); + // Find the first version that we originally supported that is listed in the + // server's other versions. + ParsedQuicVersion expected_version = ParsedQuicVersion::Unsupported(); + for (const ParsedQuicVersion& client_version : + client_original_supported_versions) { + if (std::find(parsed_other_versions.begin(), parsed_other_versions.end(), + client_version) != parsed_other_versions.end()) { + expected_version = client_version; + break; + } + } + if (expected_version != session_version) { + *error_details = absl::StrCat( + "Downgrade attack detected: used ", + ParsedQuicVersionToString(session_version), " but ServerVersions(", + version_information_other_versions.size(), ")[", + QuicVersionLabelVectorToString(version_information_other_versions, ",", + 30), + "] ClientOriginalVersions(", client_original_supported_versions.size(), + ")[", + ParsedQuicVersionVectorToString(client_original_supported_versions, ",", + 30), + "]"); + return false; + } + return true; +} + +#define RETURN_STRING_LITERAL(x) \ + case x: \ + return #x + +// Returns the name of the HandshakeFailureReason as a char* +// static +const char* CryptoUtils::HandshakeFailureReasonToString( + HandshakeFailureReason reason) { + switch (reason) { + RETURN_STRING_LITERAL(HANDSHAKE_OK); + RETURN_STRING_LITERAL(CLIENT_NONCE_UNKNOWN_FAILURE); + RETURN_STRING_LITERAL(CLIENT_NONCE_INVALID_FAILURE); + RETURN_STRING_LITERAL(CLIENT_NONCE_NOT_UNIQUE_FAILURE); + RETURN_STRING_LITERAL(CLIENT_NONCE_INVALID_ORBIT_FAILURE); + RETURN_STRING_LITERAL(CLIENT_NONCE_INVALID_TIME_FAILURE); + RETURN_STRING_LITERAL(CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT); + RETURN_STRING_LITERAL(CLIENT_NONCE_STRIKE_REGISTER_FAILURE); + + RETURN_STRING_LITERAL(SERVER_NONCE_DECRYPTION_FAILURE); + RETURN_STRING_LITERAL(SERVER_NONCE_INVALID_FAILURE); + RETURN_STRING_LITERAL(SERVER_NONCE_NOT_UNIQUE_FAILURE); + RETURN_STRING_LITERAL(SERVER_NONCE_INVALID_TIME_FAILURE); + RETURN_STRING_LITERAL(SERVER_NONCE_REQUIRED_FAILURE); + + RETURN_STRING_LITERAL(SERVER_CONFIG_INCHOATE_HELLO_FAILURE); + RETURN_STRING_LITERAL(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE); + + RETURN_STRING_LITERAL(SOURCE_ADDRESS_TOKEN_INVALID_FAILURE); + RETURN_STRING_LITERAL(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE); + RETURN_STRING_LITERAL(SOURCE_ADDRESS_TOKEN_PARSE_FAILURE); + RETURN_STRING_LITERAL(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE); + RETURN_STRING_LITERAL(SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE); + RETURN_STRING_LITERAL(SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE); + + RETURN_STRING_LITERAL(INVALID_EXPECTED_LEAF_CERTIFICATE); + RETURN_STRING_LITERAL(MAX_FAILURE_REASON); + } + // Return a default value so that we return this when |reason| doesn't match + // any HandshakeFailureReason.. This can happen when the message by the peer + // (attacker) has invalid reason. + return "INVALID_HANDSHAKE_FAILURE_REASON"; +} + +#undef RETURN_STRING_LITERAL // undef for jumbo builds + +// static +std::string CryptoUtils::EarlyDataReasonToString( + ssl_early_data_reason_t reason) { + const char* reason_string = SSL_early_data_reason_string(reason); + if (reason_string != nullptr) { + return std::string("ssl_early_data_") + reason_string; + } + QUIC_BUG_IF(quic_bug_12871_3, + reason < 0 || reason > ssl_early_data_reason_max_value) + << "Unknown ssl_early_data_reason_t " << reason; + return "unknown ssl_early_data_reason_t"; +} + +// static +std::string CryptoUtils::HashHandshakeMessage( + const CryptoHandshakeMessage& message, Perspective /*perspective*/) { + std::string output; + const QuicData& serialized = message.GetSerialized(); + uint8_t digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast<const uint8_t*>(serialized.data()), + serialized.length(), digest); + output.assign(reinterpret_cast<const char*>(digest), sizeof(digest)); + return output; +} + +// static +bool CryptoUtils::GetSSLCapabilities(const SSL* ssl, + bssl::UniquePtr<uint8_t>* capabilities, + size_t* capabilities_len) { + uint8_t* buffer; + CBB cbb; + + if (!CBB_init(&cbb, 128) || !SSL_serialize_capabilities(ssl, &cbb) || + !CBB_finish(&cbb, &buffer, capabilities_len)) { + return false; + } + + *capabilities = bssl::UniquePtr<uint8_t>(buffer); + return true; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/crypto_utils.h b/quiche/quic/core/crypto/crypto_utils.h new file mode 100644 index 0000000..1f618d9 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_utils.h
@@ -0,0 +1,255 @@ +// 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. + +// Some helpers for quic crypto + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CRYPTO_UTILS_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CRYPTO_UTILS_H_ + +#include <cstddef> +#include <cstdint> +#include <string> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_handshake_message.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/quic_crypter.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QuicRandom; + +class QUIC_EXPORT_PRIVATE CryptoUtils { + public: + CryptoUtils() = delete; + + // Diversification is a utility class that's used to act like a union type. + // Values can be created by calling the functions like |NoDiversification|, + // below. + class QUIC_EXPORT_PRIVATE Diversification { + public: + enum Mode { + NEVER, // Key diversification will never be used. Forward secure + // crypters will always use this mode. + + PENDING, // Key diversification will happen when a nonce is later + // received. This should only be used by clients initial + // decrypters which are waiting on the divesification nonce + // from the server. + + NOW, // Key diversification will happen immediate based on the nonce. + // This should only be used by servers initial encrypters. + }; + + Diversification(const Diversification& diversification) = default; + + static Diversification Never() { return Diversification(NEVER, nullptr); } + static Diversification Pending() { + return Diversification(PENDING, nullptr); + } + static Diversification Now(DiversificationNonce* nonce) { + return Diversification(NOW, nonce); + } + + Mode mode() const { return mode_; } + DiversificationNonce* nonce() const { + QUICHE_DCHECK_EQ(mode_, NOW); + return nonce_; + } + + private: + Diversification(Mode mode, DiversificationNonce* nonce) + : mode_(mode), nonce_(nonce) {} + + Mode mode_; + DiversificationNonce* nonce_; + }; + + // InitializeCrypterSecrets derives the key and IV and header protection key + // from the given packet protection secret |pp_secret| and sets those fields + // on the given QuicCrypter |*crypter|. + // This follows the derivation described in section 7.3 of RFC 8446, except + // with the label prefix in HKDF-Expand-Label changed from "tls13 " to "quic " + // as described in draft-ietf-quic-tls-14, section 5.1, or "quicv2 " as + // described in draft-ietf-quic-v2-01. + static void InitializeCrypterSecrets(const EVP_MD* prf, + const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, + QuicCrypter* crypter); + + // Derives the key and IV from the packet protection secret and sets those + // fields on the given QuicCrypter |*crypter|, but does not set the header + // protection key. GenerateHeaderProtectionKey/SetHeaderProtectionKey must be + // called before using |crypter|. + static void SetKeyAndIV(const EVP_MD* prf, + const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, + QuicCrypter* crypter); + + // Derives the header protection key from the packet protection secret. + static std::vector<uint8_t> GenerateHeaderProtectionKey( + const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, size_t out_len); + + // Given a secret for key phase n, return the secret for phase n+1. + static std::vector<uint8_t> GenerateNextKeyPhaseSecret( + const EVP_MD* prf, const ParsedQuicVersion& version, + const std::vector<uint8_t>& current_secret); + + // IETF QUIC encrypts ENCRYPTION_INITIAL messages with a version-specific key + // (to prevent network observers that are not aware of that QUIC version from + // making decisions based on the TLS handshake). This packet protection secret + // is derived from the connection ID in the client's Initial packet. + // + // This function takes that |connection_id| and creates the encrypter and + // decrypter (put in |*crypters|) to use for this packet protection, as well + // as setting the key and IV on those crypters. For older versions of QUIC + // that do not use the new IETF style ENCRYPTION_INITIAL obfuscators, this + // function puts a NullEncrypter and NullDecrypter in |*crypters|. + static void CreateInitialObfuscators(Perspective perspective, + ParsedQuicVersion version, + QuicConnectionId connection_id, + CrypterPair* crypters); + + // IETF QUIC Retry packets carry a retry integrity tag to detect packet + // corruption and make it harder for an attacker to spoof. This function + // checks whether a given retry packet is valid. + static bool ValidateRetryIntegrityTag(ParsedQuicVersion version, + QuicConnectionId original_connection_id, + absl::string_view retry_without_tag, + absl::string_view integrity_tag); + + // Generates the connection nonce. The nonce is formed as: + // <4 bytes> current time + // <8 bytes> |orbit| (or random if |orbit| is empty) + // <20 bytes> random + static void GenerateNonce(QuicWallTime now, QuicRandom* random_generator, + absl::string_view orbit, std::string* nonce); + + // DeriveKeys populates |crypters->encrypter|, |crypters->decrypter|, and + // |subkey_secret| (optional -- may be null) given the contents of + // |premaster_secret|, |client_nonce|, |server_nonce| and |hkdf_input|. |aead| + // determines which cipher will be used. |perspective| controls whether the + // server's keys are assigned to |encrypter| or |decrypter|. |server_nonce| is + // optional and, if non-empty, is mixed into the key derivation. + // |subkey_secret| will have the same length as |premaster_secret|. + // + // If |pre_shared_key| is non-empty, it is incorporated into the key + // derivation parameters. If it is empty, the key derivation is unaltered. + // + // If the mode of |diversification| is NEVER, the the crypters will be + // configured to never perform key diversification. If the mode is + // NOW (which is only for servers, then the encrypter will be keyed via a + // two-step process that uses the nonce from |diversification|. + // If the mode is PENDING (which is only for servres), then the + // decrypter will only be keyed to a preliminary state: a call to + // |SetDiversificationNonce| with a diversification nonce will be needed to + // complete keying. + static bool DeriveKeys(const ParsedQuicVersion& version, + absl::string_view premaster_secret, QuicTag aead, + absl::string_view client_nonce, + absl::string_view server_nonce, + absl::string_view pre_shared_key, + const std::string& hkdf_input, Perspective perspective, + Diversification diversification, CrypterPair* crypters, + std::string* subkey_secret); + + // Computes the FNV-1a hash of the provided DER-encoded cert for use in the + // XLCT tag. + static uint64_t ComputeLeafCertHash(absl::string_view cert); + + // Validates that |server_hello| is actually an SHLO message and that it is + // not part of a downgrade attack. + // + // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error + // code and sets |error_details|. + static QuicErrorCode ValidateServerHello( + const CryptoHandshakeMessage& server_hello, + const ParsedQuicVersionVector& negotiated_versions, + std::string* error_details); + + // Validates that the |server_versions| received do not indicate that the + // ServerHello is part of a downgrade attack. |negotiated_versions| must + // contain the list of versions received in the server's version negotiation + // packet (or be empty if no such packet was received). + // + // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error + // code and sets |error_details|. + static QuicErrorCode ValidateServerHelloVersions( + const QuicVersionLabelVector& server_versions, + const ParsedQuicVersionVector& negotiated_versions, + std::string* error_details); + + // Validates that |client_hello| is actually a CHLO and that this is not part + // of a downgrade attack. + // This includes verifiying versions and detecting downgrade attacks. + // + // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error + // code and sets |error_details|. + static QuicErrorCode ValidateClientHello( + const CryptoHandshakeMessage& client_hello, ParsedQuicVersion version, + const ParsedQuicVersionVector& supported_versions, + std::string* error_details); + + // Validates that the |client_version| received does not indicate that a + // downgrade attack has occurred. |connection_version| is the version of the + // QuicConnection, and |supported_versions| is all versions that that + // QuicConnection supports. + // + // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error + // code and sets |error_details|. + static QuicErrorCode ValidateClientHelloVersion( + QuicVersionLabel client_version, ParsedQuicVersion connection_version, + const ParsedQuicVersionVector& supported_versions, + std::string* error_details); + + // Validates that the chosen version from the version_information matches the + // version from the session. Returns true if they match, otherwise returns + // false and fills in |error_details|. + static bool ValidateChosenVersion( + const QuicVersionLabel& version_information_chosen_version, + const ParsedQuicVersion& session_version, std::string* error_details); + + // Validates that there was no downgrade attack involving a version + // negotiation packet. This verifies that if the client was initially + // configured with |client_original_supported_versions| and it had received a + // version negotiation packet with |version_information_other_versions|, then + // it would have selected |session_version|. Returns true if they match (or if + // |client_original_supported_versions| is empty indicating no version + // negotiation packet was received), otherwise returns + // false and fills in |error_details|. + static bool ValidateServerVersions( + const QuicVersionLabelVector& version_information_other_versions, + const ParsedQuicVersion& session_version, + const ParsedQuicVersionVector& client_original_supported_versions, + std::string* error_details); + + // Returns the name of the HandshakeFailureReason as a char* + static const char* HandshakeFailureReasonToString( + HandshakeFailureReason reason); + + // Returns the name of an ssl_early_data_reason_t as a char* + static std::string EarlyDataReasonToString(ssl_early_data_reason_t reason); + + // Returns a hash of the serialized |message|. + static std::string HashHandshakeMessage(const CryptoHandshakeMessage& message, + Perspective perspective); + + // Wraps SSL_serialize_capabilities. Return nullptr if failed. + static bool GetSSLCapabilities(const SSL* ssl, + bssl::UniquePtr<uint8_t>* capabilities, + size_t* capabilities_len); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CRYPTO_UTILS_H_
diff --git a/quiche/quic/core/crypto/crypto_utils_test.cc b/quiche/quic/core/crypto/crypto_utils_test.cc new file mode 100644 index 0000000..6452367 --- /dev/null +++ b/quiche/quic/core/crypto/crypto_utils_test.cc
@@ -0,0 +1,262 @@ +// 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 "quiche/quic/core/crypto/crypto_utils.h" + +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace quic { +namespace test { +namespace { + +class CryptoUtilsTest : public QuicTest {}; + +TEST_F(CryptoUtilsTest, HandshakeFailureReasonToString) { + EXPECT_STREQ("HANDSHAKE_OK", + CryptoUtils::HandshakeFailureReasonToString(HANDSHAKE_OK)); + EXPECT_STREQ("CLIENT_NONCE_UNKNOWN_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_UNKNOWN_FAILURE)); + EXPECT_STREQ("CLIENT_NONCE_INVALID_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_INVALID_FAILURE)); + EXPECT_STREQ("CLIENT_NONCE_NOT_UNIQUE_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_NOT_UNIQUE_FAILURE)); + EXPECT_STREQ("CLIENT_NONCE_INVALID_ORBIT_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_INVALID_ORBIT_FAILURE)); + EXPECT_STREQ("CLIENT_NONCE_INVALID_TIME_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_INVALID_TIME_FAILURE)); + EXPECT_STREQ("CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT)); + EXPECT_STREQ("CLIENT_NONCE_STRIKE_REGISTER_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + CLIENT_NONCE_STRIKE_REGISTER_FAILURE)); + EXPECT_STREQ("SERVER_NONCE_DECRYPTION_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_NONCE_DECRYPTION_FAILURE)); + EXPECT_STREQ("SERVER_NONCE_INVALID_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_NONCE_INVALID_FAILURE)); + EXPECT_STREQ("SERVER_NONCE_NOT_UNIQUE_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_NONCE_NOT_UNIQUE_FAILURE)); + EXPECT_STREQ("SERVER_NONCE_INVALID_TIME_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_NONCE_INVALID_TIME_FAILURE)); + EXPECT_STREQ("SERVER_NONCE_REQUIRED_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_NONCE_REQUIRED_FAILURE)); + EXPECT_STREQ("SERVER_CONFIG_INCHOATE_HELLO_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_CONFIG_INCHOATE_HELLO_FAILURE)); + EXPECT_STREQ("SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE)); + EXPECT_STREQ("SOURCE_ADDRESS_TOKEN_INVALID_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SOURCE_ADDRESS_TOKEN_INVALID_FAILURE)); + EXPECT_STREQ("SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE)); + EXPECT_STREQ("SOURCE_ADDRESS_TOKEN_PARSE_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SOURCE_ADDRESS_TOKEN_PARSE_FAILURE)); + EXPECT_STREQ("SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE)); + EXPECT_STREQ("SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE)); + EXPECT_STREQ("SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE", + CryptoUtils::HandshakeFailureReasonToString( + SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE)); + EXPECT_STREQ("INVALID_EXPECTED_LEAF_CERTIFICATE", + CryptoUtils::HandshakeFailureReasonToString( + INVALID_EXPECTED_LEAF_CERTIFICATE)); + EXPECT_STREQ("MAX_FAILURE_REASON", + CryptoUtils::HandshakeFailureReasonToString(MAX_FAILURE_REASON)); + EXPECT_STREQ( + "INVALID_HANDSHAKE_FAILURE_REASON", + CryptoUtils::HandshakeFailureReasonToString( + static_cast<HandshakeFailureReason>(MAX_FAILURE_REASON + 1))); +} + +TEST_F(CryptoUtilsTest, AuthTagLengths) { + for (const auto& version : AllSupportedVersions()) { + for (QuicTag algo : {kAESG, kCC20}) { + SCOPED_TRACE(version); + std::unique_ptr<QuicEncrypter> encrypter( + QuicEncrypter::Create(version, algo)); + size_t auth_tag_size = 12; + if (version.UsesInitialObfuscators()) { + auth_tag_size = 16; + } + EXPECT_EQ(encrypter->GetCiphertextSize(0), auth_tag_size); + } + } +} + +TEST_F(CryptoUtilsTest, ValidateChosenVersion) { + for (const ParsedQuicVersion& v1 : AllSupportedVersions()) { + for (const ParsedQuicVersion& v2 : AllSupportedVersions()) { + std::string error_details; + bool success = CryptoUtils::ValidateChosenVersion( + CreateQuicVersionLabel(v1), v2, &error_details); + EXPECT_EQ(success, v1 == v2); + EXPECT_EQ(success, error_details.empty()); + } + } +} + +TEST_F(CryptoUtilsTest, ValidateServerVersionsNoVersionNegotiation) { + QuicVersionLabelVector version_information_other_versions; + ParsedQuicVersionVector client_original_supported_versions; + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + std::string error_details; + EXPECT_TRUE(CryptoUtils::ValidateServerVersions( + version_information_other_versions, version, + client_original_supported_versions, &error_details)); + EXPECT_TRUE(error_details.empty()); + } +} + +TEST_F(CryptoUtilsTest, ValidateServerVersionsWithVersionNegotiation) { + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + QuicVersionLabelVector version_information_other_versions{ + CreateQuicVersionLabel(version)}; + ParsedQuicVersionVector client_original_supported_versions{ + ParsedQuicVersion::ReservedForNegotiation(), version}; + std::string error_details; + EXPECT_TRUE(CryptoUtils::ValidateServerVersions( + version_information_other_versions, version, + client_original_supported_versions, &error_details)); + EXPECT_TRUE(error_details.empty()); + } +} + +TEST_F(CryptoUtilsTest, ValidateServerVersionsWithDowngrade) { + if (AllSupportedVersions().size() <= 1) { + // We are not vulnerable to downgrade if we only support one version. + return; + } + ParsedQuicVersion client_version = AllSupportedVersions().front(); + ParsedQuicVersion server_version = AllSupportedVersions().back(); + ASSERT_NE(client_version, server_version); + QuicVersionLabelVector version_information_other_versions{ + CreateQuicVersionLabel(client_version)}; + ParsedQuicVersionVector client_original_supported_versions{ + ParsedQuicVersion::ReservedForNegotiation(), server_version}; + std::string error_details; + EXPECT_FALSE(CryptoUtils::ValidateServerVersions( + version_information_other_versions, server_version, + client_original_supported_versions, &error_details)); + EXPECT_FALSE(error_details.empty()); +} + +// Test that the library is using the correct labels for each version, and +// therefore generating correct obfuscators, using the test vectors in appendix +// A of each RFC or internet-draft. +TEST_F(CryptoUtilsTest, ValidateCryptoLabels) { + // if the number of HTTP/3 QUIC versions has changed, we need to change the + // expected_keys hardcoded into this test. Regrettably, this is not a + // compile-time constant. + EXPECT_EQ(AllSupportedVersionsWithTls().size(), 3u); + const char draft_29_key[] = {// test vector from draft-ietf-quic-tls-29, A.1 + 0x14, + static_cast<char>(0x9d), + 0x0b, + 0x16, + 0x62, + static_cast<char>(0xab), + static_cast<char>(0x87), + 0x1f, + static_cast<char>(0xbe), + 0x63, + static_cast<char>(0xc4), + static_cast<char>(0x9b), + 0x5e, + 0x65, + 0x5a, + 0x5d}; + const char v1_key[] = {// test vector from RFC 9001, A.1 + static_cast<char>(0xcf), + 0x3a, + 0x53, + 0x31, + 0x65, + 0x3c, + 0x36, + 0x4c, + static_cast<char>(0x88), + static_cast<char>(0xf0), + static_cast<char>(0xf3), + 0x79, + static_cast<char>(0xb6), + 0x06, + 0x7e, + 0x37}; + const char v2_01_key[] = {// test vector from draft-ietf-quic-v2-01 + 0x15, + static_cast<char>(0xd5), + static_cast<char>(0xb4), + static_cast<char>(0xd9), + static_cast<char>(0xa2), + static_cast<char>(0xb8), + static_cast<char>(0x91), + 0x6a, + static_cast<char>(0xa3), + static_cast<char>(0x9b), + 0x1b, + static_cast<char>(0xfe), + 0x57, + 0x4d, + 0x2a, + static_cast<char>(0xad)}; + const char connection_id[] = // test vector from both docs + {static_cast<char>(0x83), + static_cast<char>(0x94), + static_cast<char>(0xc8), + static_cast<char>(0xf0), + 0x3e, + 0x51, + 0x57, + 0x08}; + const QuicConnectionId cid(connection_id, sizeof(connection_id)); + const char* key_str; + size_t key_size; + for (const ParsedQuicVersion& version : AllSupportedVersionsWithTls()) { + if (version == ParsedQuicVersion::Draft29()) { + key_str = draft_29_key; + key_size = sizeof(draft_29_key); + } else if (version == ParsedQuicVersion::RFCv1()) { + key_str = v1_key; + key_size = sizeof(v1_key); + } else { // draft-ietf-quic-v2-01 + key_str = v2_01_key; + key_size = sizeof(v2_01_key); + } + const absl::string_view expected_key{key_str, key_size}; + + CrypterPair crypters; + CryptoUtils::CreateInitialObfuscators(Perspective::IS_SERVER, version, cid, + &crypters); + EXPECT_EQ(crypters.encrypter->GetKey(), expected_key); + } +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/curve25519_key_exchange.cc b/quiche/quic/core/crypto/curve25519_key_exchange.cc new file mode 100644 index 0000000..f84da2a --- /dev/null +++ b/quiche/quic/core/crypto/curve25519_key_exchange.cc
@@ -0,0 +1,87 @@ +// 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 "quiche/quic/core/crypto/curve25519_key_exchange.h" + +#include <cstdint> +#include <cstring> +#include <string> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/curve25519.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +Curve25519KeyExchange::Curve25519KeyExchange() {} + +Curve25519KeyExchange::~Curve25519KeyExchange() {} + +// static +std::unique_ptr<Curve25519KeyExchange> Curve25519KeyExchange::New( + QuicRandom* rand) { + std::unique_ptr<Curve25519KeyExchange> result = + New(Curve25519KeyExchange::NewPrivateKey(rand)); + QUIC_BUG_IF(quic_bug_12891_1, result == nullptr); + return result; +} + +// static +std::unique_ptr<Curve25519KeyExchange> Curve25519KeyExchange::New( + absl::string_view private_key) { + // We don't want to #include the BoringSSL headers in the public header file, + // so we use literals for the sizes of private_key_ and public_key_. Here we + // assert that those values are equal to the values from the BoringSSL + // header. + static_assert( + sizeof(Curve25519KeyExchange::private_key_) == X25519_PRIVATE_KEY_LEN, + "header out of sync"); + static_assert( + sizeof(Curve25519KeyExchange::public_key_) == X25519_PUBLIC_VALUE_LEN, + "header out of sync"); + + if (private_key.size() != X25519_PRIVATE_KEY_LEN) { + return nullptr; + } + + // Use absl::WrapUnique(new) instead of std::make_unique because + // Curve25519KeyExchange has a private constructor. + auto ka = absl::WrapUnique(new Curve25519KeyExchange); + memcpy(ka->private_key_, private_key.data(), X25519_PRIVATE_KEY_LEN); + X25519_public_from_private(ka->public_key_, ka->private_key_); + return ka; +} + +// static +std::string Curve25519KeyExchange::NewPrivateKey(QuicRandom* rand) { + uint8_t private_key[X25519_PRIVATE_KEY_LEN]; + rand->RandBytes(private_key, sizeof(private_key)); + return std::string(reinterpret_cast<char*>(private_key), sizeof(private_key)); +} + +bool Curve25519KeyExchange::CalculateSharedKeySync( + absl::string_view peer_public_value, + std::string* shared_key) const { + if (peer_public_value.size() != X25519_PUBLIC_VALUE_LEN) { + return false; + } + + uint8_t result[X25519_PUBLIC_VALUE_LEN]; + if (!X25519(result, private_key_, + reinterpret_cast<const uint8_t*>(peer_public_value.data()))) { + return false; + } + + shared_key->assign(reinterpret_cast<char*>(result), sizeof(result)); + return true; +} + +absl::string_view Curve25519KeyExchange::public_value() const { + return absl::string_view(reinterpret_cast<const char*>(public_key_), + sizeof(public_key_)); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/curve25519_key_exchange.h b/quiche/quic/core/crypto/curve25519_key_exchange.h new file mode 100644 index 0000000..34a49f9 --- /dev/null +++ b/quiche/quic/core/crypto/curve25519_key_exchange.h
@@ -0,0 +1,53 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CURVE25519_KEY_EXCHANGE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CURVE25519_KEY_EXCHANGE_H_ + +#include <cstdint> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/key_exchange.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QuicRandom; + +// Curve25519KeyExchange implements a SynchronousKeyExchange using +// elliptic-curve Diffie-Hellman on curve25519. See http://cr.yp.to/ecdh.html +class QUIC_EXPORT_PRIVATE Curve25519KeyExchange + : public SynchronousKeyExchange { + public: + ~Curve25519KeyExchange() override; + + // New generates a private key and then creates new key-exchange object. + static std::unique_ptr<Curve25519KeyExchange> New(QuicRandom* rand); + + // New creates a new key-exchange object from a private key. If |private_key| + // is invalid, nullptr is returned. + static std::unique_ptr<Curve25519KeyExchange> New( + absl::string_view private_key); + + // NewPrivateKey returns a private key, generated from |rand|, suitable for + // passing to |New|. + static std::string NewPrivateKey(QuicRandom* rand); + + // SynchronousKeyExchange interface. + bool CalculateSharedKeySync(absl::string_view peer_public_value, + std::string* shared_key) const override; + absl::string_view public_value() const override; + QuicTag type() const override { return kC255; } + + private: + Curve25519KeyExchange(); + + uint8_t private_key_[32]; + uint8_t public_key_[32]; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
diff --git a/quiche/quic/core/crypto/curve25519_key_exchange_test.cc b/quiche/quic/core/crypto/curve25519_key_exchange_test.cc new file mode 100644 index 0000000..551ee0e --- /dev/null +++ b/quiche/quic/core/crypto/curve25519_key_exchange_test.cc
@@ -0,0 +1,104 @@ +// 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 "quiche/quic/core/crypto/curve25519_key_exchange.h" + +#include <memory> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { + +class Curve25519KeyExchangeTest : public QuicTest { + public: + // Holds the result of a key exchange callback. + class TestCallbackResult { + public: + void set_ok(bool ok) { ok_ = ok; } + bool ok() { return ok_; } + + private: + bool ok_ = false; + }; + + // Key exchange callback which sets the result into the specified + // TestCallbackResult. + class TestCallback : public AsynchronousKeyExchange::Callback { + public: + TestCallback(TestCallbackResult* result) : result_(result) {} + virtual ~TestCallback() = default; + + void Run(bool ok) { result_->set_ok(ok); } + + private: + TestCallbackResult* result_; + }; +}; + +// SharedKey just tests that the basic key exchange identity holds: that both +// parties end up with the same key. +TEST_F(Curve25519KeyExchangeTest, SharedKey) { + QuicRandom* const rand = QuicRandom::GetInstance(); + + for (int i = 0; i < 5; i++) { + const std::string alice_key(Curve25519KeyExchange::NewPrivateKey(rand)); + const std::string bob_key(Curve25519KeyExchange::NewPrivateKey(rand)); + + std::unique_ptr<Curve25519KeyExchange> alice( + Curve25519KeyExchange::New(alice_key)); + std::unique_ptr<Curve25519KeyExchange> bob( + Curve25519KeyExchange::New(bob_key)); + + const absl::string_view alice_public(alice->public_value()); + const absl::string_view bob_public(bob->public_value()); + + std::string alice_shared, bob_shared; + ASSERT_TRUE(alice->CalculateSharedKeySync(bob_public, &alice_shared)); + ASSERT_TRUE(bob->CalculateSharedKeySync(alice_public, &bob_shared)); + ASSERT_EQ(alice_shared, bob_shared); + } +} + +// SharedKeyAsync just tests that the basic asynchronous key exchange identity +// holds: that both parties end up with the same key. +TEST_F(Curve25519KeyExchangeTest, SharedKeyAsync) { + QuicRandom* const rand = QuicRandom::GetInstance(); + + for (int i = 0; i < 5; i++) { + const std::string alice_key(Curve25519KeyExchange::NewPrivateKey(rand)); + const std::string bob_key(Curve25519KeyExchange::NewPrivateKey(rand)); + + std::unique_ptr<Curve25519KeyExchange> alice( + Curve25519KeyExchange::New(alice_key)); + std::unique_ptr<Curve25519KeyExchange> bob( + Curve25519KeyExchange::New(bob_key)); + + const absl::string_view alice_public(alice->public_value()); + const absl::string_view bob_public(bob->public_value()); + + std::string alice_shared, bob_shared; + TestCallbackResult alice_result; + ASSERT_FALSE(alice_result.ok()); + alice->CalculateSharedKeyAsync( + bob_public, &alice_shared, + std::make_unique<TestCallback>(&alice_result)); + ASSERT_TRUE(alice_result.ok()); + TestCallbackResult bob_result; + ASSERT_FALSE(bob_result.ok()); + bob->CalculateSharedKeyAsync(alice_public, &bob_shared, + std::make_unique<TestCallback>(&bob_result)); + ASSERT_TRUE(bob_result.ok()); + ASSERT_EQ(alice_shared, bob_shared); + ASSERT_NE(0u, alice_shared.length()); + ASSERT_NE(0u, bob_shared.length()); + } +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/key_exchange.cc b/quiche/quic/core/crypto/key_exchange.cc new file mode 100644 index 0000000..4a5947b --- /dev/null +++ b/quiche/quic/core/crypto/key_exchange.cc
@@ -0,0 +1,43 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/key_exchange.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/curve25519_key_exchange.h" +#include "quiche/quic/core/crypto/p256_key_exchange.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +std::unique_ptr<SynchronousKeyExchange> CreateLocalSynchronousKeyExchange( + QuicTag type, + absl::string_view private_key) { + switch (type) { + case kC255: + return Curve25519KeyExchange::New(private_key); + case kP256: + return P256KeyExchange::New(private_key); + default: + QUIC_BUG(quic_bug_10712_1) + << "Unknown key exchange method: " << QuicTagToString(type); + return nullptr; + } +} + +std::unique_ptr<SynchronousKeyExchange> CreateLocalSynchronousKeyExchange( + QuicTag type, + QuicRandom* rand) { + switch (type) { + case kC255: + return Curve25519KeyExchange::New(rand); + case kP256: + return P256KeyExchange::New(); + default: + QUIC_BUG(quic_bug_10712_2) + << "Unknown key exchange method: " << QuicTagToString(type); + return nullptr; + } +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/key_exchange.h b/quiche/quic/core/crypto/key_exchange.h new file mode 100644 index 0000000..74c44ee --- /dev/null +++ b/quiche/quic/core/crypto/key_exchange.h
@@ -0,0 +1,105 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_KEY_EXCHANGE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_KEY_EXCHANGE_H_ + +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QuicRandom; + +// Interface for a Diffie-Hellman key exchange with an asynchronous interface. +// This allows for implementations which hold the private key locally, as well +// as ones which make an RPC to an external key-exchange service. +class QUIC_EXPORT_PRIVATE AsynchronousKeyExchange { + public: + virtual ~AsynchronousKeyExchange() = default; + + // Callback base class for receiving the results of an async call to + // CalculateSharedKeys. + class QUIC_EXPORT_PRIVATE Callback { + public: + Callback() = default; + virtual ~Callback() = default; + + // Invoked upon completion of CalculateSharedKeysAsync. + // + // |ok| indicates whether the operation completed successfully. If false, + // then the value pointed to by |shared_key| passed in to + // CalculateSharedKeyAsync is undefined. + virtual void Run(bool ok) = 0; + + private: + Callback(const Callback&) = delete; + Callback& operator=(const Callback&) = delete; + }; + + // CalculateSharedKey computes the shared key between a private key which is + // conceptually owned by this object (though it may not be physically located + // in this process) and a public value from the peer. Callers should expect + // that |callback| might be invoked synchronously. Results will be written + // into |*shared_key|. + virtual void CalculateSharedKeyAsync( + absl::string_view peer_public_value, + std::string* shared_key, + std::unique_ptr<Callback> callback) const = 0; + + // Tag indicating the key-exchange algorithm this object will use. + virtual QuicTag type() const = 0; +}; + +// Interface for a Diffie-Hellman key exchange with both synchronous and +// asynchronous interfaces. Only implementations which hold the private key +// locally should implement this interface. +class QUIC_EXPORT_PRIVATE SynchronousKeyExchange + : public AsynchronousKeyExchange { + public: + virtual ~SynchronousKeyExchange() = default; + + // AyncKeyExchange API. Note that this method is marked 'final.' Subclasses + // should implement CalculateSharedKeySync only. + void CalculateSharedKeyAsync(absl::string_view peer_public_value, + std::string* shared_key, + std::unique_ptr<Callback> callback) const final { + const bool ok = CalculateSharedKeySync(peer_public_value, shared_key); + callback->Run(ok); + } + + // CalculateSharedKey computes the shared key between a local private key and + // a public value from the peer. Results will be written into |*shared_key|. + virtual bool CalculateSharedKeySync(absl::string_view peer_public_value, + std::string* shared_key) const = 0; + + // public_value returns the local public key which can be sent to a peer in + // order to complete a key exchange. The returned absl::string_view is + // a reference to a member of this object and is only valid for as long as it + // exists. + virtual absl::string_view public_value() const = 0; +}; + +// Create a SynchronousKeyExchange object which will use a keypair generated +// from |private_key|, and a key-exchange algorithm specified by |type|, which +// must be one of {kC255, kC256}. Returns nullptr if |private_key| or |type| is +// invalid. +std::unique_ptr<SynchronousKeyExchange> CreateLocalSynchronousKeyExchange( + QuicTag type, + absl::string_view private_key); + +// Create a SynchronousKeyExchange object which will use a keypair generated +// from |rand|, and a key-exchange algorithm specified by |type|, which must be +// one of {kC255, kC256}. Returns nullptr if |type| is invalid. +std::unique_ptr<SynchronousKeyExchange> CreateLocalSynchronousKeyExchange( + QuicTag type, + QuicRandom* rand); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_KEY_EXCHANGE_H_
diff --git a/quiche/quic/core/crypto/null_decrypter.cc b/quiche/quic/core/crypto/null_decrypter.cc new file mode 100644 index 0000000..5287ce9 --- /dev/null +++ b/quiche/quic/core/crypto/null_decrypter.cc
@@ -0,0 +1,136 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/null_decrypter.h" + +#include <cstdint> + +#include "absl/numeric/int128.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +NullDecrypter::NullDecrypter(Perspective perspective) + : perspective_(perspective) {} + +bool NullDecrypter::SetKey(absl::string_view key) { + return key.empty(); +} + +bool NullDecrypter::SetNoncePrefix(absl::string_view nonce_prefix) { + return nonce_prefix.empty(); +} + +bool NullDecrypter::SetIV(absl::string_view iv) { + return iv.empty(); +} + +bool NullDecrypter::SetHeaderProtectionKey(absl::string_view key) { + return key.empty(); +} + +bool NullDecrypter::SetPreliminaryKey(absl::string_view /*key*/) { + QUIC_BUG(quic_bug_10652_1) << "Should not be called"; + return false; +} + +bool NullDecrypter::SetDiversificationNonce( + const DiversificationNonce& /*nonce*/) { + QUIC_BUG(quic_bug_10652_2) << "Should not be called"; + return true; +} + +bool NullDecrypter::DecryptPacket(uint64_t /*packet_number*/, + absl::string_view associated_data, + absl::string_view ciphertext, + char* output, + size_t* output_length, + size_t max_output_length) { + QuicDataReader reader(ciphertext.data(), ciphertext.length(), + quiche::HOST_BYTE_ORDER); + absl::uint128 hash; + + if (!ReadHash(&reader, &hash)) { + return false; + } + + absl::string_view plaintext = reader.ReadRemainingPayload(); + if (plaintext.length() > max_output_length) { + QUIC_BUG(quic_bug_10652_3) + << "Output buffer must be larger than the plaintext."; + return false; + } + if (hash != ComputeHash(associated_data, plaintext)) { + return false; + } + // Copy the plaintext to output. + memcpy(output, plaintext.data(), plaintext.length()); + *output_length = plaintext.length(); + return true; +} + +std::string NullDecrypter::GenerateHeaderProtectionMask( + QuicDataReader* /*sample_reader*/) { + return std::string(5, 0); +} + +size_t NullDecrypter::GetKeySize() const { + return 0; +} + +size_t NullDecrypter::GetNoncePrefixSize() const { + return 0; +} + +size_t NullDecrypter::GetIVSize() const { + return 0; +} + +absl::string_view NullDecrypter::GetKey() const { + return absl::string_view(); +} + +absl::string_view NullDecrypter::GetNoncePrefix() const { + return absl::string_view(); +} + +uint32_t NullDecrypter::cipher_id() const { + return 0; +} + +QuicPacketCount NullDecrypter::GetIntegrityLimit() const { + return std::numeric_limits<QuicPacketCount>::max(); +} + +bool NullDecrypter::ReadHash(QuicDataReader* reader, absl::uint128* hash) { + uint64_t lo; + uint32_t hi; + if (!reader->ReadUInt64(&lo) || !reader->ReadUInt32(&hi)) { + return false; + } + *hash = absl::MakeUint128(hi, lo); + return true; +} + +absl::uint128 NullDecrypter::ComputeHash(const absl::string_view data1, + const absl::string_view data2) const { + absl::uint128 correct_hash; + if (perspective_ == Perspective::IS_CLIENT) { + // Peer is a server. + correct_hash = QuicUtils::FNV1a_128_Hash_Three(data1, data2, "Server"); + } else { + // Peer is a client. + correct_hash = QuicUtils::FNV1a_128_Hash_Three(data1, data2, "Client"); + } + absl::uint128 mask = absl::MakeUint128(UINT64_C(0x0), UINT64_C(0xffffffff)); + mask <<= 96; + correct_hash &= ~mask; + return correct_hash; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/null_decrypter.h b/quiche/quic/core/crypto/null_decrypter.h new file mode 100644 index 0000000..05e1103 --- /dev/null +++ b/quiche/quic/core/crypto/null_decrypter.h
@@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_NULL_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_NULL_DECRYPTER_H_ + +#include <cstddef> +#include <cstdint> + +#include "absl/numeric/int128.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/quic_decrypter.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QuicDataReader; + +// A NullDecrypter is a QuicDecrypter used before a crypto negotiation +// has occurred. It does not actually decrypt the payload, but does +// verify a hash (fnv128) over both the payload and associated data. +class QUIC_EXPORT_PRIVATE NullDecrypter : public QuicDecrypter { + public: + explicit NullDecrypter(Perspective perspective); + NullDecrypter(const NullDecrypter&) = delete; + NullDecrypter& operator=(const NullDecrypter&) = delete; + ~NullDecrypter() override {} + + // QuicDecrypter implementation + bool SetKey(absl::string_view key) override; + bool SetNoncePrefix(absl::string_view nonce_prefix) override; + bool SetIV(absl::string_view iv) override; + bool SetHeaderProtectionKey(absl::string_view key) override; + bool SetPreliminaryKey(absl::string_view key) override; + bool SetDiversificationNonce(const DiversificationNonce& nonce) override; + bool DecryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view ciphertext, + char* output, + size_t* output_length, + size_t max_output_length) override; + std::string GenerateHeaderProtectionMask( + QuicDataReader* sample_reader) override; + size_t GetKeySize() const override; + size_t GetNoncePrefixSize() const override; + size_t GetIVSize() const override; + absl::string_view GetKey() const override; + absl::string_view GetNoncePrefix() const override; + + uint32_t cipher_id() const override; + QuicPacketCount GetIntegrityLimit() const override; + + private: + bool ReadHash(QuicDataReader* reader, absl::uint128* hash); + absl::uint128 ComputeHash(absl::string_view data1, + absl::string_view data2) const; + + Perspective perspective_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_NULL_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/null_decrypter_test.cc b/quiche/quic/core/crypto/null_decrypter_test.cc new file mode 100644 index 0000000..ad06bea --- /dev/null +++ b/quiche/quic/core/crypto/null_decrypter_test.cc
@@ -0,0 +1,136 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/null_decrypter.h" +#include "absl/base/macros.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +namespace quic { +namespace test { + +class NullDecrypterTest : public QuicTestWithParam<bool> {}; + +TEST_F(NullDecrypterTest, DecryptClient) { + unsigned char expected[] = { + // fnv hash + 0x97, + 0xdc, + 0x27, + 0x2f, + 0x18, + 0xa8, + 0x56, + 0x73, + 0xdf, + 0x8d, + 0x1d, + 0xd0, + // payload + 'g', + 'o', + 'o', + 'd', + 'b', + 'y', + 'e', + '!', + }; + const char* data = reinterpret_cast<const char*>(expected); + size_t len = ABSL_ARRAYSIZE(expected); + NullDecrypter decrypter(Perspective::IS_SERVER); + char buffer[256]; + size_t length = 0; + ASSERT_TRUE(decrypter.DecryptPacket( + 0, "hello world!", absl::string_view(data, len), buffer, &length, 256)); + EXPECT_LT(0u, length); + EXPECT_EQ("goodbye!", absl::string_view(buffer, length)); +} + +TEST_F(NullDecrypterTest, DecryptServer) { + unsigned char expected[] = { + // fnv hash + 0x63, + 0x5e, + 0x08, + 0x03, + 0x32, + 0x80, + 0x8f, + 0x73, + 0xdf, + 0x8d, + 0x1d, + 0x1a, + // payload + 'g', + 'o', + 'o', + 'd', + 'b', + 'y', + 'e', + '!', + }; + const char* data = reinterpret_cast<const char*>(expected); + size_t len = ABSL_ARRAYSIZE(expected); + NullDecrypter decrypter(Perspective::IS_CLIENT); + char buffer[256]; + size_t length = 0; + ASSERT_TRUE(decrypter.DecryptPacket( + 0, "hello world!", absl::string_view(data, len), buffer, &length, 256)); + EXPECT_LT(0u, length); + EXPECT_EQ("goodbye!", absl::string_view(buffer, length)); +} + +TEST_F(NullDecrypterTest, BadHash) { + unsigned char expected[] = { + // fnv hash + 0x46, + 0x11, + 0xea, + 0x5f, + 0xcf, + 0x1d, + 0x66, + 0x5b, + 0xba, + 0xf0, + 0xbc, + 0xfd, + // payload + 'g', + 'o', + 'o', + 'd', + 'b', + 'y', + 'e', + '!', + }; + const char* data = reinterpret_cast<const char*>(expected); + size_t len = ABSL_ARRAYSIZE(expected); + NullDecrypter decrypter(Perspective::IS_CLIENT); + char buffer[256]; + size_t length = 0; + ASSERT_FALSE(decrypter.DecryptPacket( + 0, "hello world!", absl::string_view(data, len), buffer, &length, 256)); +} + +TEST_F(NullDecrypterTest, ShortInput) { + unsigned char expected[] = { + // fnv hash (truncated) + 0x46, 0x11, 0xea, 0x5f, 0xcf, 0x1d, 0x66, 0x5b, 0xba, 0xf0, 0xbc, + }; + const char* data = reinterpret_cast<const char*>(expected); + size_t len = ABSL_ARRAYSIZE(expected); + NullDecrypter decrypter(Perspective::IS_CLIENT); + char buffer[256]; + size_t length = 0; + ASSERT_FALSE(decrypter.DecryptPacket( + 0, "hello world!", absl::string_view(data, len), buffer, &length, 256)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/null_encrypter.cc b/quiche/quic/core/crypto/null_encrypter.cc new file mode 100644 index 0000000..8df815b --- /dev/null +++ b/quiche/quic/core/crypto/null_encrypter.cc
@@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/null_encrypter.h" + +#include "absl/numeric/int128.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/core/quic_utils.h" + +namespace quic { + +const size_t kHashSizeShort = 12; // size of uint128 serialized short + +NullEncrypter::NullEncrypter(Perspective perspective) + : perspective_(perspective) {} + +bool NullEncrypter::SetKey(absl::string_view key) { + return key.empty(); +} + +bool NullEncrypter::SetNoncePrefix(absl::string_view nonce_prefix) { + return nonce_prefix.empty(); +} + +bool NullEncrypter::SetIV(absl::string_view iv) { + return iv.empty(); +} + +bool NullEncrypter::SetHeaderProtectionKey(absl::string_view key) { + return key.empty(); +} + +bool NullEncrypter::EncryptPacket(uint64_t /*packet_number*/, + absl::string_view associated_data, + absl::string_view plaintext, + char* output, + size_t* output_length, + size_t max_output_length) { + const size_t len = plaintext.size() + GetHashLength(); + if (max_output_length < len) { + return false; + } + absl::uint128 hash; + if (perspective_ == Perspective::IS_SERVER) { + hash = + QuicUtils::FNV1a_128_Hash_Three(associated_data, plaintext, "Server"); + } else { + hash = + QuicUtils::FNV1a_128_Hash_Three(associated_data, plaintext, "Client"); + } + // TODO(ianswett): memmove required for in place encryption. Placing the + // hash at the end would allow use of memcpy, doing nothing for in place. + memmove(output + GetHashLength(), plaintext.data(), plaintext.length()); + QuicUtils::SerializeUint128Short(hash, + reinterpret_cast<unsigned char*>(output)); + *output_length = len; + return true; +} + +std::string NullEncrypter::GenerateHeaderProtectionMask( + absl::string_view /*sample*/) { + return std::string(5, 0); +} + +size_t NullEncrypter::GetKeySize() const { + return 0; +} + +size_t NullEncrypter::GetNoncePrefixSize() const { + return 0; +} + +size_t NullEncrypter::GetIVSize() const { + return 0; +} + +size_t NullEncrypter::GetMaxPlaintextSize(size_t ciphertext_size) const { + return ciphertext_size - std::min(ciphertext_size, GetHashLength()); +} + +size_t NullEncrypter::GetCiphertextSize(size_t plaintext_size) const { + return plaintext_size + GetHashLength(); +} + +QuicPacketCount NullEncrypter::GetConfidentialityLimit() const { + return std::numeric_limits<QuicPacketCount>::max(); +} + +absl::string_view NullEncrypter::GetKey() const { + return absl::string_view(); +} + +absl::string_view NullEncrypter::GetNoncePrefix() const { + return absl::string_view(); +} + +size_t NullEncrypter::GetHashLength() const { + return kHashSizeShort; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/null_encrypter.h b/quiche/quic/core/crypto/null_encrypter.h new file mode 100644 index 0000000..cdcf66c --- /dev/null +++ b/quiche/quic/core/crypto/null_encrypter.h
@@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_NULL_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_NULL_ENCRYPTER_H_ + +#include <cstddef> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// A NullEncrypter is a QuicEncrypter used before a crypto negotiation +// has occurred. It does not actually encrypt the payload, but does +// generate a MAC (fnv128) over both the payload and associated data. +class QUIC_EXPORT_PRIVATE NullEncrypter : public QuicEncrypter { + public: + explicit NullEncrypter(Perspective perspective); + NullEncrypter(const NullEncrypter&) = delete; + NullEncrypter& operator=(const NullEncrypter&) = delete; + ~NullEncrypter() override {} + + // QuicEncrypter implementation + bool SetKey(absl::string_view key) override; + bool SetNoncePrefix(absl::string_view nonce_prefix) override; + bool SetIV(absl::string_view iv) override; + bool SetHeaderProtectionKey(absl::string_view key) override; + bool EncryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view plaintext, + char* output, + size_t* output_length, + size_t max_output_length) override; + std::string GenerateHeaderProtectionMask(absl::string_view sample) override; + size_t GetKeySize() const override; + size_t GetNoncePrefixSize() const override; + size_t GetIVSize() const override; + size_t GetMaxPlaintextSize(size_t ciphertext_size) const override; + size_t GetCiphertextSize(size_t plaintext_size) const override; + QuicPacketCount GetConfidentialityLimit() const override; + absl::string_view GetKey() const override; + absl::string_view GetNoncePrefix() const override; + + private: + size_t GetHashLength() const; + + Perspective perspective_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_NULL_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/null_encrypter_test.cc b/quiche/quic/core/crypto/null_encrypter_test.cc new file mode 100644 index 0000000..26a143e --- /dev/null +++ b/quiche/quic/core/crypto/null_encrypter_test.cc
@@ -0,0 +1,102 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/null_encrypter.h" +#include "absl/base/macros.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/common/test_tools/quiche_test_utils.h" + +namespace quic { +namespace test { + +class NullEncrypterTest : public QuicTestWithParam<bool> {}; + +TEST_F(NullEncrypterTest, EncryptClient) { + unsigned char expected[] = { + // fnv hash + 0x97, + 0xdc, + 0x27, + 0x2f, + 0x18, + 0xa8, + 0x56, + 0x73, + 0xdf, + 0x8d, + 0x1d, + 0xd0, + // payload + 'g', + 'o', + 'o', + 'd', + 'b', + 'y', + 'e', + '!', + }; + char encrypted[256]; + size_t encrypted_len = 0; + NullEncrypter encrypter(Perspective::IS_CLIENT); + ASSERT_TRUE(encrypter.EncryptPacket(0, "hello world!", "goodbye!", encrypted, + &encrypted_len, 256)); + quiche::test::CompareCharArraysWithHexError( + "encrypted data", encrypted, encrypted_len, + reinterpret_cast<const char*>(expected), ABSL_ARRAYSIZE(expected)); +} + +TEST_F(NullEncrypterTest, EncryptServer) { + unsigned char expected[] = { + // fnv hash + 0x63, + 0x5e, + 0x08, + 0x03, + 0x32, + 0x80, + 0x8f, + 0x73, + 0xdf, + 0x8d, + 0x1d, + 0x1a, + // payload + 'g', + 'o', + 'o', + 'd', + 'b', + 'y', + 'e', + '!', + }; + char encrypted[256]; + size_t encrypted_len = 0; + NullEncrypter encrypter(Perspective::IS_SERVER); + ASSERT_TRUE(encrypter.EncryptPacket(0, "hello world!", "goodbye!", encrypted, + &encrypted_len, 256)); + quiche::test::CompareCharArraysWithHexError( + "encrypted data", encrypted, encrypted_len, + reinterpret_cast<const char*>(expected), ABSL_ARRAYSIZE(expected)); +} + +TEST_F(NullEncrypterTest, GetMaxPlaintextSize) { + NullEncrypter encrypter(Perspective::IS_CLIENT); + EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012)); + EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112)); + EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22)); + EXPECT_EQ(0u, encrypter.GetMaxPlaintextSize(11)); +} + +TEST_F(NullEncrypterTest, GetCiphertextSize) { + NullEncrypter encrypter(Perspective::IS_CLIENT); + EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000)); + EXPECT_EQ(112u, encrypter.GetCiphertextSize(100)); + EXPECT_EQ(22u, encrypter.GetCiphertextSize(10)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/p256_key_exchange.cc b/quiche/quic/core/crypto/p256_key_exchange.cc new file mode 100644 index 0000000..3cff503 --- /dev/null +++ b/quiche/quic/core/crypto/p256_key_exchange.cc
@@ -0,0 +1,122 @@ +// 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 "quiche/quic/core/crypto/p256_key_exchange.h" + +#include <cstdint> +#include <cstring> +#include <memory> +#include <string> +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ec.h" +#include "third_party/boringssl/src/include/openssl/ecdh.h" +#include "third_party/boringssl/src/include/openssl/err.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +P256KeyExchange::P256KeyExchange(bssl::UniquePtr<EC_KEY> private_key, + const uint8_t* public_key) + : private_key_(std::move(private_key)) { + memcpy(public_key_, public_key, sizeof(public_key_)); +} + +P256KeyExchange::~P256KeyExchange() {} + +// static +std::unique_ptr<P256KeyExchange> P256KeyExchange::New() { + return New(P256KeyExchange::NewPrivateKey()); +} + +// static +std::unique_ptr<P256KeyExchange> P256KeyExchange::New(absl::string_view key) { + if (key.empty()) { + QUIC_DLOG(INFO) << "Private key is empty"; + return nullptr; + } + + const uint8_t* keyp = reinterpret_cast<const uint8_t*>(key.data()); + bssl::UniquePtr<EC_KEY> private_key( + d2i_ECPrivateKey(nullptr, &keyp, key.size())); + if (!private_key.get() || !EC_KEY_check_key(private_key.get())) { + QUIC_DLOG(INFO) << "Private key is invalid."; + return nullptr; + } + + uint8_t public_key[kUncompressedP256PointBytes]; + if (EC_POINT_point2oct(EC_KEY_get0_group(private_key.get()), + EC_KEY_get0_public_key(private_key.get()), + POINT_CONVERSION_UNCOMPRESSED, public_key, + sizeof(public_key), nullptr) != sizeof(public_key)) { + QUIC_DLOG(INFO) << "Can't get public key."; + return nullptr; + } + + return absl::WrapUnique( + new P256KeyExchange(std::move(private_key), public_key)); +} + +// static +std::string P256KeyExchange::NewPrivateKey() { + bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + if (!key.get() || !EC_KEY_generate_key(key.get())) { + QUIC_DLOG(INFO) << "Can't generate a new private key."; + return std::string(); + } + + int key_len = i2d_ECPrivateKey(key.get(), nullptr); + if (key_len <= 0) { + QUIC_DLOG(INFO) << "Can't convert private key to string"; + return std::string(); + } + std::unique_ptr<uint8_t[]> private_key(new uint8_t[key_len]); + uint8_t* keyp = private_key.get(); + if (!i2d_ECPrivateKey(key.get(), &keyp)) { + QUIC_DLOG(INFO) << "Can't convert private key to string."; + return std::string(); + } + return std::string(reinterpret_cast<char*>(private_key.get()), key_len); +} + +bool P256KeyExchange::CalculateSharedKeySync( + absl::string_view peer_public_value, + std::string* shared_key) const { + if (peer_public_value.size() != kUncompressedP256PointBytes) { + QUIC_DLOG(INFO) << "Peer public value is invalid"; + return false; + } + + bssl::UniquePtr<EC_POINT> point( + EC_POINT_new(EC_KEY_get0_group(private_key_.get()))); + if (!point.get() || + !EC_POINT_oct2point(/* also test if point is on curve */ + EC_KEY_get0_group(private_key_.get()), point.get(), + reinterpret_cast<const uint8_t*>( + peer_public_value.data()), + peer_public_value.size(), nullptr)) { + QUIC_DLOG(INFO) << "Can't convert peer public value to curve point."; + return false; + } + + uint8_t result[kP256FieldBytes]; + if (ECDH_compute_key(result, sizeof(result), point.get(), private_key_.get(), + nullptr) != sizeof(result)) { + QUIC_DLOG(INFO) << "Can't compute ECDH shared key."; + return false; + } + + shared_key->assign(reinterpret_cast<char*>(result), sizeof(result)); + return true; +} + +absl::string_view P256KeyExchange::public_value() const { + return absl::string_view(reinterpret_cast<const char*>(public_key_), + sizeof(public_key_)); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/p256_key_exchange.h b/quiche/quic/core/crypto/p256_key_exchange.h new file mode 100644 index 0000000..4b23973 --- /dev/null +++ b/quiche/quic/core/crypto/p256_key_exchange.h
@@ -0,0 +1,68 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_P256_KEY_EXCHANGE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_P256_KEY_EXCHANGE_H_ + +#include <cstdint> +#include <string> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/base.h" +#include "quiche/quic/core/crypto/key_exchange.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// P256KeyExchange implements a SynchronousKeyExchange using elliptic-curve +// Diffie-Hellman on NIST P-256. +class QUIC_EXPORT_PRIVATE P256KeyExchange : public SynchronousKeyExchange { + public: + ~P256KeyExchange() override; + + // New generates a private key and then creates new key-exchange object. + static std::unique_ptr<P256KeyExchange> New(); + + // New creates a new key-exchange object from a private key. If |private_key| + // is invalid, nullptr is returned. + static std::unique_ptr<P256KeyExchange> New(absl::string_view private_key); + + // NewPrivateKey returns a private key, suitable for passing to |New|. + // If |NewPrivateKey| can't generate a private key, it returns an empty + // string. + static std::string NewPrivateKey(); + + // SynchronousKeyExchange interface. + bool CalculateSharedKeySync(absl::string_view peer_public_value, + std::string* shared_key) const override; + absl::string_view public_value() const override; + QuicTag type() const override { return kP256; } + + private: + enum { + // A P-256 field element consists of 32 bytes. + kP256FieldBytes = 32, + // A P-256 point in uncompressed form consists of 0x04 (to denote + // that the point is uncompressed) followed by two, 32-byte field + // elements. + kUncompressedP256PointBytes = 1 + 2 * kP256FieldBytes, + // The first byte in an uncompressed P-256 point. + kUncompressedECPointForm = 0x04, + }; + + // P256KeyExchange wraps |private_key|, and expects |public_key| consists of + // |kUncompressedP256PointBytes| bytes. + P256KeyExchange(bssl::UniquePtr<EC_KEY> private_key, + const uint8_t* public_key); + P256KeyExchange(const P256KeyExchange&) = delete; + P256KeyExchange& operator=(const P256KeyExchange&) = delete; + + bssl::UniquePtr<EC_KEY> private_key_; + // The public key stored as an uncompressed P-256 point. + uint8_t public_key_[kUncompressedP256PointBytes]; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_P256_KEY_EXCHANGE_H_
diff --git a/quiche/quic/core/crypto/p256_key_exchange_test.cc b/quiche/quic/core/crypto/p256_key_exchange_test.cc new file mode 100644 index 0000000..c9bc7d3 --- /dev/null +++ b/quiche/quic/core/crypto/p256_key_exchange_test.cc
@@ -0,0 +1,109 @@ +// 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 "quiche/quic/core/crypto/p256_key_exchange.h" + +#include <memory> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "quiche/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { + +class P256KeyExchangeTest : public QuicTest { + public: + // Holds the result of a key exchange callback. + class TestCallbackResult { + public: + void set_ok(bool ok) { ok_ = ok; } + bool ok() { return ok_; } + + private: + bool ok_ = false; + }; + + // Key exchange callback which sets the result into the specified + // TestCallbackResult. + class TestCallback : public AsynchronousKeyExchange::Callback { + public: + TestCallback(TestCallbackResult* result) : result_(result) {} + virtual ~TestCallback() = default; + + void Run(bool ok) { result_->set_ok(ok); } + + private: + TestCallbackResult* result_; + }; +}; + +// SharedKeyAsync just tests that the basic asynchronous key exchange identity +// holds: that both parties end up with the same key. +TEST_F(P256KeyExchangeTest, SharedKey) { + for (int i = 0; i < 5; i++) { + std::string alice_private(P256KeyExchange::NewPrivateKey()); + std::string bob_private(P256KeyExchange::NewPrivateKey()); + + ASSERT_FALSE(alice_private.empty()); + ASSERT_FALSE(bob_private.empty()); + ASSERT_NE(alice_private, bob_private); + + std::unique_ptr<P256KeyExchange> alice(P256KeyExchange::New(alice_private)); + std::unique_ptr<P256KeyExchange> bob(P256KeyExchange::New(bob_private)); + + ASSERT_TRUE(alice != nullptr); + ASSERT_TRUE(bob != nullptr); + + const absl::string_view alice_public(alice->public_value()); + const absl::string_view bob_public(bob->public_value()); + + std::string alice_shared, bob_shared; + ASSERT_TRUE(alice->CalculateSharedKeySync(bob_public, &alice_shared)); + ASSERT_TRUE(bob->CalculateSharedKeySync(alice_public, &bob_shared)); + ASSERT_EQ(alice_shared, bob_shared); + } +} + +// SharedKey just tests that the basic key exchange identity holds: that both +// parties end up with the same key. +TEST_F(P256KeyExchangeTest, AsyncSharedKey) { + for (int i = 0; i < 5; i++) { + std::string alice_private(P256KeyExchange::NewPrivateKey()); + std::string bob_private(P256KeyExchange::NewPrivateKey()); + + ASSERT_FALSE(alice_private.empty()); + ASSERT_FALSE(bob_private.empty()); + ASSERT_NE(alice_private, bob_private); + + std::unique_ptr<P256KeyExchange> alice(P256KeyExchange::New(alice_private)); + std::unique_ptr<P256KeyExchange> bob(P256KeyExchange::New(bob_private)); + + ASSERT_TRUE(alice != nullptr); + ASSERT_TRUE(bob != nullptr); + + const absl::string_view alice_public(alice->public_value()); + const absl::string_view bob_public(bob->public_value()); + + std::string alice_shared, bob_shared; + TestCallbackResult alice_result; + ASSERT_FALSE(alice_result.ok()); + alice->CalculateSharedKeyAsync( + bob_public, &alice_shared, + std::make_unique<TestCallback>(&alice_result)); + ASSERT_TRUE(alice_result.ok()); + TestCallbackResult bob_result; + ASSERT_FALSE(bob_result.ok()); + bob->CalculateSharedKeyAsync(alice_public, &bob_shared, + std::make_unique<TestCallback>(&bob_result)); + ASSERT_TRUE(bob_result.ok()); + ASSERT_EQ(alice_shared, bob_shared); + ASSERT_NE(0u, alice_shared.length()); + ASSERT_NE(0u, bob_shared.length()); + } +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/proof_source.cc b/quiche/quic/core/crypto/proof_source.cc new file mode 100644 index 0000000..95fb446 --- /dev/null +++ b/quiche/quic/core/crypto/proof_source.cc
@@ -0,0 +1,59 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/proof_source.h" + +#include <string> + +#include "quiche/quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +CryptoBuffers::~CryptoBuffers() { + for (size_t i = 0; i < value.size(); i++) { + CRYPTO_BUFFER_free(value[i]); + } +} + +ProofSource::Chain::Chain(const std::vector<std::string>& certs) + : certs(certs) {} + +ProofSource::Chain::~Chain() {} + +CryptoBuffers ProofSource::Chain::ToCryptoBuffers() const { + CryptoBuffers crypto_buffers; + crypto_buffers.value.reserve(certs.size()); + for (size_t i = 0; i < certs.size(); i++) { + crypto_buffers.value.push_back( + CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t*>(certs[i].data()), + certs[i].length(), nullptr)); + } + return crypto_buffers; +} + +bool ValidateCertAndKey( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const CertificatePrivateKey& key) { + if (chain.get() == nullptr || chain->certs.empty()) { + QUIC_BUG(quic_proof_source_empty_chain) << "Certificate chain is empty"; + return false; + } + + std::unique_ptr<CertificateView> leaf = + CertificateView::ParseSingleCertificate(chain->certs[0]); + if (leaf == nullptr) { + QUIC_BUG(quic_proof_source_unparsable_leaf_cert) + << "Unabled to parse leaf certificate"; + return false; + } + + if (!key.MatchesPublicKey(*leaf)) { + QUIC_BUG(quic_proof_source_key_mismatch) + << "Private key does not match the leaf certificate"; + return false; + } + return true; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/proof_source.h b/quiche/quic/core/crypto/proof_source.h new file mode 100644 index 0000000..e725409 --- /dev/null +++ b/quiche/quic/core/crypto/proof_source.h
@@ -0,0 +1,355 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/quic_crypto_proof.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/common/platform/api/quiche_reference_counted.h" + +namespace quic { + +namespace test { +class FakeProofSourceHandle; +} // namespace test + +// CryptoBuffers is a RAII class to own a std::vector<CRYPTO_BUFFER*> and the +// buffers the elements point to. +struct QUIC_EXPORT_PRIVATE CryptoBuffers { + CryptoBuffers() = default; + CryptoBuffers(const CryptoBuffers&) = delete; + CryptoBuffers(CryptoBuffers&&) = default; + ~CryptoBuffers(); + + std::vector<CRYPTO_BUFFER*> value; +}; + +// ProofSource is an interface by which a QUIC server can obtain certificate +// chains and signatures that prove its identity. +class QUIC_EXPORT_PRIVATE ProofSource { + public: + // Chain is a reference-counted wrapper for a vector of stringified + // certificates. + struct QUIC_EXPORT_PRIVATE Chain : public quiche::QuicheReferenceCounted { + explicit Chain(const std::vector<std::string>& certs); + Chain(const Chain&) = delete; + Chain& operator=(const Chain&) = delete; + + CryptoBuffers ToCryptoBuffers() const; + + const std::vector<std::string> certs; + + protected: + ~Chain() override; + }; + + // Details is an abstract class which acts as a container for any + // implementation-specific details that a ProofSource wants to return. + class QUIC_EXPORT_PRIVATE Details { + public: + virtual ~Details() {} + }; + + // Callback base class for receiving the results of an async call to GetProof. + class QUIC_EXPORT_PRIVATE Callback { + public: + Callback() {} + virtual ~Callback() {} + + // Invoked upon completion of GetProof. + // + // |ok| indicates whether the operation completed successfully. If false, + // the values of the remaining three arguments are undefined. + // + // |chain| is a reference-counted pointer to an object representing the + // certificate chain. + // + // |signature| contains the signature of the server config. + // + // |leaf_cert_sct| holds the signed timestamp (RFC6962) of the leaf cert. + // + // |details| holds a pointer to an object representing the statistics, if + // any, gathered during the operation of GetProof. If no stats are + // available, this will be nullptr. + virtual void Run(bool ok, + const quiche::QuicheReferenceCountedPointer<Chain>& chain, + const QuicCryptoProof& proof, + std::unique_ptr<Details> details) = 0; + + private: + Callback(const Callback&) = delete; + Callback& operator=(const Callback&) = delete; + }; + + // Base class for signalling the completion of a call to ComputeTlsSignature. + class QUIC_EXPORT_PRIVATE SignatureCallback { + public: + SignatureCallback() {} + virtual ~SignatureCallback() = default; + + // Invoked upon completion of ComputeTlsSignature. + // + // |ok| indicates whether the operation completed successfully. + // + // |signature| contains the signature of the data provided to + // ComputeTlsSignature. Its value is undefined if |ok| is false. + // + // |details| holds a pointer to an object representing the statistics, if + // any, gathered during the operation of ComputeTlsSignature. If no stats + // are available, this will be nullptr. + virtual void Run(bool ok, + std::string signature, + std::unique_ptr<Details> details) = 0; + + private: + SignatureCallback(const SignatureCallback&) = delete; + SignatureCallback& operator=(const SignatureCallback&) = delete; + }; + + virtual ~ProofSource() {} + + // GetProof finds a certificate chain for |hostname| (in leaf-first order), + // and calculates a signature of |server_config| using that chain. + // + // The signature uses SHA-256 as the hash function and PSS padding when the + // key is RSA. + // + // The signature uses SHA-256 as the hash function when the key is ECDSA. + // The signature may use an ECDSA key. + // + // The signature depends on |chlo_hash| which means that the signature can not + // be cached. + // + // |hostname| may be empty to signify that a default certificate should be + // used. + // + // This function may be called concurrently. + // + // Callers should expect that |callback| might be invoked synchronously. + virtual void GetProof(const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + const std::string& hostname, + const std::string& server_config, + QuicTransportVersion transport_version, + absl::string_view chlo_hash, + std::unique_ptr<Callback> callback) = 0; + + // Returns the certificate chain for |hostname| in leaf-first order. + // + // Sets *cert_matched_sni to true if the certificate matched the given + // hostname, false if a default cert not matching the hostname was used. + virtual quiche::QuicheReferenceCountedPointer<Chain> GetCertChain( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, const std::string& hostname, + bool* cert_matched_sni) = 0; + + // Computes a signature using the private key of the certificate for + // |hostname|. The value in |in| is signed using the algorithm specified by + // |signature_algorithm|, which is an |SSL_SIGN_*| value (as defined in TLS + // 1.3). Implementations can only assume that |in| is valid during the call to + // ComputeTlsSignature - an implementation computing signatures asynchronously + // must copy it if the value to be signed is used outside of this function. + // + // Callers should expect that |callback| might be invoked synchronously. + virtual void ComputeTlsSignature( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + const std::string& hostname, + uint16_t signature_algorithm, + absl::string_view in, + std::unique_ptr<SignatureCallback> callback) = 0; + + // Return the list of TLS signature algorithms that is acceptable by the + // ComputeTlsSignature method. If the entire BoringSSL's default list of + // supported signature algorithms are acceptable, return an empty list. + // + // If returns a non-empty list, ComputeTlsSignature will only be called with a + // algorithm in the list. + virtual absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const = 0; + + class QUIC_EXPORT_PRIVATE DecryptCallback { + public: + DecryptCallback() = default; + virtual ~DecryptCallback() = default; + + virtual void Run(std::vector<uint8_t> plaintext) = 0; + + private: + DecryptCallback(const Callback&) = delete; + DecryptCallback& operator=(const Callback&) = delete; + }; + + // TicketCrypter is an interface for managing encryption and decryption of TLS + // session tickets. A TicketCrypter gets used as an + // SSL_CTX_set_ticket_aead_method in BoringSSL, which has a synchronous + // Encrypt/Seal operation and a potentially asynchronous Decrypt/Open + // operation. This interface allows for ticket decryptions to be performed on + // a remote service. + class QUIC_EXPORT_PRIVATE TicketCrypter { + public: + TicketCrypter() = default; + virtual ~TicketCrypter() = default; + + // MaxOverhead returns the maximum number of bytes of overhead that may get + // added when encrypting the ticket. + virtual size_t MaxOverhead() = 0; + + // Encrypt takes a serialized TLS session ticket in |in|, encrypts it, and + // returns the encrypted ticket. The resulting value must not be larger than + // MaxOverhead bytes larger than |in|. If encryption fails, this method + // returns an empty vector. + // + // If |encryption_key| is nonempty, this method should use it for minting + // TLS resumption tickets. If it is empty, this method may use an + // internally cached encryption key, if available. + virtual std::vector<uint8_t> Encrypt(absl::string_view in, + absl::string_view encryption_key) = 0; + + // Decrypt takes an encrypted ticket |in|, decrypts it, and calls + // |callback->Run| with the decrypted ticket, which must not be larger than + // |in|. If decryption fails, the callback is invoked with an empty + // vector. + virtual void Decrypt(absl::string_view in, + std::unique_ptr<DecryptCallback> callback) = 0; + }; + + // Returns the TicketCrypter used for encrypting and decrypting TLS + // session tickets, or nullptr if that functionality is not supported. The + // TicketCrypter returned (if not nullptr) must be valid for the lifetime of + // the ProofSource, and the caller does not take ownership of said + // TicketCrypter. + virtual TicketCrypter* GetTicketCrypter() = 0; +}; + +// ProofSourceHandleCallback is an interface that contains the callbacks when +// the operations in ProofSourceHandle completes. +// TODO(wub): Consider deprecating ProofSource by moving all functionalities of +// ProofSource into ProofSourceHandle. +class QUIC_EXPORT_PRIVATE ProofSourceHandleCallback { + public: + virtual ~ProofSourceHandleCallback() = default; + + // Called when a ProofSourceHandle::SelectCertificate operation completes. + // |ok| indicates whether the operation was successful. + // |is_sync| indicates whether the operation completed synchronously, i.e. + // whether it is completed before ProofSourceHandle::SelectCertificate + // returned. + // |chain| the certificate chain in leaf-first order. + // |handshake_hints| (optional) handshake hints that can be used by + // SSL_set_handshake_hints. + // |ticket_encryption_key| (optional) encryption key to be used for minting + // TLS resumption tickets. + // |cert_matched_sni| is true if the certificate matched the SNI hostname, + // false if a non-matching default cert was used. + // |delayed_ssl_config| contains SSL configs to be applied on the SSL object. + // + // When called asynchronously(is_sync=false), this method will be responsible + // to continue the handshake from where it left off. + virtual void OnSelectCertificateDone( + bool ok, bool is_sync, const ProofSource::Chain* chain, + absl::string_view handshake_hints, + absl::string_view ticket_encryption_key, bool cert_matched_sni, + QuicDelayedSSLConfig delayed_ssl_config) = 0; + + // Called when a ProofSourceHandle::ComputeSignature operation completes. + virtual void OnComputeSignatureDone( + bool ok, + bool is_sync, + std::string signature, + std::unique_ptr<ProofSource::Details> details) = 0; + + // Return true iff ProofSourceHandle::ComputeSignature won't be called later. + // The handle can use this function to release resources promptly. + virtual bool WillNotCallComputeSignature() const = 0; +}; + +// ProofSourceHandle is an interface by which a TlsServerHandshaker can obtain +// certificate chains and signatures that prove its identity. +// The operations this interface supports are similar to those in ProofSource, +// the main difference is that ProofSourceHandle is per-handshaker, so +// an implementation can have states that are shared by multiple calls on the +// same handle. +// +// A handle object is owned by a TlsServerHandshaker. Since there might be an +// async operation pending when the handle destructs, an implementation must +// ensure when such operations finish, their corresponding callback method won't +// be invoked. +// +// A handle will have at most one async operation pending at a time. +class QUIC_EXPORT_PRIVATE ProofSourceHandle { + public: + virtual ~ProofSourceHandle() = default; + + // Close the handle. Cancel the pending operation, if any. + // Once called, any completion method on |callback()| won't be invoked, and + // future SelectCertificate and ComputeSignature calls should return failure. + virtual void CloseHandle() = 0; + + // Starts a select certificate operation. If the operation is not cancelled + // when it completes, callback()->OnSelectCertificateDone will be invoked. + // + // server_address and client_address should be normalized by the caller before + // sending down to this function. + // + // If the operation is handled synchronously: + // - QUIC_SUCCESS or QUIC_FAILURE will be returned. + // - callback()->OnSelectCertificateDone should be invoked before the function + // returns. + // + // If the operation is handled asynchronously: + // - QUIC_PENDING will be returned. + // - When the operation is done, callback()->OnSelectCertificateDone should be + // invoked. + virtual QuicAsyncStatus SelectCertificate( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + absl::string_view ssl_capabilities, + const std::string& hostname, + absl::string_view client_hello, + const std::string& alpn, + absl::optional<std::string> alps, + const std::vector<uint8_t>& quic_transport_params, + const absl::optional<std::vector<uint8_t>>& early_data_context, + const QuicSSLConfig& ssl_config) = 0; + + // Starts a compute signature operation. If the operation is not cancelled + // when it completes, callback()->OnComputeSignatureDone will be invoked. + // + // See the comments of SelectCertificate for sync vs. async operations. + virtual QuicAsyncStatus ComputeSignature( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + const std::string& hostname, + uint16_t signature_algorithm, + absl::string_view in, + size_t max_signature_size) = 0; + + protected: + // Returns the object that will be notified when an operation completes. + virtual ProofSourceHandleCallback* callback() = 0; + + private: + friend class test::FakeProofSourceHandle; +}; + +// Returns true if |chain| contains a parsable DER-encoded X.509 leaf cert and +// it matches with |key|. +QUIC_EXPORT_PRIVATE bool ValidateCertAndKey( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const CertificatePrivateKey& key); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_H_
diff --git a/quiche/quic/core/crypto/proof_source_x509.cc b/quiche/quic/core/crypto/proof_source_x509.cc new file mode 100644 index 0000000..275efc0 --- /dev/null +++ b/quiche/quic/core/crypto/proof_source_x509.cc
@@ -0,0 +1,149 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/proof_source_x509.h" + +#include <memory> + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/common/quiche_endian.h" + +namespace quic { + +std::unique_ptr<ProofSourceX509> ProofSourceX509::Create( + quiche::QuicheReferenceCountedPointer<Chain> default_chain, + CertificatePrivateKey default_key) { + std::unique_ptr<ProofSourceX509> result(new ProofSourceX509()); + if (!result->AddCertificateChain(default_chain, std::move(default_key))) { + return nullptr; + } + result->default_certificate_ = &result->certificates_.front(); + return result; +} + +void ProofSourceX509::GetProof( + const QuicSocketAddress& /*server_address*/, + const QuicSocketAddress& /*client_address*/, + const std::string& hostname, + const std::string& server_config, + QuicTransportVersion /*transport_version*/, + absl::string_view chlo_hash, + std::unique_ptr<ProofSource::Callback> callback) { + QuicCryptoProof proof; + + size_t payload_size = sizeof(kProofSignatureLabel) + sizeof(uint32_t) + + chlo_hash.size() + server_config.size(); + auto payload = std::make_unique<char[]>(payload_size); + QuicDataWriter payload_writer(payload_size, payload.get(), + quiche::Endianness::HOST_BYTE_ORDER); + bool success = payload_writer.WriteBytes(kProofSignatureLabel, + sizeof(kProofSignatureLabel)) && + payload_writer.WriteUInt32(chlo_hash.size()) && + payload_writer.WriteStringPiece(chlo_hash) && + payload_writer.WriteStringPiece(server_config); + if (!success) { + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } + + Certificate* certificate = GetCertificate(hostname, &proof.cert_matched_sni); + proof.signature = + certificate->key.Sign(absl::string_view(payload.get(), payload_size), + SSL_SIGN_RSA_PSS_RSAE_SHA256); + callback->Run(/*ok=*/!proof.signature.empty(), certificate->chain, proof, + nullptr); +} + +quiche::QuicheReferenceCountedPointer<ProofSource::Chain> +ProofSourceX509::GetCertChain(const QuicSocketAddress& /*server_address*/, + const QuicSocketAddress& /*client_address*/, + const std::string& hostname, + bool* cert_matched_sni) { + return GetCertificate(hostname, cert_matched_sni)->chain; +} + +void ProofSourceX509::ComputeTlsSignature( + const QuicSocketAddress& /*server_address*/, + const QuicSocketAddress& /*client_address*/, + const std::string& hostname, + uint16_t signature_algorithm, + absl::string_view in, + std::unique_ptr<ProofSource::SignatureCallback> callback) { + bool cert_matched_sni; + std::string signature = GetCertificate(hostname, &cert_matched_sni) + ->key.Sign(in, signature_algorithm); + callback->Run(/*ok=*/!signature.empty(), signature, nullptr); +} + +absl::InlinedVector<uint16_t, 8> +ProofSourceX509::SupportedTlsSignatureAlgorithms() const { + // Let ComputeTlsSignature() report an error if a bad signature algorithm is + // requested. + return {}; +} + +ProofSource::TicketCrypter* ProofSourceX509::GetTicketCrypter() { + return nullptr; +} + +bool ProofSourceX509::AddCertificateChain( + quiche::QuicheReferenceCountedPointer<Chain> chain, + CertificatePrivateKey key) { + if (chain->certs.empty()) { + QUIC_BUG(quic_bug_10644_1) << "Empty certificate chain supplied."; + return false; + } + + std::unique_ptr<CertificateView> leaf = + CertificateView::ParseSingleCertificate(chain->certs[0]); + if (leaf == nullptr) { + QUIC_BUG(quic_bug_10644_2) + << "Unable to parse X.509 leaf certificate in the supplied chain."; + return false; + } + if (!key.MatchesPublicKey(*leaf)) { + QUIC_BUG(quic_bug_10644_3) + << "Private key does not match the leaf certificate."; + return false; + } + + certificates_.push_front(Certificate{ + chain, + std::move(key), + }); + Certificate* certificate = &certificates_.front(); + + for (absl::string_view host : leaf->subject_alt_name_domains()) { + certificate_map_[std::string(host)] = certificate; + } + return true; +} + +ProofSourceX509::Certificate* ProofSourceX509::GetCertificate( + const std::string& hostname, bool* cert_matched_sni) const { + auto it = certificate_map_.find(hostname); + if (it != certificate_map_.end()) { + *cert_matched_sni = true; + return it->second; + } + auto dot_pos = hostname.find('.'); + if (dot_pos != std::string::npos) { + std::string wildcard = absl::StrCat("*", hostname.substr(dot_pos)); + it = certificate_map_.find(wildcard); + if (it != certificate_map_.end()) { + *cert_matched_sni = true; + return it->second; + } + } + *cert_matched_sni = false; + return default_certificate_; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/proof_source_x509.h b/quiche/quic/core/crypto/proof_source_x509.h new file mode 100644 index 0000000..107a4c8 --- /dev/null +++ b/quiche/quic/core/crypto/proof_source_x509.h
@@ -0,0 +1,78 @@ +// Copyright 2020 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_X509_H_ +#define QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_X509_H_ + +#include <forward_list> +#include <memory> + +#include "absl/base/attributes.h" +#include "absl/container/node_hash_map.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/platform/api/quic_containers.h" + +namespace quic { + +// ProofSourceX509 accepts X.509 certificates with private keys and picks a +// certificate internally based on its SubjectAltName value. +class QUIC_EXPORT_PRIVATE ProofSourceX509 : public ProofSource { + public: + // Creates a proof source that uses |default_chain| when no SubjectAltName + // value matches. Returns nullptr if |default_chain| is invalid. + static std::unique_ptr<ProofSourceX509> Create( + quiche::QuicheReferenceCountedPointer<Chain> default_chain, + CertificatePrivateKey default_key); + + // ProofSource implementation. + void GetProof(const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + const std::string& hostname, + const std::string& server_config, + QuicTransportVersion transport_version, + absl::string_view chlo_hash, + std::unique_ptr<Callback> callback) override; + quiche::QuicheReferenceCountedPointer<Chain> GetCertChain( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, const std::string& hostname, + bool* cert_matched_sni) override; + void ComputeTlsSignature( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, const std::string& hostname, + uint16_t signature_algorithm, absl::string_view in, + std::unique_ptr<SignatureCallback> callback) override; + absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const override; + TicketCrypter* GetTicketCrypter() override; + + // Adds a certificate chain to the verifier. Returns false if the chain is + // not valid. Newer certificates will override older certificates with the + // same SubjectAltName value. + ABSL_MUST_USE_RESULT bool AddCertificateChain( + quiche::QuicheReferenceCountedPointer<Chain> chain, + CertificatePrivateKey key); + + private: + ProofSourceX509() = default; + + struct QUIC_EXPORT_PRIVATE Certificate { + quiche::QuicheReferenceCountedPointer<Chain> chain; + CertificatePrivateKey key; + }; + + // Looks up certficiate for hostname, returns the default if no certificate is + // found. + Certificate* GetCertificate(const std::string& hostname, + bool* cert_matched_sni) const; + + std::forward_list<Certificate> certificates_; + Certificate* default_certificate_; + absl::node_hash_map<std::string, Certificate*> certificate_map_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_X509_H_
diff --git a/quiche/quic/core/crypto/proof_source_x509_test.cc b/quiche/quic/core/crypto/proof_source_x509_test.cc new file mode 100644 index 0000000..63ba310 --- /dev/null +++ b/quiche/quic/core/crypto/proof_source_x509_test.cc
@@ -0,0 +1,143 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/proof_source_x509.h" + +#include <memory> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/platform/api/quic_expect_bug.h" +#include "quiche/quic/platform/api/quic_ip_address.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/test_certificates.h" +#include "quiche/common/platform/api/quiche_reference_counted.h" + +namespace quic { +namespace test { +namespace { + +quiche::QuicheReferenceCountedPointer<ProofSource::Chain> MakeChain( + absl::string_view cert) { + return quiche::QuicheReferenceCountedPointer<ProofSource::Chain>( + new ProofSource::Chain(std::vector<std::string>{std::string(cert)})); +} + +class ProofSourceX509Test : public QuicTest { + public: + ProofSourceX509Test() + : test_chain_(MakeChain(kTestCertificate)), + wildcard_chain_(MakeChain(kWildcardCertificate)), + test_key_( + CertificatePrivateKey::LoadFromDer(kTestCertificatePrivateKey)), + wildcard_key_(CertificatePrivateKey::LoadFromDer( + kWildcardCertificatePrivateKey)) { + QUICHE_CHECK(test_key_ != nullptr); + QUICHE_CHECK(wildcard_key_ != nullptr); + } + + protected: + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> test_chain_, + wildcard_chain_; + std::unique_ptr<CertificatePrivateKey> test_key_, wildcard_key_; +}; + +TEST_F(ProofSourceX509Test, AddCertificates) { + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + EXPECT_TRUE(proof_source->AddCertificateChain(wildcard_chain_, + std::move(*wildcard_key_))); +} + +TEST_F(ProofSourceX509Test, AddCertificateKeyMismatch) { + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + test_key_ = CertificatePrivateKey::LoadFromDer(kTestCertificatePrivateKey); + EXPECT_QUIC_BUG((void)proof_source->AddCertificateChain( + wildcard_chain_, std::move(*test_key_)), + "Private key does not match"); +} + +TEST_F(ProofSourceX509Test, CertificateSelection) { + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + ASSERT_TRUE(proof_source->AddCertificateChain(wildcard_chain_, + std::move(*wildcard_key_))); + + // Default certificate. + bool cert_matched_sni; + EXPECT_EQ(proof_source + ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(), + "unknown.test", &cert_matched_sni) + ->certs[0], + kTestCertificate); + EXPECT_FALSE(cert_matched_sni); + // mail.example.org is explicitly a SubjectAltName in kTestCertificate. + EXPECT_EQ(proof_source + ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(), + "mail.example.org", &cert_matched_sni) + ->certs[0], + kTestCertificate); + EXPECT_TRUE(cert_matched_sni); + // www.foo.test is in kWildcardCertificate. + EXPECT_EQ(proof_source + ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(), + "www.foo.test", &cert_matched_sni) + ->certs[0], + kWildcardCertificate); + EXPECT_TRUE(cert_matched_sni); + // *.wildcard.test is in kWildcardCertificate. + EXPECT_EQ(proof_source + ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(), + "www.wildcard.test", &cert_matched_sni) + ->certs[0], + kWildcardCertificate); + EXPECT_TRUE(cert_matched_sni); + EXPECT_EQ(proof_source + ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(), + "etc.wildcard.test", &cert_matched_sni) + ->certs[0], + kWildcardCertificate); + EXPECT_TRUE(cert_matched_sni); + // wildcard.test itself is not in kWildcardCertificate. + EXPECT_EQ(proof_source + ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(), + "wildcard.test", &cert_matched_sni) + ->certs[0], + kTestCertificate); + EXPECT_FALSE(cert_matched_sni); +} + +TEST_F(ProofSourceX509Test, TlsSignature) { + class Callback : public ProofSource::SignatureCallback { + public: + void Run(bool ok, + std::string signature, + std::unique_ptr<ProofSource::Details> /*details*/) override { + ASSERT_TRUE(ok); + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(kTestCertificate); + EXPECT_TRUE(view->VerifySignature("Test data", signature, + SSL_SIGN_RSA_PSS_RSAE_SHA256)); + } + }; + + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + + proof_source->ComputeTlsSignature(QuicSocketAddress(), QuicSocketAddress(), + "example.com", SSL_SIGN_RSA_PSS_RSAE_SHA256, + "Test data", std::make_unique<Callback>()); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/proof_verifier.h b/quiche/quic/core/crypto/proof_verifier.h new file mode 100644 index 0000000..3478ca6 --- /dev/null +++ b/quiche/quic/core/crypto/proof_verifier.h
@@ -0,0 +1,128 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_PROOF_VERIFIER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_PROOF_VERIFIER_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// ProofVerifyDetails is an abstract class that acts as a container for any +// implementation specific details that a ProofVerifier wishes to return. These +// details are saved in the CachedState for the origin in question. +class QUIC_EXPORT_PRIVATE ProofVerifyDetails { + public: + virtual ~ProofVerifyDetails() {} + + // Returns an new ProofVerifyDetails object with the same contents + // as this one. + virtual ProofVerifyDetails* Clone() const = 0; +}; + +// ProofVerifyContext is an abstract class that acts as a container for any +// implementation specific context that a ProofVerifier needs. +class QUIC_EXPORT_PRIVATE ProofVerifyContext { + public: + virtual ~ProofVerifyContext() {} +}; + +// ProofVerifierCallback provides a generic mechanism for a ProofVerifier to +// call back after an asynchronous verification. +class QUIC_EXPORT_PRIVATE ProofVerifierCallback { + public: + virtual ~ProofVerifierCallback() {} + + // Run is called on the original thread to mark the completion of an + // asynchonous verification. If |ok| is true then the certificate is valid + // and |error_details| is unused. Otherwise, |error_details| contains a + // description of the error. |details| contains implementation-specific + // details of the verification. |Run| may take ownership of |details| by + // calling |release| on it. + virtual void Run(bool ok, + const std::string& error_details, + std::unique_ptr<ProofVerifyDetails>* details) = 0; +}; + +// A ProofVerifier checks the signature on a server config, and the certificate +// chain that backs the public key. +class QUIC_EXPORT_PRIVATE ProofVerifier { + public: + virtual ~ProofVerifier() {} + + // VerifyProof checks that |signature| is a valid signature of + // |server_config| by the public key in the leaf certificate of |certs|, and + // that |certs| is a valid chain for |hostname|. On success, it returns + // QUIC_SUCCESS. On failure, it returns QUIC_FAILURE and sets |*error_details| + // to a description of the problem. In either case it may set |*details|, + // which the caller takes ownership of. + // + // |context| specifies an implementation specific struct (which may be nullptr + // for some implementations) that provides useful information for the + // verifier, e.g. logging handles. + // + // This function may also return QUIC_PENDING, in which case the ProofVerifier + // will call back, on the original thread, via |callback| when complete. + // + // The signature uses SHA-256 as the hash function and PSS padding in the + // case of RSA. + virtual QuicAsyncStatus VerifyProof( + const std::string& hostname, + const uint16_t port, + const std::string& server_config, + QuicTransportVersion transport_version, + absl::string_view chlo_hash, + const std::vector<std::string>& certs, + const std::string& cert_sct, + const std::string& signature, + const ProofVerifyContext* context, + std::string* error_details, + std::unique_ptr<ProofVerifyDetails>* details, + std::unique_ptr<ProofVerifierCallback> callback) = 0; + + // VerifyCertChain checks that |certs| is a valid chain for |hostname|. On + // success, it returns QUIC_SUCCESS. On failure, it returns QUIC_FAILURE and + // sets |*error_details| to a description of the problem. In either case it + // may set |*details|, which the caller takes ownership of. + // + // |context| specifies an implementation specific struct (which may be nullptr + // for some implementations) that provides useful information for the + // verifier, e.g. logging handles. + // + // If certificate verification fails, a TLS alert will be sent when closing + // the connection. This alert defaults to certificate_unknown. By setting + // |*out_alert|, a different alert can be sent to provide a more specific + // reason why verification failed. + // + // This function may also return QUIC_PENDING, in which case the ProofVerifier + // will call back, on the original thread, via |callback| when complete. + // In this case, the ProofVerifier will take ownership of |callback|. + virtual QuicAsyncStatus VerifyCertChain( + const std::string& hostname, + const uint16_t port, + const std::vector<std::string>& certs, + const std::string& ocsp_response, + const std::string& cert_sct, + const ProofVerifyContext* context, + std::string* error_details, + std::unique_ptr<ProofVerifyDetails>* details, + uint8_t* out_alert, + std::unique_ptr<ProofVerifierCallback> callback) = 0; + + // Returns a ProofVerifyContext instance which can be use for subsequent + // verifications. Applications may chose create a different context and + // supply it for verifications instead. + virtual std::unique_ptr<ProofVerifyContext> CreateDefaultContext() = 0; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_PROOF_VERIFIER_H_
diff --git a/quiche/quic/core/crypto/quic_client_session_cache.cc b/quiche/quic/core/crypto/quic_client_session_cache.cc new file mode 100644 index 0000000..32f115d --- /dev/null +++ b/quiche/quic/core/crypto/quic_client_session_cache.cc
@@ -0,0 +1,173 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_client_session_cache.h" + +#include "quiche/quic/core/quic_clock.h" + +namespace quic { + +namespace { + +const size_t kDefaultMaxEntries = 1024; +// Returns false if the SSL |session| doesn't exist or it is expired at |now|. +bool IsValid(SSL_SESSION* session, uint64_t now) { + if (!session) return false; + + // now_u64 may be slightly behind because of differences in how + // time is calculated at this layer versus BoringSSL. + // Add a second of wiggle room to account for this. + return !(now + 1 < SSL_SESSION_get_time(session) || + now >= SSL_SESSION_get_time(session) + + SSL_SESSION_get_timeout(session)); +} + +bool DoApplicationStatesMatch(const ApplicationState* state, + ApplicationState* other) { + if ((state && !other) || (!state && other)) return false; + if ((!state && !other) || *state == *other) return true; + return false; +} + +} // namespace + +QuicClientSessionCache::QuicClientSessionCache() + : QuicClientSessionCache(kDefaultMaxEntries) {} + +QuicClientSessionCache::QuicClientSessionCache(size_t max_entries) + : cache_(max_entries) {} + +QuicClientSessionCache::~QuicClientSessionCache() { Clear(); } + +void QuicClientSessionCache::Insert(const QuicServerId& server_id, + bssl::UniquePtr<SSL_SESSION> session, + const TransportParameters& params, + const ApplicationState* application_state) { + QUICHE_DCHECK(session) << "TLS session is not inserted into client cache."; + auto iter = cache_.Lookup(server_id); + if (iter == cache_.end()) { + CreateAndInsertEntry(server_id, std::move(session), params, + application_state); + return; + } + + QUICHE_DCHECK(iter->second->params); + // The states are both the same, so only need to insert sessions. + if (params == *iter->second->params && + DoApplicationStatesMatch(application_state, + iter->second->application_state.get())) { + iter->second->PushSession(std::move(session)); + return; + } + // Erase the existing entry because this Insert call must come from a + // different QUIC session. + cache_.Erase(iter); + CreateAndInsertEntry(server_id, std::move(session), params, + application_state); +} + +std::unique_ptr<QuicResumptionState> QuicClientSessionCache::Lookup( + const QuicServerId& server_id, QuicWallTime now, const SSL_CTX* /*ctx*/) { + auto iter = cache_.Lookup(server_id); + if (iter == cache_.end()) return nullptr; + + if (!IsValid(iter->second->PeekSession(), now.ToUNIXSeconds())) { + QUIC_DLOG(INFO) << "TLS Session expired for host:" << server_id.host(); + cache_.Erase(iter); + return nullptr; + } + auto state = std::make_unique<QuicResumptionState>(); + state->tls_session = iter->second->PopSession(); + if (iter->second->params != nullptr) { + state->transport_params = + std::make_unique<TransportParameters>(*iter->second->params); + } + if (iter->second->application_state != nullptr) { + state->application_state = + std::make_unique<ApplicationState>(*iter->second->application_state); + } + if (!iter->second->token.empty()) { + state->token = iter->second->token; + // Clear token after use. + iter->second->token.clear(); + } + + return state; +} + +void QuicClientSessionCache::ClearEarlyData(const QuicServerId& server_id) { + auto iter = cache_.Lookup(server_id); + if (iter == cache_.end()) return; + for (auto& session : iter->second->sessions) { + if (session) { + QUIC_DLOG(INFO) << "Clear early data for for host: " << server_id.host(); + session.reset(SSL_SESSION_copy_without_early_data(session.get())); + } + } +} + +void QuicClientSessionCache::OnNewTokenReceived(const QuicServerId& server_id, + absl::string_view token) { + if (token.empty()) { + return; + } + auto iter = cache_.Lookup(server_id); + if (iter == cache_.end()) { + return; + } + iter->second->token = std::string(token); +} + +void QuicClientSessionCache::RemoveExpiredEntries(QuicWallTime now) { + auto iter = cache_.begin(); + while (iter != cache_.end()) { + if (!IsValid(iter->second->PeekSession(), now.ToUNIXSeconds())) { + iter = cache_.Erase(iter); + } else { + ++iter; + } + } +} + +void QuicClientSessionCache::Clear() { cache_.Clear(); } + +void QuicClientSessionCache::CreateAndInsertEntry( + const QuicServerId& server_id, bssl::UniquePtr<SSL_SESSION> session, + const TransportParameters& params, + const ApplicationState* application_state) { + auto entry = std::make_unique<Entry>(); + entry->PushSession(std::move(session)); + entry->params = std::make_unique<TransportParameters>(params); + if (application_state) { + entry->application_state = + std::make_unique<ApplicationState>(*application_state); + } + cache_.Insert(server_id, std::move(entry)); +} + +QuicClientSessionCache::Entry::Entry() = default; +QuicClientSessionCache::Entry::Entry(Entry&&) = default; +QuicClientSessionCache::Entry::~Entry() = default; + +void QuicClientSessionCache::Entry::PushSession( + bssl::UniquePtr<SSL_SESSION> session) { + if (sessions[0] != nullptr) { + sessions[1] = std::move(sessions[0]); + } + sessions[0] = std::move(session); +} + +bssl::UniquePtr<SSL_SESSION> QuicClientSessionCache::Entry::PopSession() { + if (sessions[0] == nullptr) return nullptr; + bssl::UniquePtr<SSL_SESSION> session = std::move(sessions[0]); + sessions[0] = std::move(sessions[1]); + sessions[1] = nullptr; + return session; +} + +SSL_SESSION* QuicClientSessionCache::Entry::PeekSession() { + return sessions[0].get(); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_client_session_cache.h b/quiche/quic/core/crypto/quic_client_session_cache.h new file mode 100644 index 0000000..e568db6 --- /dev/null +++ b/quiche/quic/core/crypto/quic_client_session_cache.h
@@ -0,0 +1,82 @@ +// Copyright (c) 2021 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_CLIENT_SESSION_CACHE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_CLIENT_SESSION_CACHE_H_ + +#include <memory> + +#include "quiche/quic/core/crypto/quic_crypto_client_config.h" +#include "quiche/quic/core/quic_lru_cache.h" +#include "quiche/quic/core/quic_server_id.h" + +namespace quic { + +namespace test { +class QuicClientSessionCachePeer; +} // namespace test + +// QuicClientSessionCache maps from QuicServerId to information used to resume +// TLS sessions for that server. +class QUIC_EXPORT_PRIVATE QuicClientSessionCache : public SessionCache { + public: + QuicClientSessionCache(); + explicit QuicClientSessionCache(size_t max_entries); + ~QuicClientSessionCache() override; + + void Insert(const QuicServerId& server_id, + bssl::UniquePtr<SSL_SESSION> session, + const TransportParameters& params, + const ApplicationState* application_state) override; + + std::unique_ptr<QuicResumptionState> Lookup(const QuicServerId& server_id, + QuicWallTime now, + const SSL_CTX* ctx) override; + + void ClearEarlyData(const QuicServerId& server_id) override; + + void OnNewTokenReceived(const QuicServerId& server_id, + absl::string_view token) override; + + void RemoveExpiredEntries(QuicWallTime now) override; + + void Clear() override; + + size_t size() const { return cache_.Size(); } + + private: + friend class test::QuicClientSessionCachePeer; + + struct QUIC_EXPORT_PRIVATE Entry { + Entry(); + Entry(Entry&&); + ~Entry(); + + // Adds a new |session| onto sessions, dropping the oldest one if two are + // already stored. + void PushSession(bssl::UniquePtr<SSL_SESSION> session); + + // Retrieves the latest session from the entry, meanwhile removing it. + bssl::UniquePtr<SSL_SESSION> PopSession(); + + SSL_SESSION* PeekSession(); + + bssl::UniquePtr<SSL_SESSION> sessions[2]; + std::unique_ptr<TransportParameters> params; + std::unique_ptr<ApplicationState> application_state; + std::string token; // An opaque string received in NEW_TOKEN frame. + }; + + // Creates a new entry and insert into |cache_|. + void CreateAndInsertEntry(const QuicServerId& server_id, + bssl::UniquePtr<SSL_SESSION> session, + const TransportParameters& params, + const ApplicationState* application_state); + + QuicLRUCache<QuicServerId, Entry, QuicServerIdHash> cache_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_CLIENT_SESSION_CACHE_H_
diff --git a/quiche/quic/core/crypto/quic_client_session_cache_test.cc b/quiche/quic/core/crypto/quic_client_session_cache_test.cc new file mode 100644 index 0000000..880770a --- /dev/null +++ b/quiche/quic/core/crypto/quic_client_session_cache_test.cc
@@ -0,0 +1,440 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_client_session_cache.h" + +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/mock_clock.h" +#include "quiche/common/quiche_text_utils.h" + +namespace quic { +namespace test { +namespace { + +const QuicTime::Delta kTimeout = QuicTime::Delta::FromSeconds(1000); +const QuicVersionLabel kFakeVersionLabel = 0x01234567; +const QuicVersionLabel kFakeVersionLabel2 = 0x89ABCDEF; +const uint64_t kFakeIdleTimeoutMilliseconds = 12012; +const uint8_t kFakeStatelessResetTokenData[16] = { + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F}; +const uint64_t kFakeMaxPacketSize = 9001; +const uint64_t kFakeInitialMaxData = 101; +const bool kFakeDisableMigration = true; +const auto kCustomParameter1 = + static_cast<TransportParameters::TransportParameterId>(0xffcd); +const char* kCustomParameter1Value = "foo"; +const auto kCustomParameter2 = + static_cast<TransportParameters::TransportParameterId>(0xff34); +const char* kCustomParameter2Value = "bar"; + +std::vector<uint8_t> CreateFakeStatelessResetToken() { + return std::vector<uint8_t>( + kFakeStatelessResetTokenData, + kFakeStatelessResetTokenData + sizeof(kFakeStatelessResetTokenData)); +} + +TransportParameters::LegacyVersionInformation +CreateFakeLegacyVersionInformation() { + TransportParameters::LegacyVersionInformation legacy_version_information; + legacy_version_information.version = kFakeVersionLabel; + legacy_version_information.supported_versions.push_back(kFakeVersionLabel); + legacy_version_information.supported_versions.push_back(kFakeVersionLabel2); + return legacy_version_information; +} + +TransportParameters::VersionInformation CreateFakeVersionInformation() { + TransportParameters::VersionInformation version_information; + version_information.chosen_version = kFakeVersionLabel; + version_information.other_versions.push_back(kFakeVersionLabel); + return version_information; +} + +// Make a TransportParameters that has a few fields set to help test comparison. +std::unique_ptr<TransportParameters> MakeFakeTransportParams() { + auto params = std::make_unique<TransportParameters>(); + params->perspective = Perspective::IS_CLIENT; + params->legacy_version_information = CreateFakeLegacyVersionInformation(); + params->version_information = CreateFakeVersionInformation(); + params->max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + params->stateless_reset_token = CreateFakeStatelessResetToken(); + params->max_udp_payload_size.set_value(kFakeMaxPacketSize); + params->initial_max_data.set_value(kFakeInitialMaxData); + params->disable_active_migration = kFakeDisableMigration; + params->custom_parameters[kCustomParameter1] = kCustomParameter1Value; + params->custom_parameters[kCustomParameter2] = kCustomParameter2Value; + return params; +} + +// Generated by running TlsClientHandshakerTest.ZeroRttResumption and in +// TlsClientHandshaker::InsertSession calling SSL_SESSION_to_bytes to serialize +// the received 0-RTT capable ticket. +static const char kCachedSession[] = + "30820ad7020101020203040402130104206594ce84e61a866b56163c4ba09079aebf1d4f" + "6cbcbd38dc9d7066a38a76c9cf0420ec9062063582a4cc0a44f9ff93256a195153ba6032" + "0cf3c9189990932d838adaa10602046196f7b9a205020302a300a382039f3082039b3082" + "0183a00302010202021001300d06092a864886f70d010105050030623111300f06035504" + "030c08426f677573204941310b300906035504080c024d41310b30090603550406130255" + "533121301f06092a864886f70d0109011612626f67757340626f6775732d69612e636f6d" + "3110300e060355040a0c07426f6775734941301e170d3231303132383136323030315a17" + "0d3331303132363136323030315a3069311d301b06035504030c14746573745f6563632e" + "6578616d706c652e636f6d310b300906035504080c024d41310b30090603550406130255" + "53311e301c06092a864886f70d010901160f626f67757340626f6775732e636f6d310e30" + "0c060355040a0c05426f6775733059301306072a8648ce3d020106082a8648ce3d030107" + "034200041ba5e2b6f24e64990b9f24ae6d23473d8c77fbcfb7f554f36559529a69a57170" + "a10a81b7fe4a36ebf37b0a8c5e467a8443d8b8c002892aa5c1194bd843f42c9aa31f301d" + "301b0603551d11041430128210746573742e6578616d706c652e636f6d300d06092a8648" + "86f70d0101050500038202010019921d54ac06948763d609215f64f5d6540e3da886c6c9" + "61bc737a437719b4621416ef1229f39282d7d3234e1a5d57535473066233bd246eec8e96" + "1e0633cf4fe014c800e62599981820ec33d92e74ded0fa2953db1d81e19cb6890b6305b6" + "3ede8d3e9fcf3c09f3f57283acf08aa57be4ee9a68d00bb3e2ded5920c619b5d83e5194a" + "adb77ae5d61ed3e0a5670f0ae61cc3197329f0e71e3364dcab0405e9e4a6646adef8f022" + "6415ec16c8046307b1769029fe780bd576114dde2fa9b4a32aa70bc436549a24ee4907a9" + "045f6457ce8dfd8d62cc65315afe798ae1a948eefd70b035d415e73569c48fb20085de1a" + "87de039e6b0b9a5fcb4069df27f3a7a1409e72d1ac739c72f29ef786134207e61c79855f" + "c22e3ee5f6ad59a7b1ff0f18d79776f1c95efaebbebe381664132a58a1e7ff689945b7e0" + "88634b0872feeefbf6be020884b994c6a7ff435f2b3f609077ff97cb509cfa17ff479b34" + "e633e4b5bc46b20c5f27c80a2e2943f795a928acd5a3fc43c3af8425ad600c048b41d87e" + "6361bc72fc4e5e44680a3d325674ba6ffa760d2fc7d9e4847a8e0dd9d35a543324e18b94" + "2d42af6391ed1dd54a39e3f4a4c6b32486eb4ba72815dbd89c56fc053743a0b0483ce676" + "15defce6800c629b99d0cbc56da162487f475b7c246099eaf1e6d10a022b2f49c6af1da3" + "e8ed66096f267c4a76976b9572db7456ef90278330a4020400aa81b60481b3494e534543" + "55524500f3439e548c21d2ad6e5634cc1cc0045730819702010102020304040213010400" + "0420ec9062063582a4cc0a44f9ff93256a195153ba60320cf3c9189990932d838adaa106" + "02046196f7b9a205020302a300a4020400b20302011db5060404130800cdb807020500ff" + "ffffffb9050203093a80ba0404026833bb030101ffbc23042100d27d985bfce04833f02d" + "38366b219f4def42bc4ba1b01844d1778db11731487dbd020400be020400b20302011db3" + "8205da308205d6308203bea00302010202021000300d06092a864886f70d010105050030" + "62310b3009060355040613025553310b300906035504080c024d413110300e060355040a" + "0c07426f67757343413111300f06035504030c08426f6775732043413121301f06092a86" + "4886f70d0109011612626f67757340626f6775732d63612e636f6d3020170d3231303132" + "383136313935385a180f32303730303531313136313935385a30623111300f0603550403" + "0c08426f677573204941310b300906035504080c024d41310b3009060355040613025553" + "3121301f06092a864886f70d0109011612626f67757340626f6775732d69612e636f6d31" + "10300e060355040a0c07426f677573494130820222300d06092a864886f70d0101010500" + "0382020f003082020a028202010096c03a0ffc61bcedcd5ec9bf6f848b8a066b43f08377" + "3af518a6a0044f22e666e24d2ae741954e344302c4be04612185bd53bcd848eb322bf900" + "724eb0848047d647033ffbddb00f01d1de7c1cdb684f83c9bf5fd18ff60afad5a53b0d7d" + "2c2a50abc38df019cd7f50194d05bc4597a1ef8570ea04069a2c36d74496af126573ca18" + "8e470009b56250fadf2a04e837ee3837b36b1f08b7a0cfe2533d05f26484ce4e30203d01" + "517fffd3da63d0341079ddce16e9ab4dbf9d4049e5cc52326031e645dd682fe6220d9e0e" + "95451f5a82f3e1720dc13e8499466426a0bdbea9f6a76b3c9228dd3c79ab4dcc4c145ef0" + "e78d1ee8bfd4650692d7e28a54bed809d8f7b37fe24c586be59cc46638531cb291c8c156" + "8f08d67e768e51563e95a639c1f138b275ffad6a6a2a042ba9e26ad63c2ce63b600013f0" + "a6f0703ee51c4f457f7bab0391c2fc4c5bb3213742c9cf9941bff68cc2e1cc96139d35ed" + "1885244ddde0bf658416c486701841b81f7b17503d08c59a4db08a2a80755e007aa3b6c7" + "eadcaa9e07c8325f3689f100de23970b12c9d9f6d0a8fb35ba0fd75c64410318db4a13ac" + "3972ad16cdf6408af37013c7bcd7c42f20d6d04c3e39436c7531e8dafa219dd04b784ef0" + "3c70ee5a4782b33cafa925aa3deca62a14aed704f179b932efabc2b0c5c15a8a99bfc9e6" + "189dce7da50ea303594b6af9c933dd54b6e9d17c472d0203010001a38193308190300f06" + "03551d130101ff040530030101ff301d0603551d0e041604141a98e80029a80992b7e5e0" + "068ab9b3486cd839d6301f0603551d23041830168014780beeefe2fa419c48a438bdb30b" + "e37ef0b7a94e300b0603551d0f0404030202a430130603551d25040c300a06082b060105" + "05070301301b0603551d11041430128207426f67757343418207426f6775734941300d06" + "092a864886f70d010105050003820201009e822ed8064b1aabaddf1340010ea147f68c06" + "5a5a599ea305349f1b0e545a00817d6e55c7bf85560fab429ca72186c4d520b52f5cc121" + "abd068b06f3111494431d2522efa54642f907059e7db80b73bb5ecf621377195b8700bba" + "df798cece8c67a9571548d0e6592e81ae5d934877cb170aef18d3b97f635600fe0890d98" + "f88b33fe3d1fd34c1c915beae4e5c0b133f476c40b21d220f16ce9cdd9e8f97a36a31723" + "68875f052c9271648d9cb54687c6fdc3ea96f2908003bc5e5e79de00a21da7b8429f8b08" + "af4c4d34641e386d72eabf5f01f106363f2ffd18969bf0bb9a4d17627c6427ff772c4308" + "83c276feef5fc6dba9582c22fdbe9df7e8dfca375695f028ed588df54f3c86462dbf4c07" + "91d80ca738988a1419c86bb4dd8d738b746921f01f39422e5ffd488b6f00195b996e6392" + "3a820a32cd78b5989f339c0fcf4f269103964a30a16347d0ffdc8df1f3653ddc1515fa09" + "22c7aef1af1fbcb23e93ae7622ab1ee11fcfa98319bad4c37c091cad46bd0337b3cc78b5" + "5b9f1ea7994acc1f89c49a0b4cb540d2137e266fd43e56a9b5b778217b6f77df530e1eaf" + "b3417262b5ddb86d3c6c5ac51e3f326c650dcc2434473973b7182c66220d1f3871bde7ee" + "47d3f359d3d4c5bdd61baa684c03db4c75f9d6690c9e6e3abe6eaf5fa2c33c4daf26b373" + "d85a1e8a7d671ac4a0a97b14e36e81280de4593bbb12da7695b5060404130800cdb60301" + "0100b70402020403b807020500ffffffffb9050203093a80ba0404026833bb030101ffbd" + "020400be020400"; + +class QuicClientSessionCacheTest : public QuicTest { + public: + QuicClientSessionCacheTest() : ssl_ctx_(SSL_CTX_new(TLS_method())) { + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + } + + protected: + bssl::UniquePtr<SSL_SESSION> NewSSLSession() { + std::string cached_session = + absl::HexStringToBytes(absl::string_view(kCachedSession)); + SSL_SESSION* session = SSL_SESSION_from_bytes( + reinterpret_cast<const uint8_t*>(cached_session.data()), + cached_session.size(), ssl_ctx_.get()); + QUICHE_DCHECK(session); + return bssl::UniquePtr<SSL_SESSION>(session); + } + + bssl::UniquePtr<SSL_SESSION> MakeTestSession( + QuicTime::Delta timeout = kTimeout) { + bssl::UniquePtr<SSL_SESSION> session = NewSSLSession(); + SSL_SESSION_set_time(session.get(), clock_.WallNow().ToUNIXSeconds()); + SSL_SESSION_set_timeout(session.get(), timeout.ToSeconds()); + return session; + } + + bssl::UniquePtr<SSL_CTX> ssl_ctx_; + MockClock clock_; +}; + +// Tests that simple insertion and lookup work correctly. +TEST_F(QuicClientSessionCacheTest, SingleSession) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + + auto params2 = MakeFakeTransportParams(); + auto session2 = MakeTestSession(); + SSL_SESSION* unowned2 = session2.get(); + QuicServerId id2("b.com", 443); + + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(nullptr, cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(0u, cache.size()); + + cache.Insert(id1, std::move(session), *params, nullptr); + EXPECT_EQ(1u, cache.size()); + EXPECT_EQ( + *params, + *(cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())->transport_params)); + EXPECT_EQ(nullptr, cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())); + // No session is available for id1, even though the entry exists. + EXPECT_EQ(1u, cache.size()); + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); + // Lookup() will trigger a deletion of invalid entry. + EXPECT_EQ(0u, cache.size()); + + auto session3 = MakeTestSession(); + SSL_SESSION* unowned3 = session3.get(); + QuicServerId id3("c.com", 443); + cache.Insert(id3, std::move(session3), *params, nullptr); + cache.Insert(id2, std::move(session2), *params2, nullptr); + EXPECT_EQ(2u, cache.size()); + EXPECT_EQ( + unowned2, + cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + EXPECT_EQ( + unowned3, + cache.Lookup(id3, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + + // Verify that the cache is cleared after Lookups. + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(nullptr, cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(nullptr, cache.Lookup(id3, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(0u, cache.size()); +} + +TEST_F(QuicClientSessionCacheTest, MultipleSessions) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + auto session2 = MakeTestSession(); + SSL_SESSION* unowned2 = session2.get(); + auto session3 = MakeTestSession(); + SSL_SESSION* unowned3 = session3.get(); + + cache.Insert(id1, std::move(session), *params, nullptr); + cache.Insert(id1, std::move(session2), *params, nullptr); + cache.Insert(id1, std::move(session3), *params, nullptr); + // The latest session is popped first. + EXPECT_EQ( + unowned3, + cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + EXPECT_EQ( + unowned2, + cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + // Only two sessions are cached. + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); +} + +// Test that when a different TransportParameter is inserted for +// the same server id, the existing entry is removed. +TEST_F(QuicClientSessionCacheTest, DifferentTransportParams) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + auto session2 = MakeTestSession(); + auto session3 = MakeTestSession(); + SSL_SESSION* unowned3 = session3.get(); + + cache.Insert(id1, std::move(session), *params, nullptr); + cache.Insert(id1, std::move(session2), *params, nullptr); + // tweak the transport parameters a little bit. + params->perspective = Perspective::IS_SERVER; + cache.Insert(id1, std::move(session3), *params, nullptr); + auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()); + EXPECT_EQ(unowned3, resumption_state->tls_session.get()); + EXPECT_EQ(*params.get(), *resumption_state->transport_params); + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); +} + +TEST_F(QuicClientSessionCacheTest, DifferentApplicationState) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + auto session2 = MakeTestSession(); + auto session3 = MakeTestSession(); + SSL_SESSION* unowned3 = session3.get(); + ApplicationState state; + state.push_back('a'); + + cache.Insert(id1, std::move(session), *params, &state); + cache.Insert(id1, std::move(session2), *params, &state); + cache.Insert(id1, std::move(session3), *params, nullptr); + auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()); + EXPECT_EQ(unowned3, resumption_state->tls_session.get()); + EXPECT_EQ(nullptr, resumption_state->application_state); + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); +} + +TEST_F(QuicClientSessionCacheTest, BothStatesDifferent) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + auto session2 = MakeTestSession(); + auto session3 = MakeTestSession(); + SSL_SESSION* unowned3 = session3.get(); + ApplicationState state; + state.push_back('a'); + + cache.Insert(id1, std::move(session), *params, &state); + cache.Insert(id1, std::move(session2), *params, &state); + params->perspective = Perspective::IS_SERVER; + cache.Insert(id1, std::move(session3), *params, nullptr); + auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()); + EXPECT_EQ(unowned3, resumption_state->tls_session.get()); + EXPECT_EQ(*params.get(), *resumption_state->transport_params); + EXPECT_EQ(nullptr, resumption_state->application_state); + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); +} + +// When the size limit is exceeded, the oldest entry should be erased. +TEST_F(QuicClientSessionCacheTest, SizeLimit) { + QuicClientSessionCache cache(2); + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + + auto session2 = MakeTestSession(); + SSL_SESSION* unowned2 = session2.get(); + QuicServerId id2("b.com", 443); + + auto session3 = MakeTestSession(); + SSL_SESSION* unowned3 = session3.get(); + QuicServerId id3("c.com", 443); + + cache.Insert(id1, std::move(session), *params, nullptr); + cache.Insert(id2, std::move(session2), *params, nullptr); + cache.Insert(id3, std::move(session3), *params, nullptr); + + EXPECT_EQ(2u, cache.size()); + EXPECT_EQ( + unowned2, + cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + EXPECT_EQ( + unowned3, + cache.Lookup(id3, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); +} + +TEST_F(QuicClientSessionCacheTest, ClearEarlyData) { + QuicClientSessionCache cache; + SSL_CTX_set_early_data_enabled(ssl_ctx_.get(), 1); + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + auto session2 = MakeTestSession(); + + EXPECT_TRUE(SSL_SESSION_early_data_capable(session.get())); + EXPECT_TRUE(SSL_SESSION_early_data_capable(session2.get())); + + cache.Insert(id1, std::move(session), *params, nullptr); + cache.Insert(id1, std::move(session2), *params, nullptr); + + cache.ClearEarlyData(id1); + + auto resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()); + EXPECT_FALSE( + SSL_SESSION_early_data_capable(resumption_state->tls_session.get())); + resumption_state = cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get()); + EXPECT_FALSE( + SSL_SESSION_early_data_capable(resumption_state->tls_session.get())); + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); +} + +// Expired session isn't considered valid and nullptr will be returned upon +// Lookup. +TEST_F(QuicClientSessionCacheTest, Expiration) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + QuicServerId id1("a.com", 443); + + auto session2 = MakeTestSession(3 * kTimeout); + SSL_SESSION* unowned2 = session2.get(); + QuicServerId id2("b.com", 443); + + cache.Insert(id1, std::move(session), *params, nullptr); + cache.Insert(id2, std::move(session2), *params, nullptr); + + EXPECT_EQ(2u, cache.size()); + // Expire the session. + clock_.AdvanceTime(kTimeout * 2); + // The entry has not been removed yet. + EXPECT_EQ(2u, cache.size()); + + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(1u, cache.size()); + EXPECT_EQ( + unowned2, + cache.Lookup(id2, clock_.WallNow(), ssl_ctx_.get())->tls_session.get()); + EXPECT_EQ(1u, cache.size()); +} + +TEST_F(QuicClientSessionCacheTest, RemoveExpiredEntriesAndClear) { + QuicClientSessionCache cache; + + auto params = MakeFakeTransportParams(); + auto session = MakeTestSession(); + quic::QuicServerId id1("a.com", 443); + + auto session2 = MakeTestSession(3 * kTimeout); + quic::QuicServerId id2("b.com", 443); + + cache.Insert(id1, std::move(session), *params, nullptr); + cache.Insert(id2, std::move(session2), *params, nullptr); + + EXPECT_EQ(2u, cache.size()); + // Expire the session. + clock_.AdvanceTime(kTimeout * 2); + // The entry has not been removed yet. + EXPECT_EQ(2u, cache.size()); + + // Flush expired sessions. + cache.RemoveExpiredEntries(clock_.WallNow()); + + // session is expired and should be flushed. + EXPECT_EQ(nullptr, cache.Lookup(id1, clock_.WallNow(), ssl_ctx_.get())); + EXPECT_EQ(1u, cache.size()); + + cache.Clear(); + EXPECT_EQ(0u, cache.size()); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_compressed_certs_cache.cc b/quiche/quic/core/crypto/quic_compressed_certs_cache.cc new file mode 100644 index 0000000..9627aba --- /dev/null +++ b/quiche/quic/core/crypto/quic_compressed_certs_cache.cc
@@ -0,0 +1,119 @@ +// Copyright 2016 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 <string> + +#include "quiche/quic/core/crypto/quic_compressed_certs_cache.h" + +namespace quic { + +namespace { + +// Inline helper function for extending a 64-bit |seed| in-place with a 64-bit +// |value|. Based on Boost's hash_combine function. +inline void hash_combine(uint64_t* seed, const uint64_t& val) { + (*seed) ^= val + 0x9e3779b9 + ((*seed) << 6) + ((*seed) >> 2); +} + +} // namespace + +const size_t QuicCompressedCertsCache::kQuicCompressedCertsCacheSize = 225; + +QuicCompressedCertsCache::UncompressedCerts::UncompressedCerts() + : chain(nullptr), + client_cached_cert_hashes(nullptr) {} + +QuicCompressedCertsCache::UncompressedCerts::UncompressedCerts( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string* client_cached_cert_hashes) + : chain(chain), client_cached_cert_hashes(client_cached_cert_hashes) {} + +QuicCompressedCertsCache::UncompressedCerts::~UncompressedCerts() {} + +QuicCompressedCertsCache::CachedCerts::CachedCerts() {} + +QuicCompressedCertsCache::CachedCerts::CachedCerts( + const UncompressedCerts& uncompressed_certs, + const std::string& compressed_cert) + : chain_(uncompressed_certs.chain), + client_cached_cert_hashes_(*uncompressed_certs.client_cached_cert_hashes), + compressed_cert_(compressed_cert) {} + +QuicCompressedCertsCache::CachedCerts::CachedCerts(const CachedCerts& other) = + default; + +QuicCompressedCertsCache::CachedCerts::~CachedCerts() {} + +bool QuicCompressedCertsCache::CachedCerts::MatchesUncompressedCerts( + const UncompressedCerts& uncompressed_certs) const { + return (client_cached_cert_hashes_ == + *uncompressed_certs.client_cached_cert_hashes && + chain_ == uncompressed_certs.chain); +} + +const std::string* QuicCompressedCertsCache::CachedCerts::compressed_cert() + const { + return &compressed_cert_; +} + +QuicCompressedCertsCache::QuicCompressedCertsCache(int64_t max_num_certs) + : certs_cache_(max_num_certs) {} + +QuicCompressedCertsCache::~QuicCompressedCertsCache() { + // Underlying cache must be cleared before destruction. + certs_cache_.Clear(); +} + +const std::string* QuicCompressedCertsCache::GetCompressedCert( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& client_cached_cert_hashes) { + UncompressedCerts uncompressed_certs(chain, &client_cached_cert_hashes); + + uint64_t key = ComputeUncompressedCertsHash(uncompressed_certs); + + CachedCerts* cached_value = nullptr; + auto iter = certs_cache_.Lookup(key); + if (iter != certs_cache_.end()) { + cached_value = iter->second.get(); + } + if (cached_value != nullptr && + cached_value->MatchesUncompressedCerts(uncompressed_certs)) { + return cached_value->compressed_cert(); + } + return nullptr; +} + +void QuicCompressedCertsCache::Insert( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& client_cached_cert_hashes, + const std::string& compressed_cert) { + UncompressedCerts uncompressed_certs(chain, &client_cached_cert_hashes); + + uint64_t key = ComputeUncompressedCertsHash(uncompressed_certs); + + // Insert one unit to the cache. + std::unique_ptr<CachedCerts> cached_certs( + new CachedCerts(uncompressed_certs, compressed_cert)); + certs_cache_.Insert(key, std::move(cached_certs)); +} + +size_t QuicCompressedCertsCache::MaxSize() { + return certs_cache_.MaxSize(); +} + +size_t QuicCompressedCertsCache::Size() { + return certs_cache_.Size(); +} + +uint64_t QuicCompressedCertsCache::ComputeUncompressedCertsHash( + const UncompressedCerts& uncompressed_certs) { + uint64_t hash = + std::hash<std::string>()(*uncompressed_certs.client_cached_cert_hashes); + + hash_combine(&hash, + reinterpret_cast<uint64_t>(uncompressed_certs.chain.get())); + return hash; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_compressed_certs_cache.h b/quiche/quic/core/crypto/quic_compressed_certs_cache.h new file mode 100644 index 0000000..918981e --- /dev/null +++ b/quiche/quic/core/crypto/quic_compressed_certs_cache.h
@@ -0,0 +1,103 @@ +// Copyright 2016 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_COMPRESSED_CERTS_CACHE_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_COMPRESSED_CERTS_CACHE_H_ + +#include <string> +#include <vector> + +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/quic_lru_cache.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// QuicCompressedCertsCache is a cache to track most recently compressed certs. +class QUIC_EXPORT_PRIVATE QuicCompressedCertsCache { + public: + explicit QuicCompressedCertsCache(int64_t max_num_certs); + ~QuicCompressedCertsCache(); + + // Returns the pointer to the cached compressed cert if + // |chain, client_cached_cert_hashes| hits cache. + // Otherwise, return nullptr. + // Returned pointer might become invalid on the next call to Insert(). + const std::string* GetCompressedCert( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& client_cached_cert_hashes); + + // Inserts the specified + // |chain, client_cached_cert_hashes, compressed_cert| tuple to the cache. + // If the insertion causes the cache to become overfull, entries will + // be deleted in an LRU order to make room. + void Insert( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& client_cached_cert_hashes, + const std::string& compressed_cert); + + // Returns max number of cache entries the cache can carry. + size_t MaxSize(); + + // Returns current number of cache entries in the cache. + size_t Size(); + + // Default size of the QuicCompressedCertsCache per server side investigation. + static const size_t kQuicCompressedCertsCacheSize; + + private: + // A wrapper of the tuple: + // |chain, client_cached_cert_hashes| + // to identify uncompressed representation of certs. + struct QUIC_EXPORT_PRIVATE UncompressedCerts { + UncompressedCerts(); + UncompressedCerts( + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string* client_cached_cert_hashes); + ~UncompressedCerts(); + + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain; + const std::string* client_cached_cert_hashes; + }; + + // Certs stored by QuicCompressedCertsCache where uncompressed certs data is + // used to identify the uncompressed representation of certs and + // |compressed_cert| is the cached compressed representation. + class QUIC_EXPORT_PRIVATE CachedCerts { + public: + CachedCerts(); + CachedCerts(const UncompressedCerts& uncompressed_certs, + const std::string& compressed_cert); + CachedCerts(const CachedCerts& other); + ~CachedCerts(); + + // Returns true if the |uncompressed_certs| matches uncompressed + // representation of this cert. + bool MatchesUncompressedCerts( + const UncompressedCerts& uncompressed_certs) const; + + const std::string* compressed_cert() const; + + private: + // Uncompressed certs data. + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain_; + const std::string client_cached_cert_hashes_; + + // Cached compressed representation derived from uncompressed certs. + const std::string compressed_cert_; + }; + + // Computes a uint64_t hash for |uncompressed_certs|. + uint64_t ComputeUncompressedCertsHash( + const UncompressedCerts& uncompressed_certs); + + // Key is a unit64_t hash for UncompressedCerts. Stored associated value is + // CachedCerts which has both original uncompressed certs data and the + // compressed representation of the certs. + QuicLRUCache<uint64_t, CachedCerts> certs_cache_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_COMPRESSED_CERTS_CACHE_H_
diff --git a/quiche/quic/core/crypto/quic_compressed_certs_cache_test.cc b/quiche/quic/core/crypto/quic_compressed_certs_cache_test.cc new file mode 100644 index 0000000..b98f9f2 --- /dev/null +++ b/quiche/quic/core/crypto/quic_compressed_certs_cache_test.cc
@@ -0,0 +1,91 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_compressed_certs_cache.h" + +#include <string> + +#include "absl/strings/str_cat.h" +#include "quiche/quic/core/crypto/cert_compressor.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" + +namespace quic { + +namespace test { + +namespace { + +class QuicCompressedCertsCacheTest : public QuicTest { + public: + QuicCompressedCertsCacheTest() + : certs_cache_(QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {} + + protected: + QuicCompressedCertsCache certs_cache_; +}; + +TEST_F(QuicCompressedCertsCacheTest, CacheHit) { + std::vector<std::string> certs = {"leaf cert", "intermediate cert", + "root cert"}; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain( + new ProofSource::Chain(certs)); + std::string cached_certs = "cached certs"; + std::string compressed = "compressed cert"; + + certs_cache_.Insert(chain, cached_certs, compressed); + + const std::string* cached_value = + certs_cache_.GetCompressedCert(chain, cached_certs); + ASSERT_NE(nullptr, cached_value); + EXPECT_EQ(*cached_value, compressed); +} + +TEST_F(QuicCompressedCertsCacheTest, CacheMiss) { + std::vector<std::string> certs = {"leaf cert", "intermediate cert", + "root cert"}; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain( + new ProofSource::Chain(certs)); + + std::string cached_certs = "cached certs"; + std::string compressed = "compressed cert"; + + certs_cache_.Insert(chain, cached_certs, compressed); + + EXPECT_EQ(nullptr, + certs_cache_.GetCompressedCert(chain, "mismatched cached certs")); + + // A different chain though with equivalent certs should get a cache miss. + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain2( + new ProofSource::Chain(certs)); + EXPECT_EQ(nullptr, certs_cache_.GetCompressedCert(chain2, cached_certs)); +} + +TEST_F(QuicCompressedCertsCacheTest, CacheMissDueToEviction) { + // Test cache returns a miss when a queried uncompressed certs was cached but + // then evicted. + std::vector<std::string> certs = {"leaf cert", "intermediate cert", + "root cert"}; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain( + new ProofSource::Chain(certs)); + + std::string cached_certs = "cached certs"; + std::string compressed = "compressed cert"; + certs_cache_.Insert(chain, cached_certs, compressed); + + // Insert another kQuicCompressedCertsCacheSize certs to evict the first + // cached cert. + for (unsigned int i = 0; + i < QuicCompressedCertsCache::kQuicCompressedCertsCacheSize; i++) { + EXPECT_EQ(certs_cache_.Size(), i + 1); + certs_cache_.Insert(chain, absl::StrCat(i), absl::StrCat(i)); + } + EXPECT_EQ(certs_cache_.MaxSize(), certs_cache_.Size()); + + EXPECT_EQ(nullptr, certs_cache_.GetCompressedCert(chain, cached_certs)); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_crypter.cc b/quiche/quic/core/crypto/quic_crypter.cc new file mode 100644 index 0000000..05c8e3a --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypter.cc
@@ -0,0 +1,18 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_crypter.h" +#include "absl/strings/string_view.h" + +namespace quic { + +bool QuicCrypter::SetNoncePrefixOrIV(const ParsedQuicVersion& version, + absl::string_view nonce_prefix_or_iv) { + if (version.UsesInitialObfuscators()) { + return SetIV(nonce_prefix_or_iv); + } + return SetNoncePrefix(nonce_prefix_or_iv); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_crypter.h b/quiche/quic/core/crypto/quic_crypter.h new file mode 100644 index 0000000..d57a8e6 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypter.h
@@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTER_H_ + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// QuicCrypter is the parent class for QuicEncrypter and QuicDecrypter. +// Its purpose is to provide an interface for using methods that are common to +// both classes when operations are being done that apply to both encrypters and +// decrypters. +class QUIC_EXPORT_PRIVATE QuicCrypter { + public: + virtual ~QuicCrypter() {} + + // Sets the symmetric encryption/decryption key. Returns true on success, + // false on failure. + // + // NOTE: The key is the client_write_key or server_write_key derived from + // the master secret. + virtual bool SetKey(absl::string_view key) = 0; + + // Sets the fixed initial bytes of the nonce. Returns true on success, + // false on failure. This method must only be used with Google QUIC crypters. + // + // NOTE: The nonce prefix is the client_write_iv or server_write_iv + // derived from the master secret. A 64-bit packet number will + // be appended to form the nonce. + // + // <------------ 64 bits -----------> + // +---------------------+----------------------------------+ + // | Fixed prefix | packet number | + // +---------------------+----------------------------------+ + // Nonce format + // + // The security of the nonce format requires that QUIC never reuse a + // packet number, even when retransmitting a lost packet. + virtual bool SetNoncePrefix(absl::string_view nonce_prefix) = 0; + + // Sets |iv| as the initialization vector to use when constructing the nonce. + // Returns true on success, false on failure. This method must only be used + // with IETF QUIC crypters. + // + // Google QUIC and IETF QUIC use different nonce constructions. This method + // must be used when using IETF QUIC; SetNoncePrefix must be used when using + // Google QUIC. + // + // The nonce is constructed as follows (draft-ietf-quic-tls-14 section 5.2): + // + // <---------------- max(8, N_MIN) bytes -----------------> + // +--------------------------------------------------------+ + // | packet protection IV | + // +--------------------------------------------------------+ + // XOR + // <------------ 64 bits -----------> + // +---------------------+----------------------------------+ + // | zeroes | reconstructed packet number | + // +---------------------+----------------------------------+ + // + // The nonce is the packet protection IV (|iv|) XOR'd with the left-padded + // reconstructed packet number. + // + // The security of the nonce format requires that QUIC never reuse a + // packet number, even when retransmitting a lost packet. + virtual bool SetIV(absl::string_view iv) = 0; + + // Calls SetNoncePrefix or SetIV depending on whether |version| uses the + // Google QUIC crypto or IETF QUIC nonce construction. + virtual bool SetNoncePrefixOrIV(const ParsedQuicVersion& version, + absl::string_view nonce_prefix_or_iv); + + // Sets the key to use for header protection. + virtual bool SetHeaderProtectionKey(absl::string_view key) = 0; + + // GetKeySize, GetIVSize, and GetNoncePrefixSize are used to know how many + // bytes of key material needs to be derived from the master secret. + + // Returns the size in bytes of a key for the algorithm. + virtual size_t GetKeySize() const = 0; + // Returns the size in bytes of an IV to use with the algorithm. + virtual size_t GetIVSize() const = 0; + // Returns the size in bytes of the fixed initial part of the nonce. + virtual size_t GetNoncePrefixSize() const = 0; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTER_H_
diff --git a/quiche/quic/core/crypto/quic_crypto_client_config.cc b/quiche/quic/core/crypto/quic_crypto_client_config.cc new file mode 100644 index 0000000..e857591 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_client_config.cc
@@ -0,0 +1,857 @@ +// 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 "quiche/quic/core/crypto/quic_crypto_client_config.h" + +#include <algorithm> +#include <memory> +#include <string> + +#include "absl/base/macros.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/cert_compressor.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_encrypter.h" +#include "quiche/quic/core/crypto/crypto_framer.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/crypto_utils.h" +#include "quiche/quic/core/crypto/curve25519_key_exchange.h" +#include "quiche/quic/core/crypto/key_exchange.h" +#include "quiche/quic/core/crypto/p256_key_exchange.h" +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/crypto/tls_client_connection.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_client_stats.h" +#include "quiche/quic/platform/api/quic_hostname_utils.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +namespace { + +// Tracks the reason (the state of the server config) for sending inchoate +// ClientHello to the server. +void RecordInchoateClientHelloReason( + QuicCryptoClientConfig::CachedState::ServerConfigState state) { + QUIC_CLIENT_HISTOGRAM_ENUM( + "QuicInchoateClientHelloReason", state, + QuicCryptoClientConfig::CachedState::SERVER_CONFIG_COUNT, ""); +} + +// Tracks the state of the QUIC server information loaded from the disk cache. +void RecordDiskCacheServerConfigState( + QuicCryptoClientConfig::CachedState::ServerConfigState state) { + QUIC_CLIENT_HISTOGRAM_ENUM( + "QuicServerInfo.DiskCacheState", state, + QuicCryptoClientConfig::CachedState::SERVER_CONFIG_COUNT, ""); +} + +} // namespace + +QuicCryptoClientConfig::QuicCryptoClientConfig( + std::unique_ptr<ProofVerifier> proof_verifier) + : QuicCryptoClientConfig(std::move(proof_verifier), nullptr) {} + +QuicCryptoClientConfig::QuicCryptoClientConfig( + std::unique_ptr<ProofVerifier> proof_verifier, + std::unique_ptr<SessionCache> session_cache) + : proof_verifier_(std::move(proof_verifier)), + session_cache_(std::move(session_cache)), + ssl_ctx_(TlsClientConnection::CreateSslCtx( + !GetQuicFlag(FLAGS_quic_disable_client_tls_zero_rtt))) { + QUICHE_DCHECK(proof_verifier_.get()); + SetDefaults(); +} + +QuicCryptoClientConfig::~QuicCryptoClientConfig() {} + +QuicCryptoClientConfig::CachedState::CachedState() + : server_config_valid_(false), + expiration_time_(QuicWallTime::Zero()), + generation_counter_(0) {} + +QuicCryptoClientConfig::CachedState::~CachedState() {} + +bool QuicCryptoClientConfig::CachedState::IsComplete(QuicWallTime now) const { + if (server_config_.empty()) { + RecordInchoateClientHelloReason(SERVER_CONFIG_EMPTY); + return false; + } + + if (!server_config_valid_) { + RecordInchoateClientHelloReason(SERVER_CONFIG_INVALID); + return false; + } + + const CryptoHandshakeMessage* scfg = GetServerConfig(); + if (!scfg) { + // Should be impossible short of cache corruption. + RecordInchoateClientHelloReason(SERVER_CONFIG_CORRUPTED); + QUICHE_DCHECK(false); + return false; + } + + if (now.IsBefore(expiration_time_)) { + return true; + } + + QUIC_CLIENT_HISTOGRAM_TIMES( + "QuicClientHelloServerConfig.InvalidDuration", + QuicTime::Delta::FromSeconds(now.ToUNIXSeconds() - + expiration_time_.ToUNIXSeconds()), + QuicTime::Delta::FromSeconds(60), // 1 min. + QuicTime::Delta::FromSeconds(20 * 24 * 3600), // 20 days. + 50, ""); + RecordInchoateClientHelloReason(SERVER_CONFIG_EXPIRED); + return false; +} + +bool QuicCryptoClientConfig::CachedState::IsEmpty() const { + return server_config_.empty(); +} + +const CryptoHandshakeMessage* +QuicCryptoClientConfig::CachedState::GetServerConfig() const { + if (server_config_.empty()) { + return nullptr; + } + + if (!scfg_) { + scfg_ = CryptoFramer::ParseMessage(server_config_); + QUICHE_DCHECK(scfg_.get()); + } + return scfg_.get(); +} + +QuicCryptoClientConfig::CachedState::ServerConfigState +QuicCryptoClientConfig::CachedState::SetServerConfig( + absl::string_view server_config, + QuicWallTime now, + QuicWallTime expiry_time, + std::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. + std::unique_ptr<CryptoHandshakeMessage> new_scfg_storage; + const CryptoHandshakeMessage* new_scfg; + + if (!matches_existing) { + new_scfg_storage = CryptoFramer::ParseMessage(server_config); + new_scfg = new_scfg_storage.get(); + } else { + new_scfg = GetServerConfig(); + } + + if (!new_scfg) { + *error_details = "SCFG invalid"; + return SERVER_CONFIG_INVALID; + } + + if (expiry_time.IsZero()) { + uint64_t expiry_seconds; + if (new_scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) { + *error_details = "SCFG missing EXPY"; + return SERVER_CONFIG_INVALID_EXPIRY; + } + expiration_time_ = QuicWallTime::FromUNIXSeconds(expiry_seconds); + } else { + expiration_time_ = expiry_time; + } + + if (now.IsAfter(expiration_time_)) { + *error_details = "SCFG has expired"; + return SERVER_CONFIG_EXPIRED; + } + + if (!matches_existing) { + server_config_ = std::string(server_config); + SetProofInvalid(); + scfg_ = std::move(new_scfg_storage); + } + return SERVER_CONFIG_VALID; +} + +void QuicCryptoClientConfig::CachedState::InvalidateServerConfig() { + server_config_.clear(); + scfg_.reset(); + SetProofInvalid(); +} + +void QuicCryptoClientConfig::CachedState::SetProof( + const std::vector<std::string>& certs, + absl::string_view cert_sct, + absl::string_view chlo_hash, + absl::string_view signature) { + bool has_changed = signature != server_config_sig_ || + chlo_hash != chlo_hash_ || 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. + SetProofInvalid(); + certs_ = certs; + cert_sct_ = std::string(cert_sct); + chlo_hash_ = std::string(chlo_hash); + server_config_sig_ = std::string(signature); +} + +void QuicCryptoClientConfig::CachedState::Clear() { + server_config_.clear(); + source_address_token_.clear(); + certs_.clear(); + cert_sct_.clear(); + chlo_hash_.clear(); + server_config_sig_.clear(); + server_config_valid_ = false; + proof_verify_details_.reset(); + scfg_.reset(); + ++generation_counter_; +} + +void QuicCryptoClientConfig::CachedState::ClearProof() { + SetProofInvalid(); + certs_.clear(); + cert_sct_.clear(); + chlo_hash_.clear(); + server_config_sig_.clear(); +} + +void QuicCryptoClientConfig::CachedState::SetProofValid() { + server_config_valid_ = true; +} + +void QuicCryptoClientConfig::CachedState::SetProofInvalid() { + server_config_valid_ = false; + ++generation_counter_; +} + +bool QuicCryptoClientConfig::CachedState::Initialize( + absl::string_view server_config, + absl::string_view source_address_token, + const std::vector<std::string>& certs, + const std::string& cert_sct, + absl::string_view chlo_hash, + absl::string_view signature, + QuicWallTime now, + QuicWallTime expiration_time) { + QUICHE_DCHECK(server_config_.empty()); + + if (server_config.empty()) { + RecordDiskCacheServerConfigState(SERVER_CONFIG_EMPTY); + return false; + } + + std::string error_details; + ServerConfigState state = + SetServerConfig(server_config, now, expiration_time, &error_details); + RecordDiskCacheServerConfigState(state); + if (state != SERVER_CONFIG_VALID) { + QUIC_DVLOG(1) << "SetServerConfig failed with " << error_details; + return false; + } + + chlo_hash_.assign(chlo_hash.data(), chlo_hash.size()); + server_config_sig_.assign(signature.data(), signature.size()); + source_address_token_.assign(source_address_token.data(), + source_address_token.size()); + certs_ = certs; + cert_sct_ = cert_sct; + return true; +} + +const std::string& QuicCryptoClientConfig::CachedState::server_config() const { + return server_config_; +} + +const std::string& QuicCryptoClientConfig::CachedState::source_address_token() + const { + return source_address_token_; +} + +const std::vector<std::string>& QuicCryptoClientConfig::CachedState::certs() + const { + return certs_; +} + +const std::string& QuicCryptoClientConfig::CachedState::cert_sct() const { + return cert_sct_; +} + +const std::string& QuicCryptoClientConfig::CachedState::chlo_hash() const { + return chlo_hash_; +} + +const std::string& QuicCryptoClientConfig::CachedState::signature() const { + return server_config_sig_; +} + +bool QuicCryptoClientConfig::CachedState::proof_valid() const { + return server_config_valid_; +} + +uint64_t QuicCryptoClientConfig::CachedState::generation_counter() const { + return generation_counter_; +} + +const ProofVerifyDetails* +QuicCryptoClientConfig::CachedState::proof_verify_details() const { + return proof_verify_details_.get(); +} + +void QuicCryptoClientConfig::CachedState::set_source_address_token( + absl::string_view token) { + source_address_token_ = std::string(token); +} + +void QuicCryptoClientConfig::CachedState::set_cert_sct( + absl::string_view cert_sct) { + cert_sct_ = std::string(cert_sct); +} + +void QuicCryptoClientConfig::CachedState::SetProofVerifyDetails( + ProofVerifyDetails* details) { + proof_verify_details_.reset(details); +} + +void QuicCryptoClientConfig::CachedState::InitializeFrom( + const QuicCryptoClientConfig::CachedState& other) { + QUICHE_DCHECK(server_config_.empty()); + QUICHE_DCHECK(!server_config_valid_); + server_config_ = other.server_config_; + source_address_token_ = other.source_address_token_; + certs_ = other.certs_; + cert_sct_ = other.cert_sct_; + chlo_hash_ = other.chlo_hash_; + server_config_sig_ = other.server_config_sig_; + server_config_valid_ = other.server_config_valid_; + expiration_time_ = other.expiration_time_; + if (other.proof_verify_details_ != nullptr) { + proof_verify_details_.reset(other.proof_verify_details_->Clone()); + } + ++generation_counter_; +} + +void QuicCryptoClientConfig::SetDefaults() { + // Key exchange methods. + kexs = {kC255, kP256}; + + // Authenticated encryption algorithms. Prefer AES-GCM if hardware-supported + // fast implementation is available. + if (EVP_has_aes_hardware() == 1) { + aead = {kAESG, kCC20}; + } else { + aead = {kCC20, kAESG}; + } +} + +QuicCryptoClientConfig::CachedState* QuicCryptoClientConfig::LookupOrCreate( + const QuicServerId& server_id) { + auto it = cached_states_.find(server_id); + if (it != cached_states_.end()) { + return it->second.get(); + } + + CachedState* cached = new CachedState; + cached_states_.insert(std::make_pair(server_id, absl::WrapUnique(cached))); + bool cache_populated = PopulateFromCanonicalConfig(server_id, cached); + QUIC_CLIENT_HISTOGRAM_BOOL( + "QuicCryptoClientConfig.PopulatedFromCanonicalConfig", cache_populated, + ""); + return cached; +} + +void QuicCryptoClientConfig::ClearCachedStates(const ServerIdFilter& filter) { + for (auto it = cached_states_.begin(); it != cached_states_.end(); ++it) { + if (filter.Matches(it->first)) + it->second->Clear(); + } +} + +void QuicCryptoClientConfig::FillInchoateClientHello( + const QuicServerId& server_id, const ParsedQuicVersion preferred_version, + const CachedState* cached, QuicRandom* rand, bool demand_x509_proof, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + CryptoHandshakeMessage* out) const { + out->set_tag(kCHLO); + out->set_minimum_size(1); + + // Server name indication. We only send SNI if it's a valid domain name, as + // per the spec. + if (QuicHostnameUtils::IsValidSNI(server_id.host())) { + out->SetStringPiece(kSNI, server_id.host()); + } + out->SetVersion(kVER, preferred_version); + + if (!user_agent_id_.empty()) { + out->SetStringPiece(kUAID, user_agent_id_); + } + + if (!alpn_.empty()) { + out->SetStringPiece(kALPN, alpn_); + } + + // Even though this is an inchoate CHLO, send the SCID so that + // the STK can be validated by the server. + const CryptoHandshakeMessage* scfg = cached->GetServerConfig(); + if (scfg != nullptr) { + absl::string_view scid; + if (scfg->GetStringPiece(kSCID, &scid)) { + out->SetStringPiece(kSCID, scid); + } + } + + if (!cached->source_address_token().empty()) { + out->SetStringPiece(kSourceAddressTokenTag, cached->source_address_token()); + } + + if (!demand_x509_proof) { + return; + } + + char proof_nonce[32]; + rand->RandBytes(proof_nonce, ABSL_ARRAYSIZE(proof_nonce)); + out->SetStringPiece( + kNONP, absl::string_view(proof_nonce, ABSL_ARRAYSIZE(proof_nonce))); + + out->SetVector(kPDMD, QuicTagVector{kX509}); + + out->SetStringPiece(kCertificateSCTTag, ""); + + const std::vector<std::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()) { + std::vector<uint64_t> hashes; + hashes.reserve(certs.size()); + for (auto i = certs.begin(); i != certs.end(); ++i) { + hashes.push_back(QuicUtils::FNV1a_64_Hash(*i)); + } + out->SetVector(kCCRT, hashes); + } +} + +QuicErrorCode QuicCryptoClientConfig::FillClientHello( + const QuicServerId& server_id, QuicConnectionId connection_id, + const ParsedQuicVersion preferred_version, + const ParsedQuicVersion actual_version, const CachedState* cached, + QuicWallTime now, QuicRandom* rand, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + CryptoHandshakeMessage* out, std::string* error_details) const { + QUICHE_DCHECK(error_details != nullptr); + QUIC_BUG_IF(quic_bug_12943_2, + !QuicUtils::IsConnectionIdValidForVersion( + connection_id, preferred_version.transport_version)) + << "FillClientHello: attempted to use connection ID " << connection_id + << " which is invalid with version " << preferred_version; + + FillInchoateClientHello(server_id, preferred_version, cached, rand, + /* demand_x509_proof= */ true, out_params, out); + + out->set_minimum_size(1); + + 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; + } + + absl::string_view scid; + if (!scfg->GetStringPiece(kSCID, &scid)) { + *error_details = "SCFG missing SCID"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + out->SetStringPiece(kSCID, scid); + + out->SetStringPiece(kCertificateSCTTag, ""); + + QuicTagVector their_aeads; + QuicTagVector their_key_exchanges; + if (scfg->GetTaglist(kAEAD, &their_aeads) != QUIC_NO_ERROR || + scfg->GetTaglist(kKEXS, &their_key_exchanges) != QUIC_NO_ERROR) { + *error_details = "Missing AEAD or KEXS"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + // AEAD: the work loads on the client and server are symmetric. Since the + // client is more likely to be CPU-constrained, break the tie by favoring + // the client's preference. + // Key exchange: the client does more work than the server, so favor the + // client's preference. + size_t key_exchange_index; + if (!FindMutualQuicTag(aead, their_aeads, &out_params->aead, nullptr) || + !FindMutualQuicTag(kexs, their_key_exchanges, &out_params->key_exchange, + &key_exchange_index)) { + *error_details = "Unsupported AEAD or KEXS"; + return QUIC_CRYPTO_NO_SUPPORT; + } + out->SetVector(kAEAD, QuicTagVector{out_params->aead}); + out->SetVector(kKEXS, QuicTagVector{out_params->key_exchange}); + + absl::string_view 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; + } + + absl::string_view 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 = Curve25519KeyExchange::New( + Curve25519KeyExchange::NewPrivateKey(rand)); + break; + case kP256: + out_params->client_key_exchange = + P256KeyExchange::New(P256KeyExchange::NewPrivateKey()); + break; + default: + QUICHE_DCHECK(false); + *error_details = "Configured to support an unknown key exchange"; + return QUIC_CRYPTO_INTERNAL_ERROR; + } + + if (!out_params->client_key_exchange->CalculateSharedKeySync( + 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()); + + const std::vector<std::string>& certs = cached->certs(); + if (certs.empty()) { + *error_details = "No certs to calculate XLCT"; + return QUIC_CRYPTO_INTERNAL_ERROR; + } + out->SetValue(kXLCT, CryptoUtils::ComputeLeafCertHash(certs[0])); + + // Derive the symmetric keys and set up the encrypters and decrypters. + // Set the following members of out_params: + // out_params->hkdf_input_suffix + // out_params->initial_crypters + out_params->hkdf_input_suffix.clear(); + out_params->hkdf_input_suffix.append(connection_id.data(), + connection_id.length()); + 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()); + if (certs.empty()) { + *error_details = "No certs found to include in KDF"; + return QUIC_CRYPTO_INTERNAL_ERROR; + } + out_params->hkdf_input_suffix.append(certs[0]); + + std::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); + + std::string* subkey_secret = &out_params->initial_subkey_secret; + + if (!CryptoUtils::DeriveKeys( + actual_version, out_params->initial_premaster_secret, + out_params->aead, out_params->client_nonce, out_params->server_nonce, + pre_shared_key_, hkdf_input, Perspective::IS_CLIENT, + CryptoUtils::Diversification::Pending(), + &out_params->initial_crypters, subkey_secret)) { + *error_details = "Symmetric key setup failed"; + return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; + } + + return QUIC_NO_ERROR; +} + +QuicErrorCode QuicCryptoClientConfig::CacheNewServerConfig( + const CryptoHandshakeMessage& message, + QuicWallTime now, + QuicTransportVersion /*version*/, + absl::string_view chlo_hash, + const std::vector<std::string>& cached_certs, + CachedState* cached, + std::string* error_details) { + QUICHE_DCHECK(error_details != nullptr); + + absl::string_view scfg; + if (!message.GetStringPiece(kSCFG, &scfg)) { + *error_details = "Missing SCFG"; + return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND; + } + + QuicWallTime expiration_time = QuicWallTime::Zero(); + uint64_t expiry_seconds; + if (message.GetUint64(kSTTL, &expiry_seconds) == QUIC_NO_ERROR) { + // Only cache configs for a maximum of 1 week. + expiration_time = now.Add(QuicTime::Delta::FromSeconds( + std::min(expiry_seconds, kNumSecondsPerWeek))); + } + + CachedState::ServerConfigState state = + cached->SetServerConfig(scfg, now, expiration_time, error_details); + if (state == CachedState::SERVER_CONFIG_EXPIRED) { + return QUIC_CRYPTO_SERVER_CONFIG_EXPIRED; + } + // TODO(rtenneti): Return more specific error code than returning + // QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER. + if (state != CachedState::SERVER_CONFIG_VALID) { + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + absl::string_view token; + if (message.GetStringPiece(kSourceAddressTokenTag, &token)) { + cached->set_source_address_token(token); + } + + absl::string_view proof, cert_bytes, cert_sct; + bool has_proof = message.GetStringPiece(kPROF, &proof); + bool has_cert = message.GetStringPiece(kCertificateTag, &cert_bytes); + if (has_proof && has_cert) { + std::vector<std::string> certs; + if (!CertCompressor::DecompressChain(cert_bytes, cached_certs, &certs)) { + *error_details = "Certificate data invalid"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + message.GetStringPiece(kCertificateSCTTag, &cert_sct); + cached->SetProof(certs, cert_sct, chlo_hash, proof); + } else { + // Secure QUIC: clear existing proof as we have been sent a new SCFG + // without matching proof/certs. + cached->ClearProof(); + + if (has_proof && !has_cert) { + *error_details = "Certificate missing"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + if (!has_proof && has_cert) { + *error_details = "Proof missing"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + } + + return QUIC_NO_ERROR; +} + +QuicErrorCode QuicCryptoClientConfig::ProcessRejection( + const CryptoHandshakeMessage& rej, QuicWallTime now, + const QuicTransportVersion version, absl::string_view chlo_hash, + CachedState* cached, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + std::string* error_details) { + QUICHE_DCHECK(error_details != nullptr); + + if (rej.tag() != kREJ) { + *error_details = "Message is not REJ"; + return QUIC_CRYPTO_INTERNAL_ERROR; + } + + QuicErrorCode error = + CacheNewServerConfig(rej, now, version, chlo_hash, + out_params->cached_certs, cached, error_details); + if (error != QUIC_NO_ERROR) { + return error; + } + + absl::string_view nonce; + if (rej.GetStringPiece(kServerNonceTag, &nonce)) { + out_params->server_nonce = std::string(nonce); + } + + return QUIC_NO_ERROR; +} + +QuicErrorCode QuicCryptoClientConfig::ProcessServerHello( + const CryptoHandshakeMessage& server_hello, + QuicConnectionId /*connection_id*/, ParsedQuicVersion version, + const ParsedQuicVersionVector& negotiated_versions, CachedState* cached, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + std::string* error_details) { + QUICHE_DCHECK(error_details != nullptr); + + QuicErrorCode valid = CryptoUtils::ValidateServerHello( + server_hello, negotiated_versions, error_details); + if (valid != QUIC_NO_ERROR) { + return valid; + } + + // Learn about updated source address tokens. + absl::string_view token; + if (server_hello.GetStringPiece(kSourceAddressTokenTag, &token)) { + cached->set_source_address_token(token); + } + + absl::string_view shlo_nonce; + if (!server_hello.GetStringPiece(kServerNonceTag, &shlo_nonce)) { + *error_details = "server hello missing server nonce"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + // TODO(agl): + // learn about updated SCFGs. + + absl::string_view 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->CalculateSharedKeySync( + public_value, &out_params->forward_secure_premaster_secret)) { + *error_details = "Key exchange failure"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + + std::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); + + if (!CryptoUtils::DeriveKeys( + version, out_params->forward_secure_premaster_secret, + out_params->aead, out_params->client_nonce, + shlo_nonce.empty() ? out_params->server_nonce : shlo_nonce, + pre_shared_key_, hkdf_input, Perspective::IS_CLIENT, + CryptoUtils::Diversification::Never(), + &out_params->forward_secure_crypters, &out_params->subkey_secret)) { + *error_details = "Symmetric key setup failed"; + return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; + } + + return QUIC_NO_ERROR; +} + +QuicErrorCode QuicCryptoClientConfig::ProcessServerConfigUpdate( + const CryptoHandshakeMessage& server_config_update, QuicWallTime now, + const QuicTransportVersion version, absl::string_view chlo_hash, + CachedState* cached, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + std::string* error_details) { + QUICHE_DCHECK(error_details != nullptr); + + if (server_config_update.tag() != kSCUP) { + *error_details = "ServerConfigUpdate must have kSCUP tag."; + return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; + } + return CacheNewServerConfig(server_config_update, now, version, chlo_hash, + out_params->cached_certs, cached, error_details); +} + +ProofVerifier* QuicCryptoClientConfig::proof_verifier() const { + return proof_verifier_.get(); +} + +SessionCache* QuicCryptoClientConfig::session_cache() const { + return session_cache_.get(); +} + +ClientProofSource* QuicCryptoClientConfig::proof_source() const { + return proof_source_.get(); +} + +void QuicCryptoClientConfig::set_proof_source( + std::unique_ptr<ClientProofSource> proof_source) { + proof_source_ = std::move(proof_source); +} + +SSL_CTX* QuicCryptoClientConfig::ssl_ctx() const { + return ssl_ctx_.get(); +} + +void QuicCryptoClientConfig::InitializeFrom( + const QuicServerId& server_id, + const QuicServerId& canonical_server_id, + QuicCryptoClientConfig* canonical_crypto_config) { + CachedState* canonical_cached = + canonical_crypto_config->LookupOrCreate(canonical_server_id); + if (!canonical_cached->proof_valid()) { + return; + } + CachedState* cached = LookupOrCreate(server_id); + cached->InitializeFrom(*canonical_cached); +} + +void QuicCryptoClientConfig::AddCanonicalSuffix(const std::string& suffix) { + canonical_suffixes_.push_back(suffix); +} + +bool QuicCryptoClientConfig::PopulateFromCanonicalConfig( + const QuicServerId& server_id, CachedState* cached) { + QUICHE_DCHECK(cached->IsEmpty()); + size_t i = 0; + for (; i < canonical_suffixes_.size(); ++i) { + if (absl::EndsWithIgnoreCase(server_id.host(), canonical_suffixes_[i])) { + break; + } + } + if (i == canonical_suffixes_.size()) { + return false; + } + + QuicServerId suffix_server_id(canonical_suffixes_[i], server_id.port(), + server_id.privacy_mode_enabled()); + auto it = canonical_server_map_.lower_bound(suffix_server_id); + if (it == canonical_server_map_.end() || it->first != suffix_server_id) { + // This is the first host we've seen which matches the suffix, so make it + // canonical. Use |it| as position hint for faster insertion. + canonical_server_map_.insert( + it, std::make_pair(std::move(suffix_server_id), std::move(server_id))); + return false; + } + + const QuicServerId& canonical_server_id = it->second; + CachedState* canonical_state = cached_states_[canonical_server_id].get(); + if (!canonical_state->proof_valid()) { + return false; + } + + // Update canonical version to point at the "most recent" entry. + it->second = server_id; + + cached->InitializeFrom(*canonical_state); + return true; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_crypto_client_config.h b/quiche/quic/core/crypto/quic_crypto_client_config.h new file mode 100644 index 0000000..8b66ad5 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_client_config.h
@@ -0,0 +1,473 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_ + +#include <cstdint> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/base.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/client_proof_source.h" +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/transport_parameters.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/core/quic_server_id.h" +#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/common/platform/api/quiche_reference_counted.h" + +namespace quic { + +class CryptoHandshakeMessage; +class ProofVerifier; +class ProofVerifyDetails; +class QuicRandom; + +// QuicResumptionState stores the state a client needs for performing connection +// resumption. +struct QUIC_EXPORT_PRIVATE QuicResumptionState { + // |tls_session| holds the cryptographic state necessary for a resumption. It + // includes the ALPN negotiated on the connection where the ticket was + // received. + bssl::UniquePtr<SSL_SESSION> tls_session; + + // If the application using QUIC doesn't support 0-RTT handshakes or the + // client didn't receive a 0-RTT capable session ticket from the server, + // |transport_params| will be null. Otherwise, it will contain the transport + // parameters received from the server on the original connection. + std::unique_ptr<TransportParameters> transport_params = nullptr; + + // If |transport_params| is null, then |application_state| is ignored and + // should be empty. |application_state| contains serialized state that the + // client received from the server at the application layer that the client + // needs to remember when performing a 0-RTT handshake. + std::unique_ptr<ApplicationState> application_state = nullptr; + + // Opaque token received in NEW_TOKEN frame if any. + std::string token; +}; + +// SessionCache is an interface for managing storing and retrieving +// QuicResumptionState structs. +class QUIC_EXPORT_PRIVATE SessionCache { + public: + virtual ~SessionCache() {} + + // Inserts |session|, |params|, and |application_states| into the cache, keyed + // by |server_id|. Insert is first called after all three values are present. + // The ownership of |session| is transferred to the cache, while other two are + // copied. Multiple sessions might need to be inserted for a connection. + // SessionCache implementations should support storing + // multiple entries per server ID. + virtual void Insert(const QuicServerId& server_id, + bssl::UniquePtr<SSL_SESSION> session, + const TransportParameters& params, + const ApplicationState* application_state) = 0; + + // Lookup is called once at the beginning of each TLS handshake to potentially + // provide the saved state both for the TLS handshake and for sending 0-RTT + // data (if supported). Lookup may return a nullptr. Implementations should + // delete cache entries after returning them in Lookup so that session tickets + // are used only once. + virtual std::unique_ptr<QuicResumptionState> Lookup( + const QuicServerId& server_id, QuicWallTime now, const SSL_CTX* ctx) = 0; + + // Called when 0-RTT is rejected. Disables early data for all the TLS tickets + // associated with |server_id|. + virtual void ClearEarlyData(const QuicServerId& server_id) = 0; + + // Called when NEW_TOKEN frame is received. + virtual void OnNewTokenReceived(const QuicServerId& server_id, + absl::string_view token) = 0; + + // Called to remove expired entries. + virtual void RemoveExpiredEntries(QuicWallTime now) = 0; + + // Clear the session cache. + virtual void Clear() = 0; +}; + +// QuicCryptoClientConfig contains crypto-related configuration settings for a +// client. Note that this object isn't thread-safe. It's designed to be used on +// a single thread at a time. +class QUIC_EXPORT_PRIVATE QuicCryptoClientConfig : public QuicCryptoConfig { + public: + // A CachedState contains the information that the client needs in order to + // perform a 0-RTT handshake with a server. This information can be reused + // over several connections to the same server. + class QUIC_EXPORT_PRIVATE CachedState { + public: + // Enum to track if the server config is valid or not. If it is not valid, + // it specifies why it is invalid. + enum ServerConfigState { + // WARNING: Do not change the numerical values of any of server config + // state. Do not remove deprecated server config states - just comment + // them as deprecated. + SERVER_CONFIG_EMPTY = 0, + SERVER_CONFIG_INVALID = 1, + SERVER_CONFIG_CORRUPTED = 2, + SERVER_CONFIG_EXPIRED = 3, + SERVER_CONFIG_INVALID_EXPIRY = 4, + SERVER_CONFIG_VALID = 5, + // NOTE: Add new server config states only immediately above this line. + // Make sure to update the QuicServerConfigState enum in + // tools/metrics/histograms/histograms.xml accordingly. + SERVER_CONFIG_COUNT + }; + + CachedState(); + CachedState(const CachedState&) = delete; + CachedState& operator=(const CachedState&) = delete; + ~CachedState(); + + // IsComplete returns true if this object contains enough information to + // perform a handshake with the server. |now| is used to judge whether any + // cached server config has expired. + bool IsComplete(QuicWallTime now) const; + + // IsEmpty returns true if |server_config_| is empty. + bool IsEmpty() const; + + // GetServerConfig returns the parsed contents of |server_config|, or + // nullptr if |server_config| is empty. The return value is owned by this + // object and is destroyed when this object is. + const CryptoHandshakeMessage* GetServerConfig() const; + + // SetServerConfig checks that |server_config| parses correctly and stores + // it in |server_config_|. |now| is used to judge whether |server_config| + // has expired. + ServerConfigState SetServerConfig(absl::string_view server_config, + QuicWallTime now, + QuicWallTime expiry_time, + std::string* error_details); + + // InvalidateServerConfig clears the cached server config (if any). + void InvalidateServerConfig(); + + // SetProof stores a cert chain, cert signed timestamp and signature. + void SetProof(const std::vector<std::string>& certs, + absl::string_view cert_sct, + absl::string_view chlo_hash, + absl::string_view signature); + + // Clears all the data. + void Clear(); + + // Clears the certificate chain and signature and invalidates the proof. + void ClearProof(); + + // SetProofValid records that the certificate chain and signature have been + // validated and that it's safe to assume that the server is legitimate. + // (Note: this does not check the chain or signature.) + void SetProofValid(); + + // If the server config or the proof has changed then it needs to be + // revalidated. Helper function to keep server_config_valid_ and + // generation_counter_ in sync. + void SetProofInvalid(); + + const std::string& server_config() const; + const std::string& source_address_token() const; + const std::vector<std::string>& certs() const; + const std::string& cert_sct() const; + const std::string& chlo_hash() const; + const std::string& signature() const; + bool proof_valid() const; + uint64_t generation_counter() const; + const ProofVerifyDetails* proof_verify_details() const; + + void set_source_address_token(absl::string_view token); + + void set_cert_sct(absl::string_view cert_sct); + + // SetProofVerifyDetails takes ownership of |details|. + void SetProofVerifyDetails(ProofVerifyDetails* details); + + // Copy the |server_config_|, |source_address_token_|, |certs_|, + // |expiration_time_|, |cert_sct_|, |chlo_hash_| and |server_config_sig_| + // from the |other|. The remaining fields, |generation_counter_|, + // |proof_verify_details_|, and |scfg_| remain unchanged. + void InitializeFrom(const CachedState& other); + + // Initializes this cached state based on the arguments provided. + // Returns false if there is a problem parsing the server config. + bool Initialize(absl::string_view server_config, + absl::string_view source_address_token, + const std::vector<std::string>& certs, + const std::string& cert_sct, + absl::string_view chlo_hash, + absl::string_view signature, + QuicWallTime now, + QuicWallTime expiration_time); + + private: + std::string server_config_; // A serialized handshake message. + std::string source_address_token_; // An opaque proof of IP ownership. + std::vector<std::string> certs_; // A list of certificates in leaf-first + // order. + std::string cert_sct_; // Signed timestamp of the leaf cert. + std::string chlo_hash_; // Hash of the CHLO message. + std::string server_config_sig_; // A signature of |server_config_|. + bool server_config_valid_; // True if |server_config_| is correctly + // signed and |certs_| has been validated. + QuicWallTime expiration_time_; // Time when the config is no longer valid. + // Generation counter associated with the |server_config_|, |certs_| and + // |server_config_sig_| combination. It is incremented whenever we set + // server_config_valid_ to false. + uint64_t generation_counter_; + + std::unique_ptr<ProofVerifyDetails> proof_verify_details_; + + // scfg contains the cached, parsed value of |server_config|. + mutable std::unique_ptr<CryptoHandshakeMessage> scfg_; + }; + + // Used to filter server ids for partial config deletion. + class QUIC_EXPORT_PRIVATE ServerIdFilter { + public: + virtual ~ServerIdFilter() {} + + // Returns true if |server_id| matches the filter. + virtual bool Matches(const QuicServerId& server_id) const = 0; + }; + + // DEPRECATED: Use the constructor below instead. + explicit QuicCryptoClientConfig( + std::unique_ptr<ProofVerifier> proof_verifier); + QuicCryptoClientConfig(std::unique_ptr<ProofVerifier> proof_verifier, + std::unique_ptr<SessionCache> session_cache); + QuicCryptoClientConfig(const QuicCryptoClientConfig&) = delete; + QuicCryptoClientConfig& operator=(const QuicCryptoClientConfig&) = delete; + ~QuicCryptoClientConfig(); + + // LookupOrCreate returns a CachedState for the given |server_id|. If no such + // CachedState currently exists, it will be created and cached. + CachedState* LookupOrCreate(const QuicServerId& server_id); + + // Delete CachedState objects whose server ids match |filter| from + // cached_states. + void ClearCachedStates(const ServerIdFilter& filter); + + // FillInchoateClientHello sets |out| to be a CHLO message that elicits a + // source-address token or SCFG from a server. If |cached| is non-nullptr, the + // source-address token will be taken from it. |out_params| is used in order + // to store the cached certs that were sent as hints to the server in + // |out_params->cached_certs|. |preferred_version| is the version of the + // QUIC protocol that this client chose to use initially. This allows the + // server to detect downgrade attacks. If |demand_x509_proof| is true, + // then |out| will include an X509 proof demand, and the associated + // certificate related fields. + void FillInchoateClientHello( + const QuicServerId& server_id, const ParsedQuicVersion preferred_version, + const CachedState* cached, QuicRandom* rand, bool demand_x509_proof, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + CryptoHandshakeMessage* out) const; + + // FillClientHello sets |out| to be a CHLO message based on the configuration + // of this object. This object must have cached enough information about + // the server's hostname in order to perform a handshake. This can be checked + // with the |IsComplete| member of |CachedState|. + // + // |now| and |rand| are used to generate the nonce and |out_params| is + // filled with the results of the handshake that the server is expected to + // accept. |preferred_version| is the version of the QUIC protocol that this + // client chose to use initially. This allows the server to detect downgrade + // attacks. + // + // If |channel_id_key| is not null, it is used to sign a secret value derived + // from the client and server's keys, and the Channel ID public key and the + // signature are placed in the CETV value of the CHLO. + QuicErrorCode FillClientHello( + const QuicServerId& server_id, QuicConnectionId connection_id, + const ParsedQuicVersion preferred_version, + const ParsedQuicVersion actual_version, const CachedState* cached, + QuicWallTime now, QuicRandom* rand, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + CryptoHandshakeMessage* out, std::string* error_details) const; + + // ProcessRejection processes a REJ message from a server and updates the + // cached information about that server. After this, |IsComplete| may return + // true for that server's CachedState. If the rejection message contains state + // about a future handshake (i.e. an nonce value from the server), then it + // will be saved in |out_params|. |now| is used to judge whether the server + // config in the rejection message has expired. + QuicErrorCode ProcessRejection( + const CryptoHandshakeMessage& rej, QuicWallTime now, + QuicTransportVersion version, absl::string_view chlo_hash, + CachedState* cached, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + std::string* error_details); + + // ProcessServerHello processes the message in |server_hello|, updates the + // cached information about that server, writes the negotiated parameters to + // |out_params| and returns QUIC_NO_ERROR. If |server_hello| is unacceptable + // then it puts an error message in |error_details| and returns an error + // code. |version| is the QUIC version for the current connection. + // |negotiated_versions| contains the list of version, if any, that were + // present in a version negotiation packet previously received from the + // server. The contents of this list will be compared against the list of + // versions provided in the VER tag of the server hello. + QuicErrorCode ProcessServerHello( + const CryptoHandshakeMessage& server_hello, + QuicConnectionId connection_id, ParsedQuicVersion version, + const ParsedQuicVersionVector& negotiated_versions, CachedState* cached, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + std::string* error_details); + + // Processes the message in |server_config_update|, updating the cached source + // address token, and server config. + // If |server_config_update| is invalid then |error_details| will contain an + // error message, and an error code will be returned. If all has gone well + // QUIC_NO_ERROR is returned. + QuicErrorCode ProcessServerConfigUpdate( + const CryptoHandshakeMessage& server_config_update, QuicWallTime now, + const QuicTransportVersion version, absl::string_view chlo_hash, + CachedState* cached, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params, + std::string* error_details); + + ProofVerifier* proof_verifier() const; + SessionCache* session_cache() const; + ClientProofSource* proof_source() const; + void set_proof_source(std::unique_ptr<ClientProofSource> proof_source); + SSL_CTX* ssl_ctx() const; + + // Initialize the CachedState from |canonical_crypto_config| for the + // |canonical_server_id| as the initial CachedState for |server_id|. We will + // copy config data only if |canonical_crypto_config| has valid proof. + void InitializeFrom(const QuicServerId& server_id, + const QuicServerId& canonical_server_id, + QuicCryptoClientConfig* canonical_crypto_config); + + // Adds |suffix| as a domain suffix for which the server's crypto config + // is expected to be shared among servers with the domain suffix. If a server + // matches this suffix, then the server config from another server with the + // suffix will be used to initialize the cached state for this server. + void AddCanonicalSuffix(const std::string& suffix); + + // Saves the |user_agent_id| that will be passed in QUIC's CHLO message. + void set_user_agent_id(const std::string& user_agent_id) { + user_agent_id_ = user_agent_id; + } + + // Returns the user_agent_id that will be provided in the client hello + // handshake message. + const std::string& user_agent_id() const { return user_agent_id_; } + + void set_tls_signature_algorithms(std::string signature_algorithms) { + tls_signature_algorithms_ = std::move(signature_algorithms); + } + + const absl::optional<std::string>& tls_signature_algorithms() const { + return tls_signature_algorithms_; + } + + // Saves the |alpn| that will be passed in QUIC's CHLO message. + void set_alpn(const std::string& alpn) { alpn_ = alpn; } + + // Saves the pre-shared key used during the handshake. + void set_pre_shared_key(absl::string_view psk) { + pre_shared_key_ = std::string(psk); + } + + // Returns the pre-shared key used during the handshake. + const std::string& pre_shared_key() const { return pre_shared_key_; } + + bool pad_inchoate_hello() const { return pad_inchoate_hello_; } + void set_pad_inchoate_hello(bool new_value) { + pad_inchoate_hello_ = new_value; + } + + bool pad_full_hello() const { return pad_full_hello_; } + void set_pad_full_hello(bool new_value) { pad_full_hello_ = new_value; } + + SessionCache* mutable_session_cache() { return session_cache_.get(); } + + private: + // Sets the members to reasonable, default values. + void SetDefaults(); + + // CacheNewServerConfig checks for SCFG, STK, PROF, and CRT tags in |message|, + // verifies them, and stores them in the cached state if they validate. + // This is used on receipt of a REJ from a server, or when a server sends + // updated server config during a connection. + QuicErrorCode CacheNewServerConfig( + const CryptoHandshakeMessage& message, + QuicWallTime now, + QuicTransportVersion version, + absl::string_view chlo_hash, + const std::vector<std::string>& cached_certs, + CachedState* cached, + std::string* error_details); + + // If the suffix of the hostname in |server_id| is in |canonical_suffixes_|, + // then populate |cached| with the canonical cached state from + // |canonical_server_map_| for that suffix. Returns true if |cached| is + // initialized with canonical cached state. + bool PopulateFromCanonicalConfig(const QuicServerId& server_id, + CachedState* cached); + + // cached_states_ maps from the server_id to the cached information about + // that server. + std::map<QuicServerId, std::unique_ptr<CachedState>> cached_states_; + + // Contains a map of servers which could share the same server config. Map + // from a canonical host suffix/port/scheme to a representative server with + // the canonical suffix, which has a plausible set of initial certificates + // (or at least server public key). + std::map<QuicServerId, QuicServerId> canonical_server_map_; + + // Contains list of suffixes (for exmaple ".c.youtube.com", + // ".googlevideo.com") of canonical hostnames. + std::vector<std::string> canonical_suffixes_; + + std::unique_ptr<ProofVerifier> proof_verifier_; + std::unique_ptr<SessionCache> session_cache_; + std::unique_ptr<ClientProofSource> proof_source_; + + bssl::UniquePtr<SSL_CTX> ssl_ctx_; + + // The |user_agent_id_| passed in QUIC's CHLO message. + std::string user_agent_id_; + + // The |alpn_| passed in QUIC's CHLO message. + std::string alpn_; + + // If non-empty, the client will operate in the pre-shared key mode by + // incorporating |pre_shared_key_| into the key schedule. + std::string pre_shared_key_; + + // If set, configure the client to use the specified signature algorithms, via + // SSL_set1_sigalgs_list. TLS only. + absl::optional<std::string> tls_signature_algorithms_; + + // In QUIC, technically, client hello should be fully padded. + // However, fully padding on slow network connection (e.g. 50kbps) can add + // 150ms latency to one roundtrip. Therefore, you can disable padding of + // individual messages. It is recommend to leave at least one message in + // each direction fully padded (e.g. full CHLO and SHLO), but if you know + // the lower-bound MTU, you don't need to pad all of them (keep in mind that + // it's not OK to do it according to the standard). + // + // Also, if you disable padding, you must disable (change) the + // anti-amplification protection. You should only do so if you have some + // other means of verifying the client. + bool pad_inchoate_hello_ = true; + bool pad_full_hello_ = true; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_
diff --git a/quiche/quic/core/crypto/quic_crypto_client_config_test.cc b/quiche/quic/core/crypto/quic_crypto_client_config_test.cc new file mode 100644 index 0000000..7556592 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_client_config_test.cc
@@ -0,0 +1,550 @@ +// 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 "quiche/quic/core/crypto/quic_crypto_client_config.h" + +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/quic_server_id.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_expect_bug.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/mock_random.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +using testing::StartsWith; + +namespace quic { +namespace test { +namespace { + +class TestProofVerifyDetails : public ProofVerifyDetails { + ~TestProofVerifyDetails() override {} + + // ProofVerifyDetails implementation + ProofVerifyDetails* Clone() const override { + return new TestProofVerifyDetails; + } +}; + +class OneServerIdFilter : public QuicCryptoClientConfig::ServerIdFilter { + public: + explicit OneServerIdFilter(const QuicServerId* server_id) + : server_id_(*server_id) {} + + bool Matches(const QuicServerId& server_id) const override { + return server_id == server_id_; + } + + private: + const QuicServerId server_id_; +}; + +class AllServerIdsFilter : public QuicCryptoClientConfig::ServerIdFilter { + public: + bool Matches(const QuicServerId& /*server_id*/) const override { + return true; + } +}; + +} // namespace + +class QuicCryptoClientConfigTest : public QuicTest {}; + +TEST_F(QuicCryptoClientConfigTest, CachedState_IsEmpty) { + QuicCryptoClientConfig::CachedState state; + EXPECT_TRUE(state.IsEmpty()); +} + +TEST_F(QuicCryptoClientConfigTest, CachedState_IsComplete) { + QuicCryptoClientConfig::CachedState state; + EXPECT_FALSE(state.IsComplete(QuicWallTime::FromUNIXSeconds(0))); +} + +TEST_F(QuicCryptoClientConfigTest, CachedState_GenerationCounter) { + QuicCryptoClientConfig::CachedState state; + EXPECT_EQ(0u, state.generation_counter()); + state.SetProofInvalid(); + EXPECT_EQ(1u, state.generation_counter()); +} + +TEST_F(QuicCryptoClientConfigTest, CachedState_SetProofVerifyDetails) { + QuicCryptoClientConfig::CachedState state; + EXPECT_TRUE(state.proof_verify_details() == nullptr); + ProofVerifyDetails* details = new TestProofVerifyDetails; + state.SetProofVerifyDetails(details); + EXPECT_EQ(details, state.proof_verify_details()); +} + +TEST_F(QuicCryptoClientConfigTest, CachedState_InitializeFrom) { + QuicCryptoClientConfig::CachedState state; + QuicCryptoClientConfig::CachedState other; + state.set_source_address_token("TOKEN"); + // TODO(rch): Populate other fields of |state|. + other.InitializeFrom(state); + EXPECT_EQ(state.server_config(), other.server_config()); + EXPECT_EQ(state.source_address_token(), other.source_address_token()); + EXPECT_EQ(state.certs(), other.certs()); + EXPECT_EQ(1u, other.generation_counter()); +} + +TEST_F(QuicCryptoClientConfigTest, InchoateChlo) { + QuicCryptoClientConfig::CachedState state; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.set_user_agent_id("quic-tester"); + config.set_alpn("hq"); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + CryptoHandshakeMessage msg; + QuicServerId server_id("www.google.com", 443, false); + MockRandom rand; + config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, &rand, + /* demand_x509_proof= */ true, params, &msg); + + QuicVersionLabel cver; + EXPECT_THAT(msg.GetVersionLabel(kVER, &cver), IsQuicNoError()); + EXPECT_EQ(CreateQuicVersionLabel(QuicVersionMax()), cver); + absl::string_view proof_nonce; + EXPECT_TRUE(msg.GetStringPiece(kNONP, &proof_nonce)); + EXPECT_EQ(std::string(32, 'r'), proof_nonce); + absl::string_view user_agent_id; + EXPECT_TRUE(msg.GetStringPiece(kUAID, &user_agent_id)); + EXPECT_EQ("quic-tester", user_agent_id); + absl::string_view alpn; + EXPECT_TRUE(msg.GetStringPiece(kALPN, &alpn)); + EXPECT_EQ("hq", alpn); + EXPECT_EQ(msg.minimum_size(), 1u); +} + +TEST_F(QuicCryptoClientConfigTest, InchoateChloIsNotPadded) { + QuicCryptoClientConfig::CachedState state; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.set_pad_inchoate_hello(false); + config.set_user_agent_id("quic-tester"); + config.set_alpn("hq"); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + CryptoHandshakeMessage msg; + QuicServerId server_id("www.google.com", 443, false); + MockRandom rand; + config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, &rand, + /* demand_x509_proof= */ true, params, &msg); + + EXPECT_EQ(msg.minimum_size(), 1u); +} + +// Make sure AES-GCM is the preferred encryption algorithm if it has hardware +// acceleration. +TEST_F(QuicCryptoClientConfigTest, PreferAesGcm) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + if (EVP_has_aes_hardware() == 1) { + EXPECT_EQ(kAESG, config.aead[0]); + } else { + EXPECT_EQ(kCC20, config.aead[0]); + } +} + +TEST_F(QuicCryptoClientConfigTest, InchoateChloSecure) { + QuicCryptoClientConfig::CachedState state; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + CryptoHandshakeMessage msg; + QuicServerId server_id("www.google.com", 443, false); + MockRandom rand; + config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, &rand, + /* demand_x509_proof= */ true, params, &msg); + + QuicTag pdmd; + EXPECT_THAT(msg.GetUint32(kPDMD, &pdmd), IsQuicNoError()); + EXPECT_EQ(kX509, pdmd); + absl::string_view scid; + EXPECT_FALSE(msg.GetStringPiece(kSCID, &scid)); +} + +TEST_F(QuicCryptoClientConfigTest, InchoateChloSecureWithSCIDNoEXPY) { + // Test that a config with no EXPY is still valid when a non-zero + // expiry time is passed in. + QuicCryptoClientConfig::CachedState state; + CryptoHandshakeMessage scfg; + scfg.set_tag(kSCFG); + scfg.SetStringPiece(kSCID, "12345678"); + std::string details; + QuicWallTime now = QuicWallTime::FromUNIXSeconds(1); + QuicWallTime expiry = QuicWallTime::FromUNIXSeconds(2); + state.SetServerConfig(scfg.GetSerialized().AsStringPiece(), now, expiry, + &details); + EXPECT_FALSE(state.IsEmpty()); + + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + CryptoHandshakeMessage msg; + QuicServerId server_id("www.google.com", 443, false); + MockRandom rand; + config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, &rand, + /* demand_x509_proof= */ true, params, &msg); + + absl::string_view scid; + EXPECT_TRUE(msg.GetStringPiece(kSCID, &scid)); + EXPECT_EQ("12345678", scid); +} + +TEST_F(QuicCryptoClientConfigTest, InchoateChloSecureWithSCID) { + QuicCryptoClientConfig::CachedState state; + CryptoHandshakeMessage scfg; + scfg.set_tag(kSCFG); + uint64_t future = 1; + scfg.SetValue(kEXPY, future); + scfg.SetStringPiece(kSCID, "12345678"); + std::string details; + state.SetServerConfig(scfg.GetSerialized().AsStringPiece(), + QuicWallTime::FromUNIXSeconds(1), + QuicWallTime::FromUNIXSeconds(0), &details); + EXPECT_FALSE(state.IsEmpty()); + + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + CryptoHandshakeMessage msg; + QuicServerId server_id("www.google.com", 443, false); + MockRandom rand; + config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, &rand, + /* demand_x509_proof= */ true, params, &msg); + + absl::string_view scid; + EXPECT_TRUE(msg.GetStringPiece(kSCID, &scid)); + EXPECT_EQ("12345678", scid); +} + +TEST_F(QuicCryptoClientConfigTest, FillClientHello) { + QuicCryptoClientConfig::CachedState state; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + QuicConnectionId kConnectionId = TestConnectionId(1234); + std::string error_details; + MockRandom rand; + CryptoHandshakeMessage chlo; + QuicServerId server_id("www.google.com", 443, false); + config.FillClientHello(server_id, kConnectionId, QuicVersionMax(), + QuicVersionMax(), &state, QuicWallTime::Zero(), &rand, + params, &chlo, &error_details); + + // Verify that the version label has been set correctly in the CHLO. + QuicVersionLabel cver; + EXPECT_THAT(chlo.GetVersionLabel(kVER, &cver), IsQuicNoError()); + EXPECT_EQ(CreateQuicVersionLabel(QuicVersionMax()), cver); +} + +TEST_F(QuicCryptoClientConfigTest, FillClientHelloNoPadding) { + QuicCryptoClientConfig::CachedState state; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.set_pad_full_hello(false); + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( + new QuicCryptoNegotiatedParameters); + QuicConnectionId kConnectionId = TestConnectionId(1234); + std::string error_details; + MockRandom rand; + CryptoHandshakeMessage chlo; + QuicServerId server_id("www.google.com", 443, false); + config.FillClientHello(server_id, kConnectionId, QuicVersionMax(), + QuicVersionMax(), &state, QuicWallTime::Zero(), &rand, + params, &chlo, &error_details); + + // Verify that the version label has been set correctly in the CHLO. + QuicVersionLabel cver; + EXPECT_THAT(chlo.GetVersionLabel(kVER, &cver), IsQuicNoError()); + EXPECT_EQ(CreateQuicVersionLabel(QuicVersionMax()), cver); + EXPECT_EQ(chlo.minimum_size(), 1u); +} + +TEST_F(QuicCryptoClientConfigTest, ProcessServerDowngradeAttack) { + ParsedQuicVersionVector supported_versions = AllSupportedVersions(); + if (supported_versions.size() == 1) { + // No downgrade attack is possible if the client only supports one version. + return; + } + + ParsedQuicVersionVector supported_version_vector; + for (size_t i = supported_versions.size(); i > 0; --i) { + supported_version_vector.push_back(supported_versions[i - 1]); + } + + CryptoHandshakeMessage msg; + msg.set_tag(kSHLO); + msg.SetVersionVector(kVER, supported_version_vector); + + QuicCryptoClientConfig::CachedState cached; + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params(new QuicCryptoNegotiatedParameters); + std::string error; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + EXPECT_THAT(config.ProcessServerHello( + msg, EmptyQuicConnectionId(), supported_versions.front(), + supported_versions, &cached, out_params, &error), + IsError(QUIC_VERSION_NEGOTIATION_MISMATCH)); + EXPECT_THAT(error, StartsWith("Downgrade attack detected: ServerVersions")); +} + +TEST_F(QuicCryptoClientConfigTest, InitializeFrom) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + QuicServerId canonical_server_id("www.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state = + config.LookupOrCreate(canonical_server_id); + // TODO(rch): Populate other fields of |state|. + state->set_source_address_token("TOKEN"); + state->SetProofValid(); + + QuicServerId other_server_id("mail.google.com", 443, false); + config.InitializeFrom(other_server_id, canonical_server_id, &config); + QuicCryptoClientConfig::CachedState* other = + config.LookupOrCreate(other_server_id); + + EXPECT_EQ(state->server_config(), other->server_config()); + EXPECT_EQ(state->source_address_token(), other->source_address_token()); + EXPECT_EQ(state->certs(), other->certs()); + EXPECT_EQ(1u, other->generation_counter()); +} + +TEST_F(QuicCryptoClientConfigTest, Canonical) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.AddCanonicalSuffix(".google.com"); + QuicServerId canonical_id1("www.google.com", 443, false); + QuicServerId canonical_id2("mail.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state = + config.LookupOrCreate(canonical_id1); + // TODO(rch): Populate other fields of |state|. + state->set_source_address_token("TOKEN"); + state->SetProofValid(); + + QuicCryptoClientConfig::CachedState* other = + config.LookupOrCreate(canonical_id2); + + EXPECT_TRUE(state->IsEmpty()); + EXPECT_EQ(state->server_config(), other->server_config()); + EXPECT_EQ(state->source_address_token(), other->source_address_token()); + EXPECT_EQ(state->certs(), other->certs()); + EXPECT_EQ(1u, other->generation_counter()); + + QuicServerId different_id("mail.google.org", 443, false); + EXPECT_TRUE(config.LookupOrCreate(different_id)->IsEmpty()); +} + +TEST_F(QuicCryptoClientConfigTest, CanonicalNotUsedIfNotValid) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.AddCanonicalSuffix(".google.com"); + QuicServerId canonical_id1("www.google.com", 443, false); + QuicServerId canonical_id2("mail.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state = + config.LookupOrCreate(canonical_id1); + // TODO(rch): Populate other fields of |state|. + state->set_source_address_token("TOKEN"); + + // Do not set the proof as valid, and check that it is not used + // as a canonical entry. + EXPECT_TRUE(config.LookupOrCreate(canonical_id2)->IsEmpty()); +} + +TEST_F(QuicCryptoClientConfigTest, ClearCachedStates) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + + // Create two states on different origins. + struct TestCase { + TestCase(const std::string& host, QuicCryptoClientConfig* config) + : server_id(host, 443, false), + state(config->LookupOrCreate(server_id)) { + // TODO(rch): Populate other fields of |state|. + CryptoHandshakeMessage scfg; + scfg.set_tag(kSCFG); + uint64_t future = 1; + scfg.SetValue(kEXPY, future); + scfg.SetStringPiece(kSCID, "12345678"); + std::string details; + state->SetServerConfig(scfg.GetSerialized().AsStringPiece(), + QuicWallTime::FromUNIXSeconds(0), + QuicWallTime::FromUNIXSeconds(future), &details); + + std::vector<std::string> certs(1); + certs[0] = "Hello Cert for " + host; + state->SetProof(certs, "cert_sct", "chlo_hash", "signature"); + state->set_source_address_token("TOKEN"); + state->SetProofValid(); + + // The generation counter starts at 2, because proof has been once + // invalidated in SetServerConfig(). + EXPECT_EQ(2u, state->generation_counter()); + } + + QuicServerId server_id; + QuicCryptoClientConfig::CachedState* state; + } test_cases[] = {TestCase("www.google.com", &config), + TestCase("www.example.com", &config)}; + + // Verify LookupOrCreate returns the same data. + for (const TestCase& test_case : test_cases) { + QuicCryptoClientConfig::CachedState* other = + config.LookupOrCreate(test_case.server_id); + EXPECT_EQ(test_case.state, other); + EXPECT_EQ(2u, other->generation_counter()); + } + + // Clear the cached state for www.google.com. + OneServerIdFilter google_com_filter(&test_cases[0].server_id); + config.ClearCachedStates(google_com_filter); + + // Verify LookupOrCreate doesn't have any data for google.com. + QuicCryptoClientConfig::CachedState* cleared_cache = + config.LookupOrCreate(test_cases[0].server_id); + + EXPECT_EQ(test_cases[0].state, cleared_cache); + EXPECT_FALSE(cleared_cache->proof_valid()); + EXPECT_TRUE(cleared_cache->server_config().empty()); + EXPECT_TRUE(cleared_cache->certs().empty()); + EXPECT_TRUE(cleared_cache->cert_sct().empty()); + EXPECT_TRUE(cleared_cache->signature().empty()); + EXPECT_EQ(3u, cleared_cache->generation_counter()); + + // But it still does for www.example.com. + QuicCryptoClientConfig::CachedState* existing_cache = + config.LookupOrCreate(test_cases[1].server_id); + + EXPECT_EQ(test_cases[1].state, existing_cache); + EXPECT_TRUE(existing_cache->proof_valid()); + EXPECT_FALSE(existing_cache->server_config().empty()); + EXPECT_FALSE(existing_cache->certs().empty()); + EXPECT_FALSE(existing_cache->cert_sct().empty()); + EXPECT_FALSE(existing_cache->signature().empty()); + EXPECT_EQ(2u, existing_cache->generation_counter()); + + // Clear all cached states. + AllServerIdsFilter all_server_ids; + config.ClearCachedStates(all_server_ids); + + // The data for www.example.com should now be cleared as well. + cleared_cache = config.LookupOrCreate(test_cases[1].server_id); + + EXPECT_EQ(test_cases[1].state, cleared_cache); + EXPECT_FALSE(cleared_cache->proof_valid()); + EXPECT_TRUE(cleared_cache->server_config().empty()); + EXPECT_TRUE(cleared_cache->certs().empty()); + EXPECT_TRUE(cleared_cache->cert_sct().empty()); + EXPECT_TRUE(cleared_cache->signature().empty()); + EXPECT_EQ(3u, cleared_cache->generation_counter()); +} + +TEST_F(QuicCryptoClientConfigTest, ProcessReject) { + CryptoHandshakeMessage rej; + crypto_test_utils::FillInDummyReject(&rej); + + // Now process the rejection. + QuicCryptoClientConfig::CachedState cached; + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params(new QuicCryptoNegotiatedParameters); + std::string error; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + EXPECT_THAT( + config.ProcessRejection( + rej, QuicWallTime::FromUNIXSeconds(0), + AllSupportedVersionsWithQuicCrypto().front().transport_version, "", + &cached, out_params, &error), + IsQuicNoError()); +} + +TEST_F(QuicCryptoClientConfigTest, ProcessRejectWithLongTTL) { + CryptoHandshakeMessage rej; + crypto_test_utils::FillInDummyReject(&rej); + QuicTime::Delta one_week = QuicTime::Delta::FromSeconds(kNumSecondsPerWeek); + int64_t long_ttl = 3 * one_week.ToSeconds(); + rej.SetValue(kSTTL, long_ttl); + + // Now process the rejection. + QuicCryptoClientConfig::CachedState cached; + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params(new QuicCryptoNegotiatedParameters); + std::string error; + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + EXPECT_THAT( + config.ProcessRejection( + rej, QuicWallTime::FromUNIXSeconds(0), + AllSupportedVersionsWithQuicCrypto().front().transport_version, "", + &cached, out_params, &error), + IsQuicNoError()); + cached.SetProofValid(); + EXPECT_FALSE(cached.IsComplete(QuicWallTime::FromUNIXSeconds(long_ttl))); + EXPECT_FALSE( + cached.IsComplete(QuicWallTime::FromUNIXSeconds(one_week.ToSeconds()))); + EXPECT_TRUE(cached.IsComplete( + QuicWallTime::FromUNIXSeconds(one_week.ToSeconds() - 1))); +} + +TEST_F(QuicCryptoClientConfigTest, ServerNonceinSHLO) { + // Test that the server must include a nonce in the SHLO. + CryptoHandshakeMessage msg; + msg.set_tag(kSHLO); + // Choose the latest version. + ParsedQuicVersionVector supported_versions; + ParsedQuicVersion version = AllSupportedVersions().front(); + supported_versions.push_back(version); + msg.SetVersionVector(kVER, supported_versions); + + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + QuicCryptoClientConfig::CachedState cached; + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + out_params(new QuicCryptoNegotiatedParameters); + std::string error_details; + EXPECT_THAT(config.ProcessServerHello(msg, EmptyQuicConnectionId(), version, + supported_versions, &cached, out_params, + &error_details), + IsError(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER)); + EXPECT_EQ("server hello missing server nonce", error_details); +} + +// Test that PopulateFromCanonicalConfig() handles the case of multiple entries +// in |canonical_server_map_|. +TEST_F(QuicCryptoClientConfigTest, MultipleCanonicalEntries) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.AddCanonicalSuffix(".google.com"); + QuicServerId canonical_server_id1("www.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state1 = + config.LookupOrCreate(canonical_server_id1); + + CryptoHandshakeMessage scfg; + scfg.set_tag(kSCFG); + scfg.SetStringPiece(kSCID, "12345678"); + std::string details; + QuicWallTime now = QuicWallTime::FromUNIXSeconds(1); + QuicWallTime expiry = QuicWallTime::FromUNIXSeconds(2); + state1->SetServerConfig(scfg.GetSerialized().AsStringPiece(), now, expiry, + &details); + state1->set_source_address_token("TOKEN"); + state1->SetProofValid(); + EXPECT_FALSE(state1->IsEmpty()); + + // This will have the same |suffix_server_id| as |canonical_server_id1|, + // therefore |*state2| will be initialized from |*state1|. + QuicServerId canonical_server_id2("mail.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state2 = + config.LookupOrCreate(canonical_server_id2); + EXPECT_FALSE(state2->IsEmpty()); + const CryptoHandshakeMessage* const scfg2 = state2->GetServerConfig(); + ASSERT_TRUE(scfg2); + EXPECT_EQ(kSCFG, scfg2->tag()); + + // With a different |suffix_server_id|, this will return an empty CachedState. + config.AddCanonicalSuffix(".example.com"); + QuicServerId canonical_server_id3("www.example.com", 443, false); + QuicCryptoClientConfig::CachedState* state3 = + config.LookupOrCreate(canonical_server_id3); + EXPECT_TRUE(state3->IsEmpty()); + const CryptoHandshakeMessage* const scfg3 = state3->GetServerConfig(); + EXPECT_FALSE(scfg3); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_crypto_proof.cc b/quiche/quic/core/crypto/quic_crypto_proof.cc new file mode 100644 index 0000000..a449c26 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_proof.cc
@@ -0,0 +1,12 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_crypto_proof.h" + +namespace quic { + +QuicCryptoProof::QuicCryptoProof() + : send_expect_ct_header(false), cert_matched_sni(false) {} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_crypto_proof.h b/quiche/quic/core/crypto/quic_crypto_proof.h new file mode 100644 index 0000000..579d264 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_proof.h
@@ -0,0 +1,32 @@ +// Copyright 2016 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_PROOF_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_PROOF_H_ + +#include <string> + +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// Contains the crypto-related data provided by ProofSource +struct QUIC_EXPORT_PRIVATE QuicCryptoProof { + QuicCryptoProof(); + + // Signature generated by ProofSource + std::string signature; + // SCTList (RFC6962) to be sent to the client, if it supports receiving it. + std::string leaf_cert_scts; + // Should the Expect-CT header be sent on the connection where the + // certificate is used. + bool send_expect_ct_header; + // Did the selected leaf certificate contain a SubjectAltName that included + // the requested SNI. + bool cert_matched_sni; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_PROOF_H_
diff --git a/quiche/quic/core/crypto/quic_crypto_server_config.cc b/quiche/quic/core/crypto/quic_crypto_server_config.cc new file mode 100644 index 0000000..aa2d6e0 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_server_config.cc
@@ -0,0 +1,1918 @@ +// 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 "quiche/quic/core/crypto/quic_crypto_server_config.h" + +#include <algorithm> +#include <cstdlib> +#include <memory> +#include <string> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "third_party/boringssl/src/include/openssl/sha.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h" +#include "quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h" +#include "quiche/quic/core/crypto/cert_compressor.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_encrypter.h" +#include "quiche/quic/core/crypto/channel_id.h" +#include "quiche/quic/core/crypto/crypto_framer.h" +#include "quiche/quic/core/crypto/crypto_handshake_message.h" +#include "quiche/quic/core/crypto/crypto_utils.h" +#include "quiche/quic/core/crypto/curve25519_key_exchange.h" +#include "quiche/quic/core/crypto/key_exchange.h" +#include "quiche/quic/core/crypto/p256_key_exchange.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/crypto/quic_decrypter.h" +#include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/crypto/quic_hkdf.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/crypto/tls_server_connection.h" +#include "quiche/quic/core/proto/crypto_server_config_proto.h" +#include "quiche/quic/core/proto/source_address_token_proto.h" +#include "quiche/quic/core/quic_clock.h" +#include "quiche/quic/core/quic_connection_context.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/core/quic_socket_address_coder.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" +#include "quiche/quic/platform/api/quic_hostname_utils.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/quic/platform/api/quic_testvalue.h" +#include "quiche/common/platform/api/quiche_reference_counted.h" + +namespace quic { + +namespace { + +// kMultiplier is the multiple of the CHLO message size that a REJ message +// must stay under when the client doesn't present a valid source-address +// token. This is used to protect QUIC from amplification attacks. +// TODO(rch): Reduce this to 2 again once b/25933682 is fixed. +const size_t kMultiplier = 3; + +const int kMaxTokenAddresses = 4; + +std::string DeriveSourceAddressTokenKey( + absl::string_view source_address_token_secret) { + QuicHKDF hkdf(source_address_token_secret, absl::string_view() /* no salt */, + "QUIC source address token key", + CryptoSecretBoxer::GetKeySize(), 0 /* no fixed IV needed */, + 0 /* no subkey secret */); + return std::string(hkdf.server_write_key()); +} + +// Default source for creating KeyExchange objects. +class DefaultKeyExchangeSource : public KeyExchangeSource { + public: + DefaultKeyExchangeSource() = default; + ~DefaultKeyExchangeSource() override = default; + + std::unique_ptr<AsynchronousKeyExchange> Create( + std::string /*server_config_id*/, + bool /* is_fallback */, + QuicTag type, + absl::string_view private_key) override { + if (private_key.empty()) { + QUIC_LOG(WARNING) << "Server config contains key exchange method without " + "corresponding private key of type " + << QuicTagToString(type); + return nullptr; + } + + std::unique_ptr<SynchronousKeyExchange> ka = + CreateLocalSynchronousKeyExchange(type, private_key); + if (!ka) { + QUIC_LOG(WARNING) << "Failed to create key exchange method of type " + << QuicTagToString(type); + } + return ka; + } +}; + +// Returns true if the PDMD field from the client hello demands an X509 +// certificate. +bool ClientDemandsX509Proof(const CryptoHandshakeMessage& client_hello) { + QuicTagVector their_proof_demands; + + if (client_hello.GetTaglist(kPDMD, &their_proof_demands) != QUIC_NO_ERROR) { + return false; + } + + for (const QuicTag tag : their_proof_demands) { + if (tag == kX509) { + return true; + } + } + return false; +} + +std::string FormatCryptoHandshakeMessageForTrace( + const CryptoHandshakeMessage* message) { + if (message == nullptr) { + return "<null message>"; + } + + std::string s = QuicTagToString(message->tag()); + + // Append the reasons for REJ. + if (const auto it = message->tag_value_map().find(kRREJ); + it != message->tag_value_map().end()) { + const std::string& value = it->second; + // The value is a vector of uint32_t(s). + if (value.size() % sizeof(uint32_t) == 0) { + absl::StrAppend(&s, " RREJ:["); + // Append comma-separated list of reasons to |s|. + for (size_t j = 0; j < value.size(); j += sizeof(uint32_t)) { + uint32_t reason; + memcpy(&reason, value.data() + j, sizeof(reason)); + if (j > 0) { + absl::StrAppend(&s, ","); + } + absl::StrAppend(&s, CryptoUtils::HandshakeFailureReasonToString( + static_cast<HandshakeFailureReason>(reason))); + } + absl::StrAppend(&s, "]"); + } else { + absl::StrAppendFormat(&s, " RREJ:[unexpected length:%u]", value.size()); + } + } + + return s; +} + +} // namespace + +// static +std::unique_ptr<KeyExchangeSource> KeyExchangeSource::Default() { + return std::make_unique<DefaultKeyExchangeSource>(); +} + +class ValidateClientHelloHelper { + public: + // Note: stores a pointer to a unique_ptr, and std::moves the unique_ptr when + // ValidationComplete is called. + ValidateClientHelloHelper( + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + result, + std::unique_ptr<ValidateClientHelloResultCallback>* done_cb) + : result_(std::move(result)), done_cb_(done_cb) {} + ValidateClientHelloHelper(const ValidateClientHelloHelper&) = delete; + ValidateClientHelloHelper& operator=(const ValidateClientHelloHelper&) = + delete; + + ~ValidateClientHelloHelper() { + QUIC_BUG_IF(quic_bug_12963_1, done_cb_ != nullptr) + << "Deleting ValidateClientHelloHelper with a pending callback."; + } + + void ValidationComplete( + QuicErrorCode error_code, + const char* error_details, + std::unique_ptr<ProofSource::Details> proof_source_details) { + result_->error_code = error_code; + result_->error_details = error_details; + (*done_cb_)->Run(std::move(result_), std::move(proof_source_details)); + DetachCallback(); + } + + void DetachCallback() { + QUIC_BUG_IF(quic_bug_10630_1, done_cb_ == nullptr) + << "Callback already detached."; + done_cb_ = nullptr; + } + + private: + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + result_; + std::unique_ptr<ValidateClientHelloResultCallback>* done_cb_; +}; + +// static +const char QuicCryptoServerConfig::TESTING[] = "secret string for testing"; + +ClientHelloInfo::ClientHelloInfo(const QuicIpAddress& in_client_ip, + QuicWallTime in_now) + : client_ip(in_client_ip), now(in_now), valid_source_address_token(false) {} + +ClientHelloInfo::ClientHelloInfo(const ClientHelloInfo& other) = default; + +ClientHelloInfo::~ClientHelloInfo() {} + +PrimaryConfigChangedCallback::PrimaryConfigChangedCallback() {} + +PrimaryConfigChangedCallback::~PrimaryConfigChangedCallback() {} + +ValidateClientHelloResultCallback::Result::Result( + const CryptoHandshakeMessage& in_client_hello, + QuicIpAddress in_client_ip, + QuicWallTime in_now) + : client_hello(in_client_hello), + info(in_client_ip, in_now), + error_code(QUIC_NO_ERROR) {} + +ValidateClientHelloResultCallback::Result::~Result() {} + +ValidateClientHelloResultCallback::ValidateClientHelloResultCallback() {} + +ValidateClientHelloResultCallback::~ValidateClientHelloResultCallback() {} + +ProcessClientHelloResultCallback::ProcessClientHelloResultCallback() {} + +ProcessClientHelloResultCallback::~ProcessClientHelloResultCallback() {} + +QuicCryptoServerConfig::ConfigOptions::ConfigOptions() + : expiry_time(QuicWallTime::Zero()), + channel_id_enabled(false), + p256(false) {} + +QuicCryptoServerConfig::ConfigOptions::ConfigOptions( + const ConfigOptions& other) = default; + +QuicCryptoServerConfig::ConfigOptions::~ConfigOptions() {} + +QuicCryptoServerConfig::ProcessClientHelloContext:: + ~ProcessClientHelloContext() { + if (done_cb_ != nullptr) { + QUIC_LOG(WARNING) + << "Deleting ProcessClientHelloContext with a pending callback."; + } +} + +void QuicCryptoServerConfig::ProcessClientHelloContext::Fail( + QuicErrorCode error, + const std::string& error_details) { + QUIC_TRACEPRINTF("ProcessClientHello failed: error=%s, details=%s", + QuicErrorCodeToString(error), error_details); + done_cb_->Run(error, error_details, nullptr, nullptr, nullptr); + done_cb_ = nullptr; +} + +void QuicCryptoServerConfig::ProcessClientHelloContext::Succeed( + std::unique_ptr<CryptoHandshakeMessage> message, + std::unique_ptr<DiversificationNonce> diversification_nonce, + std::unique_ptr<ProofSource::Details> proof_source_details) { + QUIC_TRACEPRINTF("ProcessClientHello succeeded: %s", + FormatCryptoHandshakeMessageForTrace(message.get())); + + done_cb_->Run(QUIC_NO_ERROR, std::string(), std::move(message), + std::move(diversification_nonce), + std::move(proof_source_details)); + done_cb_ = nullptr; +} + +QuicCryptoServerConfig::QuicCryptoServerConfig( + absl::string_view source_address_token_secret, + QuicRandom* server_nonce_entropy, + std::unique_ptr<ProofSource> proof_source, + std::unique_ptr<KeyExchangeSource> key_exchange_source) + : replay_protection_(true), + chlo_multiplier_(kMultiplier), + configs_lock_(), + primary_config_(nullptr), + next_config_promotion_time_(QuicWallTime::Zero()), + proof_source_(std::move(proof_source)), + key_exchange_source_(std::move(key_exchange_source)), + ssl_ctx_(TlsServerConnection::CreateSslCtx(proof_source_.get())), + source_address_token_future_secs_(3600), + source_address_token_lifetime_secs_(86400), + enable_serving_sct_(false), + rejection_observer_(nullptr), + pad_rej_(true), + pad_shlo_(true), + validate_chlo_size_(true), + validate_source_address_token_(true) { + QUICHE_DCHECK(proof_source_.get()); + source_address_token_boxer_.SetKeys( + {DeriveSourceAddressTokenKey(source_address_token_secret)}); + + // Generate a random key and orbit for server nonces. + server_nonce_entropy->RandBytes(server_nonce_orbit_, + sizeof(server_nonce_orbit_)); + const size_t key_size = server_nonce_boxer_.GetKeySize(); + std::unique_ptr<uint8_t[]> key_bytes(new uint8_t[key_size]); + server_nonce_entropy->RandBytes(key_bytes.get(), key_size); + + server_nonce_boxer_.SetKeys( + {std::string(reinterpret_cast<char*>(key_bytes.get()), key_size)}); +} + +QuicCryptoServerConfig::~QuicCryptoServerConfig() {} + +// static +QuicServerConfigProtobuf QuicCryptoServerConfig::GenerateConfig( + QuicRandom* rand, + const QuicClock* clock, + const ConfigOptions& options) { + CryptoHandshakeMessage msg; + + const std::string curve25519_private_key = + Curve25519KeyExchange::NewPrivateKey(rand); + std::unique_ptr<Curve25519KeyExchange> curve25519 = + Curve25519KeyExchange::New(curve25519_private_key); + absl::string_view curve25519_public_value = curve25519->public_value(); + + std::string encoded_public_values; + // First three bytes encode the length of the public value. + QUICHE_DCHECK_LT(curve25519_public_value.size(), (1U << 24)); + encoded_public_values.push_back( + static_cast<char>(curve25519_public_value.size())); + encoded_public_values.push_back( + static_cast<char>(curve25519_public_value.size() >> 8)); + encoded_public_values.push_back( + static_cast<char>(curve25519_public_value.size() >> 16)); + encoded_public_values.append(curve25519_public_value.data(), + curve25519_public_value.size()); + + std::string p256_private_key; + if (options.p256) { + p256_private_key = P256KeyExchange::NewPrivateKey(); + std::unique_ptr<P256KeyExchange> p256( + P256KeyExchange::New(p256_private_key)); + absl::string_view p256_public_value = p256->public_value(); + + QUICHE_DCHECK_LT(p256_public_value.size(), (1U << 24)); + encoded_public_values.push_back( + static_cast<char>(p256_public_value.size())); + encoded_public_values.push_back( + static_cast<char>(p256_public_value.size() >> 8)); + encoded_public_values.push_back( + static_cast<char>(p256_public_value.size() >> 16)); + encoded_public_values.append(p256_public_value.data(), + p256_public_value.size()); + } + + msg.set_tag(kSCFG); + if (options.p256) { + msg.SetVector(kKEXS, QuicTagVector{kC255, kP256}); + } else { + msg.SetVector(kKEXS, QuicTagVector{kC255}); + } + msg.SetVector(kAEAD, QuicTagVector{kAESG, kCC20}); + msg.SetStringPiece(kPUBS, encoded_public_values); + + if (options.expiry_time.IsZero()) { + const QuicWallTime now = clock->WallNow(); + const QuicWallTime expiry = now.Add(QuicTime::Delta::FromSeconds( + 60 * 60 * 24 * 180 /* 180 days, ~six months */)); + const uint64_t expiry_seconds = expiry.ToUNIXSeconds(); + msg.SetValue(kEXPY, expiry_seconds); + } else { + msg.SetValue(kEXPY, options.expiry_time.ToUNIXSeconds()); + } + + char orbit_bytes[kOrbitSize]; + if (options.orbit.size() == sizeof(orbit_bytes)) { + memcpy(orbit_bytes, options.orbit.data(), sizeof(orbit_bytes)); + } else { + QUICHE_DCHECK(options.orbit.empty()); + rand->RandBytes(orbit_bytes, sizeof(orbit_bytes)); + } + msg.SetStringPiece(kORBT, + absl::string_view(orbit_bytes, sizeof(orbit_bytes))); + + if (options.channel_id_enabled) { + msg.SetVector(kPDMD, QuicTagVector{kCHID}); + } + + if (options.id.empty()) { + // We need to ensure that the SCID changes whenever the server config does + // thus we make it a hash of the rest of the server config. + std::unique_ptr<QuicData> serialized = + CryptoFramer::ConstructHandshakeMessage(msg); + + uint8_t scid_bytes[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast<const uint8_t*>(serialized->data()), + serialized->length(), scid_bytes); + // The SCID is a truncated SHA-256 digest. + static_assert(16 <= SHA256_DIGEST_LENGTH, "SCID length too high."); + msg.SetStringPiece( + kSCID, + absl::string_view(reinterpret_cast<const char*>(scid_bytes), 16)); + } else { + msg.SetStringPiece(kSCID, options.id); + } + // Don't put new tags below this point. The SCID generation should hash over + // everything but itself and so extra tags should be added prior to the + // preceding if block. + + std::unique_ptr<QuicData> serialized = + CryptoFramer::ConstructHandshakeMessage(msg); + + QuicServerConfigProtobuf config; + config.set_config(std::string(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; +} + +std::unique_ptr<CryptoHandshakeMessage> QuicCryptoServerConfig::AddConfig( + const QuicServerConfigProtobuf& protobuf, + const QuicWallTime now) { + std::unique_ptr<CryptoHandshakeMessage> msg = + CryptoFramer::ParseMessage(protobuf.config()); + + if (!msg) { + QUIC_LOG(WARNING) << "Failed to parse server config message"; + return nullptr; + } + + quiche::QuicheReferenceCountedPointer<Config> config = + ParseConfigProtobuf(protobuf, /* is_fallback = */ false); + if (!config) { + QUIC_LOG(WARNING) << "Failed to parse server config message"; + return nullptr; + } + + { + QuicWriterMutexLock locked(&configs_lock_); + if (configs_.find(config->id) != configs_.end()) { + QUIC_LOG(WARNING) << "Failed to add config because another with the same " + "server config id already exists: " + << absl::BytesToHexString(config->id); + return nullptr; + } + + configs_[config->id] = config; + SelectNewPrimaryConfig(now); + QUICHE_DCHECK(primary_config_.get()); + QUICHE_DCHECK_EQ(configs_.find(primary_config_->id)->second.get(), + primary_config_.get()); + } + + return msg; +} + +std::unique_ptr<CryptoHandshakeMessage> +QuicCryptoServerConfig::AddDefaultConfig(QuicRandom* rand, + const QuicClock* clock, + const ConfigOptions& options) { + return AddConfig(GenerateConfig(rand, clock, options), clock->WallNow()); +} + +bool QuicCryptoServerConfig::SetConfigs( + const std::vector<QuicServerConfigProtobuf>& protobufs, + const QuicServerConfigProtobuf* fallback_protobuf, + const QuicWallTime now) { + std::vector<quiche::QuicheReferenceCountedPointer<Config>> parsed_configs; + for (auto& protobuf : protobufs) { + quiche::QuicheReferenceCountedPointer<Config> config = + ParseConfigProtobuf(protobuf, /* is_fallback = */ false); + if (!config) { + QUIC_LOG(WARNING) << "Rejecting QUIC configs because of above errors"; + return false; + } + + parsed_configs.push_back(config); + } + + quiche::QuicheReferenceCountedPointer<Config> fallback_config; + if (fallback_protobuf != nullptr) { + fallback_config = + ParseConfigProtobuf(*fallback_protobuf, /* is_fallback = */ true); + if (!fallback_config) { + QUIC_LOG(WARNING) << "Rejecting QUIC configs because of above errors"; + return false; + } + QUIC_LOG(INFO) << "Fallback config has scid " + << absl::BytesToHexString(fallback_config->id); + parsed_configs.push_back(fallback_config); + } else { + QUIC_LOG(INFO) << "No fallback config provided"; + } + + if (parsed_configs.empty()) { + QUIC_LOG(WARNING) + << "Rejecting QUIC configs because new config list is empty."; + return false; + } + + QUIC_LOG(INFO) << "Updating configs:"; + + QuicWriterMutexLock locked(&configs_lock_); + ConfigMap new_configs; + + for (const quiche::QuicheReferenceCountedPointer<Config>& config : + parsed_configs) { + auto it = configs_.find(config->id); + if (it != configs_.end()) { + QUIC_LOG(INFO) << "Keeping scid: " << absl::BytesToHexString(config->id) + << " orbit: " + << absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char*>(config->orbit), + kOrbitSize)) + << " new primary_time " + << config->primary_time.ToUNIXSeconds() + << " old primary_time " + << it->second->primary_time.ToUNIXSeconds() + << " new priority " << config->priority << " old priority " + << it->second->priority; + // Update primary_time and priority. + it->second->primary_time = config->primary_time; + it->second->priority = config->priority; + new_configs.insert(*it); + } else { + QUIC_LOG(INFO) << "Adding scid: " << absl::BytesToHexString(config->id) + << " orbit: " + << absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char*>(config->orbit), + kOrbitSize)) + << " primary_time " << config->primary_time.ToUNIXSeconds() + << " priority " << config->priority; + new_configs.emplace(config->id, config); + } + } + + configs_ = std::move(new_configs); + fallback_config_ = fallback_config; + SelectNewPrimaryConfig(now); + QUICHE_DCHECK(primary_config_.get()); + QUICHE_DCHECK_EQ(configs_.find(primary_config_->id)->second.get(), + primary_config_.get()); + + return true; +} + +void QuicCryptoServerConfig::SetSourceAddressTokenKeys( + const std::vector<std::string>& keys) { + // TODO(b/208866709) + source_address_token_boxer_.SetKeys(keys); +} + +std::vector<std::string> QuicCryptoServerConfig::GetConfigIds() const { + QuicReaderMutexLock locked(&configs_lock_); + std::vector<std::string> scids; + for (auto it = configs_.begin(); it != configs_.end(); ++it) { + scids.push_back(it->first); + } + return scids; +} + +void QuicCryptoServerConfig::ValidateClientHello( + const CryptoHandshakeMessage& client_hello, + const QuicSocketAddress& client_address, + const QuicSocketAddress& server_address, QuicTransportVersion version, + const QuicClock* clock, + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config, + std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const { + const QuicWallTime now(clock->WallNow()); + + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + result(new ValidateClientHelloResultCallback::Result( + client_hello, client_address.host(), now)); + + absl::string_view requested_scid; + // We ignore here the return value from GetStringPiece. If there is no SCID + // tag, EvaluateClientHello will discover that because GetCurrentConfigs will + // not have found the requested config (i.e. because none of the configs will + // have an empty string as its id). + client_hello.GetStringPiece(kSCID, &requested_scid); + Configs configs; + if (!GetCurrentConfigs(now, requested_scid, + /* old_primary_config = */ nullptr, &configs)) { + result->error_code = QUIC_CRYPTO_INTERNAL_ERROR; + result->error_details = "No configurations loaded"; + } + signed_config->config = configs.primary; + + if (result->error_code == QUIC_NO_ERROR) { + // QUIC requires a new proof for each CHLO so clear any existing proof. + signed_config->chain = nullptr; + signed_config->proof.signature = ""; + signed_config->proof.leaf_cert_scts = ""; + EvaluateClientHello(server_address, client_address, version, configs, + result, std::move(done_cb)); + } else { + done_cb->Run(result, /* details = */ nullptr); + } +} + +class QuicCryptoServerConfig::ProcessClientHelloCallback + : public ProofSource::Callback { + public: + ProcessClientHelloCallback(const QuicCryptoServerConfig* config, + std::unique_ptr<ProcessClientHelloContext> context, + const Configs& configs) + : config_(config), context_(std::move(context)), configs_(configs) {} + + void Run( + bool ok, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const QuicCryptoProof& proof, + std::unique_ptr<ProofSource::Details> details) override { + if (ok) { + context_->signed_config()->chain = chain; + context_->signed_config()->proof = proof; + } + config_->ProcessClientHelloAfterGetProof(!ok, std::move(details), + std::move(context_), configs_); + } + + private: + const QuicCryptoServerConfig* config_; + std::unique_ptr<ProcessClientHelloContext> context_; + const Configs configs_; +}; + +class QuicCryptoServerConfig::ProcessClientHelloAfterGetProofCallback + : public AsynchronousKeyExchange::Callback { + public: + ProcessClientHelloAfterGetProofCallback( + const QuicCryptoServerConfig* config, + std::unique_ptr<ProofSource::Details> proof_source_details, + QuicTag key_exchange_type, + std::unique_ptr<CryptoHandshakeMessage> out, + absl::string_view public_value, + std::unique_ptr<ProcessClientHelloContext> context, + const Configs& configs) + : config_(config), + proof_source_details_(std::move(proof_source_details)), + key_exchange_type_(key_exchange_type), + out_(std::move(out)), + public_value_(public_value), + context_(std::move(context)), + configs_(configs) {} + + void Run(bool ok) override { + config_->ProcessClientHelloAfterCalculateSharedKeys( + !ok, std::move(proof_source_details_), key_exchange_type_, + std::move(out_), public_value_, std::move(context_), configs_); + } + + private: + const QuicCryptoServerConfig* config_; + std::unique_ptr<ProofSource::Details> proof_source_details_; + const QuicTag key_exchange_type_; + std::unique_ptr<CryptoHandshakeMessage> out_; + const std::string public_value_; + std::unique_ptr<ProcessClientHelloContext> context_; + const Configs configs_; + std::unique_ptr<ProcessClientHelloResultCallback> done_cb_; +}; + +class QuicCryptoServerConfig::SendRejectWithFallbackConfigCallback + : public ProofSource::Callback { + public: + SendRejectWithFallbackConfigCallback( + const QuicCryptoServerConfig* config, + std::unique_ptr<ProcessClientHelloContext> context, + quiche::QuicheReferenceCountedPointer<Config> fallback_config) + : config_(config), + context_(std::move(context)), + fallback_config_(fallback_config) {} + + // Capture |chain| and |proof| into the signed config, and then invoke + // SendRejectWithFallbackConfigAfterGetProof. + void Run( + bool ok, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const QuicCryptoProof& proof, + std::unique_ptr<ProofSource::Details> details) override { + if (ok) { + context_->signed_config()->chain = chain; + context_->signed_config()->proof = proof; + } + config_->SendRejectWithFallbackConfigAfterGetProof( + !ok, std::move(details), std::move(context_), fallback_config_); + } + + private: + const QuicCryptoServerConfig* config_; + std::unique_ptr<ProcessClientHelloContext> context_; + quiche::QuicheReferenceCountedPointer<Config> fallback_config_; +}; + +void QuicCryptoServerConfig::ProcessClientHello( + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + validate_chlo_result, + bool reject_only, QuicConnectionId connection_id, + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, ParsedQuicVersion version, + const ParsedQuicVersionVector& supported_versions, const QuicClock* clock, + QuicRandom* rand, QuicCompressedCertsCache* compressed_certs_cache, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + params, + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config, + QuicByteCount total_framing_overhead, QuicByteCount chlo_packet_size, + std::unique_ptr<ProcessClientHelloResultCallback> done_cb) const { + QUICHE_DCHECK(done_cb); + auto context = std::make_unique<ProcessClientHelloContext>( + validate_chlo_result, reject_only, connection_id, server_address, + client_address, version, supported_versions, clock, rand, + compressed_certs_cache, params, signed_config, total_framing_overhead, + chlo_packet_size, std::move(done_cb)); + + // Verify that various parts of the CHLO are valid + std::string error_details; + QuicErrorCode valid = CryptoUtils::ValidateClientHello( + context->client_hello(), context->version(), + context->supported_versions(), &error_details); + if (valid != QUIC_NO_ERROR) { + context->Fail(valid, error_details); + return; + } + + absl::string_view requested_scid; + context->client_hello().GetStringPiece(kSCID, &requested_scid); + Configs configs; + if (!GetCurrentConfigs(context->clock()->WallNow(), requested_scid, + signed_config->config, &configs)) { + context->Fail(QUIC_CRYPTO_INTERNAL_ERROR, "No configurations loaded"); + return; + } + + if (context->validate_chlo_result()->error_code != QUIC_NO_ERROR) { + context->Fail(context->validate_chlo_result()->error_code, + context->validate_chlo_result()->error_details); + return; + } + + if (!ClientDemandsX509Proof(context->client_hello())) { + context->Fail(QUIC_UNSUPPORTED_PROOF_DEMAND, "Missing or invalid PDMD"); + return; + } + + // No need to get a new proof if one was already generated. + if (!context->signed_config()->chain) { + const std::string chlo_hash = CryptoUtils::HashHandshakeMessage( + context->client_hello(), Perspective::IS_SERVER); + const QuicSocketAddress server_address = context->server_address(); + const std::string sni = std::string(context->info().sni); + const QuicTransportVersion transport_version = context->transport_version(); + + auto cb = std::make_unique<ProcessClientHelloCallback>( + this, std::move(context), configs); + + QUICHE_DCHECK(proof_source_.get()); + proof_source_->GetProof(server_address, client_address, sni, + configs.primary->serialized, transport_version, + chlo_hash, std::move(cb)); + return; + } + + ProcessClientHelloAfterGetProof( + /* found_error = */ false, /* proof_source_details = */ nullptr, + std::move(context), configs); +} + +void QuicCryptoServerConfig::ProcessClientHelloAfterGetProof( + bool found_error, + std::unique_ptr<ProofSource::Details> proof_source_details, + std::unique_ptr<ProcessClientHelloContext> context, + const Configs& configs) const { + QUIC_BUG_IF(quic_bug_12963_2, + !QuicUtils::IsConnectionIdValidForVersion( + context->connection_id(), context->transport_version())) + << "ProcessClientHelloAfterGetProof: attempted to use connection ID " + << context->connection_id() << " which is invalid with version " + << context->version(); + + if (context->info().reject_reasons.empty()) { + if (!context->signed_config() || !context->signed_config()->chain) { + // No chain. + context->validate_chlo_result()->info.reject_reasons.push_back( + SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE); + } else if (!ValidateExpectedLeafCertificate( + context->client_hello(), + context->signed_config()->chain->certs)) { + // Has chain but leaf is invalid. + context->validate_chlo_result()->info.reject_reasons.push_back( + INVALID_EXPECTED_LEAF_CERTIFICATE); + } + } + + if (found_error) { + context->Fail(QUIC_HANDSHAKE_FAILED, "Failed to get proof"); + return; + } + + auto out_diversification_nonce = std::make_unique<DiversificationNonce>(); + + absl::string_view cert_sct; + if (context->client_hello().GetStringPiece(kCertificateSCTTag, &cert_sct) && + cert_sct.empty()) { + context->params()->sct_supported_by_client = true; + } + + auto out = std::make_unique<CryptoHandshakeMessage>(); + if (!context->info().reject_reasons.empty() || !configs.requested) { + BuildRejectionAndRecordStats(*context, *configs.primary, + context->info().reject_reasons, out.get()); + context->Succeed(std::move(out), std::move(out_diversification_nonce), + std::move(proof_source_details)); + return; + } + + if (context->reject_only()) { + context->Succeed(std::move(out), std::move(out_diversification_nonce), + std::move(proof_source_details)); + return; + } + + QuicTagVector their_aeads; + QuicTagVector their_key_exchanges; + if (context->client_hello().GetTaglist(kAEAD, &their_aeads) != + QUIC_NO_ERROR || + context->client_hello().GetTaglist(kKEXS, &their_key_exchanges) != + QUIC_NO_ERROR || + their_aeads.size() != 1 || their_key_exchanges.size() != 1) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "Missing or invalid AEAD or KEXS"); + return; + } + + size_t key_exchange_index; + if (!FindMutualQuicTag(configs.requested->aead, their_aeads, + &context->params()->aead, nullptr) || + !FindMutualQuicTag(configs.requested->kexs, their_key_exchanges, + &context->params()->key_exchange, + &key_exchange_index)) { + context->Fail(QUIC_CRYPTO_NO_SUPPORT, "Unsupported AEAD or KEXS"); + return; + } + + absl::string_view public_value; + if (!context->client_hello().GetStringPiece(kPUBS, &public_value)) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "Missing public value"); + return; + } + + // Allow testing a specific adversarial case in which a client sends a public + // value of incorrect size. + AdjustTestValue("quic::QuicCryptoServerConfig::public_value_adjust", + &public_value); + + const AsynchronousKeyExchange* key_exchange = + configs.requested->key_exchanges[key_exchange_index].get(); + std::string* initial_premaster_secret = + &context->params()->initial_premaster_secret; + auto cb = std::make_unique<ProcessClientHelloAfterGetProofCallback>( + this, std::move(proof_source_details), key_exchange->type(), + std::move(out), public_value, std::move(context), configs); + key_exchange->CalculateSharedKeyAsync(public_value, initial_premaster_secret, + std::move(cb)); +} + +void QuicCryptoServerConfig::ProcessClientHelloAfterCalculateSharedKeys( + bool found_error, + std::unique_ptr<ProofSource::Details> proof_source_details, + QuicTag key_exchange_type, + std::unique_ptr<CryptoHandshakeMessage> out, + absl::string_view public_value, + std::unique_ptr<ProcessClientHelloContext> context, + const Configs& configs) const { + QUIC_BUG_IF(quic_bug_12963_3, + !QuicUtils::IsConnectionIdValidForVersion( + context->connection_id(), context->transport_version())) + << "ProcessClientHelloAfterCalculateSharedKeys:" + " attempted to use connection ID " + << context->connection_id() << " which is invalid with version " + << context->version(); + + if (found_error) { + // If we are already using the fallback config, or there is no fallback + // config to use, just bail out of the handshake. + if (configs.fallback == nullptr || + context->signed_config()->config == configs.fallback) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "Failed to calculate shared key"); + } else { + SendRejectWithFallbackConfig(std::move(context), configs.fallback); + } + return; + } + + if (!context->info().sni.empty()) { + context->params()->sni = + QuicHostnameUtils::NormalizeHostname(context->info().sni); + } + + std::string hkdf_suffix; + const QuicData& client_hello_serialized = + context->client_hello().GetSerialized(); + hkdf_suffix.reserve(context->connection_id().length() + + client_hello_serialized.length() + + configs.requested->serialized.size()); + hkdf_suffix.append(context->connection_id().data(), + context->connection_id().length()); + hkdf_suffix.append(client_hello_serialized.data(), + client_hello_serialized.length()); + hkdf_suffix.append(configs.requested->serialized); + QUICHE_DCHECK(proof_source_.get()); + if (context->signed_config()->chain->certs.empty()) { + context->Fail(QUIC_CRYPTO_INTERNAL_ERROR, "Failed to get certs"); + return; + } + hkdf_suffix.append(context->signed_config()->chain->certs.at(0)); + + absl::string_view cetv_ciphertext; + if (configs.requested->channel_id_enabled && + context->client_hello().GetStringPiece(kCETV, &cetv_ciphertext)) { + CryptoHandshakeMessage client_hello_copy(context->client_hello()); + client_hello_copy.Erase(kCETV); + client_hello_copy.Erase(kPAD); + + const QuicData& client_hello_copy_serialized = + client_hello_copy.GetSerialized(); + std::string hkdf_input; + hkdf_input.append(QuicCryptoConfig::kCETVLabel, + strlen(QuicCryptoConfig::kCETVLabel) + 1); + hkdf_input.append(context->connection_id().data(), + context->connection_id().length()); + hkdf_input.append(client_hello_copy_serialized.data(), + client_hello_copy_serialized.length()); + hkdf_input.append(configs.requested->serialized); + + CrypterPair crypters; + if (!CryptoUtils::DeriveKeys( + context->version(), context->params()->initial_premaster_secret, + context->params()->aead, context->info().client_nonce, + context->info().server_nonce, pre_shared_key_, hkdf_input, + Perspective::IS_SERVER, CryptoUtils::Diversification::Never(), + &crypters, nullptr /* subkey secret */)) { + context->Fail(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED, + "Symmetric key setup failed"); + return; + } + + char plaintext[kMaxOutgoingPacketSize]; + size_t plaintext_length = 0; + const bool success = crypters.decrypter->DecryptPacket( + 0 /* packet number */, absl::string_view() /* associated data */, + cetv_ciphertext, plaintext, &plaintext_length, kMaxOutgoingPacketSize); + if (!success) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "CETV decryption failure"); + return; + } + std::unique_ptr<CryptoHandshakeMessage> cetv(CryptoFramer::ParseMessage( + absl::string_view(plaintext, plaintext_length))); + if (!cetv) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, "CETV parse error"); + return; + } + + absl::string_view key, signature; + if (cetv->GetStringPiece(kCIDK, &key) && + cetv->GetStringPiece(kCIDS, &signature)) { + if (!ChannelIDVerifier::Verify(key, hkdf_input, signature)) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "ChannelID signature failure"); + return; + } + + context->params()->channel_id = std::string(key); + } + } + + std::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); + + auto out_diversification_nonce = std::make_unique<DiversificationNonce>(); + context->rand()->RandBytes(out_diversification_nonce->data(), + out_diversification_nonce->size()); + CryptoUtils::Diversification diversification = + CryptoUtils::Diversification::Now(out_diversification_nonce.get()); + if (!CryptoUtils::DeriveKeys( + context->version(), context->params()->initial_premaster_secret, + context->params()->aead, context->info().client_nonce, + context->info().server_nonce, pre_shared_key_, hkdf_input, + Perspective::IS_SERVER, diversification, + &context->params()->initial_crypters, + &context->params()->initial_subkey_secret)) { + context->Fail(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED, + "Symmetric key setup failed"); + return; + } + + std::string forward_secure_public_value; + std::unique_ptr<SynchronousKeyExchange> forward_secure_key_exchange = + CreateLocalSynchronousKeyExchange(key_exchange_type, context->rand()); + if (!forward_secure_key_exchange) { + QUIC_DLOG(WARNING) << "Failed to create keypair"; + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "Failed to create keypair"); + return; + } + + forward_secure_public_value = + std::string(forward_secure_key_exchange->public_value()); + if (!forward_secure_key_exchange->CalculateSharedKeySync( + public_value, &context->params()->forward_secure_premaster_secret)) { + context->Fail(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "Invalid public value"); + return; + } + + std::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); + + std::string shlo_nonce; + shlo_nonce = NewServerNonce(context->rand(), context->info().now); + out->SetStringPiece(kServerNonceTag, shlo_nonce); + + if (!CryptoUtils::DeriveKeys( + context->version(), + context->params()->forward_secure_premaster_secret, + context->params()->aead, context->info().client_nonce, + shlo_nonce.empty() ? context->info().server_nonce : shlo_nonce, + pre_shared_key_, forward_secure_hkdf_input, Perspective::IS_SERVER, + CryptoUtils::Diversification::Never(), + &context->params()->forward_secure_crypters, + &context->params()->subkey_secret)) { + context->Fail(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED, + "Symmetric key setup failed"); + return; + } + + out->set_tag(kSHLO); + out->SetVersionVector(kVER, context->supported_versions()); + out->SetStringPiece( + kSourceAddressTokenTag, + NewSourceAddressToken(*configs.requested->source_address_token_boxer, + context->info().source_address_tokens, + context->client_address().host(), context->rand(), + context->info().now, nullptr)); + QuicSocketAddressCoder address_coder(context->client_address()); + out->SetStringPiece(kCADR, address_coder.Encode()); + out->SetStringPiece(kPUBS, forward_secure_public_value); + + context->Succeed(std::move(out), std::move(out_diversification_nonce), + std::move(proof_source_details)); +} + +void QuicCryptoServerConfig::SendRejectWithFallbackConfig( + std::unique_ptr<ProcessClientHelloContext> context, + quiche::QuicheReferenceCountedPointer<Config> fallback_config) const { + // We failed to calculate a shared initial key, likely because we tried to use + // a remote key-exchange service which could not be reached. We want to send + // a REJ which tells the client to use a different ServerConfig which + // corresponds to a local keypair. To generate the REJ we need to request a + // new proof. + const std::string chlo_hash = CryptoUtils::HashHandshakeMessage( + context->client_hello(), Perspective::IS_SERVER); + const QuicSocketAddress server_address = context->server_address(); + const std::string sni(context->info().sni); + const QuicTransportVersion transport_version = context->transport_version(); + + const QuicSocketAddress& client_address = context->client_address(); + auto cb = std::make_unique<SendRejectWithFallbackConfigCallback>( + this, std::move(context), fallback_config); + proof_source_->GetProof(server_address, client_address, sni, + fallback_config->serialized, transport_version, + chlo_hash, std::move(cb)); +} + +void QuicCryptoServerConfig::SendRejectWithFallbackConfigAfterGetProof( + bool found_error, + std::unique_ptr<ProofSource::Details> proof_source_details, + std::unique_ptr<ProcessClientHelloContext> context, + quiche::QuicheReferenceCountedPointer<Config> fallback_config) const { + if (found_error) { + context->Fail(QUIC_HANDSHAKE_FAILED, "Failed to get proof"); + return; + } + + auto out = std::make_unique<CryptoHandshakeMessage>(); + BuildRejectionAndRecordStats(*context, *fallback_config, + {SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE}, + out.get()); + + context->Succeed(std::move(out), std::make_unique<DiversificationNonce>(), + std::move(proof_source_details)); +} + +quiche::QuicheReferenceCountedPointer<QuicCryptoServerConfig::Config> +QuicCryptoServerConfig::GetConfigWithScid( + absl::string_view requested_scid) const { + configs_lock_.AssertReaderHeld(); + + if (!requested_scid.empty()) { + auto it = configs_.find((std::string(requested_scid))); + if (it != configs_.end()) { + // We'll use the config that the client requested in order to do + // key-agreement. + return quiche::QuicheReferenceCountedPointer<Config>(it->second); + } + } + + return quiche::QuicheReferenceCountedPointer<Config>(); +} + +bool QuicCryptoServerConfig::GetCurrentConfigs( + const QuicWallTime& now, absl::string_view requested_scid, + quiche::QuicheReferenceCountedPointer<Config> old_primary_config, + Configs* configs) const { + QuicReaderMutexLock locked(&configs_lock_); + + if (!primary_config_) { + return false; + } + + if (IsNextConfigReady(now)) { + configs_lock_.ReaderUnlock(); + configs_lock_.WriterLock(); + SelectNewPrimaryConfig(now); + QUICHE_DCHECK(primary_config_.get()); + QUICHE_DCHECK_EQ(configs_.find(primary_config_->id)->second.get(), + primary_config_.get()); + configs_lock_.WriterUnlock(); + configs_lock_.ReaderLock(); + } + + if (old_primary_config != nullptr) { + configs->primary = old_primary_config; + } else { + configs->primary = primary_config_; + } + configs->requested = GetConfigWithScid(requested_scid); + configs->fallback = fallback_config_; + + return true; +} + +// ConfigPrimaryTimeLessThan is a comparator that implements "less than" for +// Config's based on their primary_time. +// static +bool QuicCryptoServerConfig::ConfigPrimaryTimeLessThan( + const quiche::QuicheReferenceCountedPointer<Config>& a, + const quiche::QuicheReferenceCountedPointer<Config>& b) { + if (a->primary_time.IsBefore(b->primary_time) || + b->primary_time.IsBefore(a->primary_time)) { + // Primary times differ. + return a->primary_time.IsBefore(b->primary_time); + } else if (a->priority != b->priority) { + // Primary times are equal, sort backwards by priority. + return a->priority < b->priority; + } else { + // Primary times and priorities are equal, sort by config id. + return a->id < b->id; + } +} + +void QuicCryptoServerConfig::SelectNewPrimaryConfig( + const QuicWallTime now) const { + std::vector<quiche::QuicheReferenceCountedPointer<Config>> configs; + configs.reserve(configs_.size()); + + for (auto it = configs_.begin(); it != configs_.end(); ++it) { + // TODO(avd) Exclude expired configs? + configs.push_back(it->second); + } + + if (configs.empty()) { + if (primary_config_ != nullptr) { + QUIC_BUG(quic_bug_10630_2) + << "No valid QUIC server config. Keeping the current config."; + } else { + QUIC_BUG(quic_bug_10630_3) << "No valid QUIC server config."; + } + return; + } + + std::sort(configs.begin(), configs.end(), ConfigPrimaryTimeLessThan); + + quiche::QuicheReferenceCountedPointer<Config> best_candidate = configs[0]; + + for (size_t i = 0; i < configs.size(); ++i) { + const quiche::QuicheReferenceCountedPointer<Config> config(configs[i]); + if (!config->primary_time.IsAfter(now)) { + if (config->primary_time.IsAfter(best_candidate->primary_time)) { + best_candidate = config; + } + continue; + } + + // This is the first config with a primary_time in the future. Thus the + // previous Config should be the primary and this one should determine the + // next_config_promotion_time_. + quiche::QuicheReferenceCountedPointer<Config> new_primary = best_candidate; + if (i == 0) { + // We need the primary_time of the next config. + if (configs.size() > 1) { + next_config_promotion_time_ = configs[1]->primary_time; + } else { + next_config_promotion_time_ = QuicWallTime::Zero(); + } + } else { + next_config_promotion_time_ = config->primary_time; + } + + if (primary_config_) { + primary_config_->is_primary = false; + } + primary_config_ = new_primary; + new_primary->is_primary = true; + QUIC_DLOG(INFO) << "New primary config. orbit: " + << absl::BytesToHexString( + absl::string_view(reinterpret_cast<const char*>( + primary_config_->orbit), + kOrbitSize)); + if (primary_config_changed_cb_ != nullptr) { + primary_config_changed_cb_->Run(primary_config_->id); + } + + return; + } + + // All config's primary times are in the past. We should make the most recent + // and highest priority candidate primary. + quiche::QuicheReferenceCountedPointer<Config> new_primary = best_candidate; + if (primary_config_) { + primary_config_->is_primary = false; + } + primary_config_ = new_primary; + new_primary->is_primary = true; + QUIC_DLOG(INFO) << "New primary config. orbit: " + << absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char*>(primary_config_->orbit), + kOrbitSize)) + << " scid: " << absl::BytesToHexString(primary_config_->id); + next_config_promotion_time_ = QuicWallTime::Zero(); + if (primary_config_changed_cb_ != nullptr) { + primary_config_changed_cb_->Run(primary_config_->id); + } +} + +void QuicCryptoServerConfig::EvaluateClientHello( + const QuicSocketAddress& /*server_address*/, + const QuicSocketAddress& /*client_address*/, + QuicTransportVersion /*version*/, const Configs& configs, + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + client_hello_state, + std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const { + ValidateClientHelloHelper helper(client_hello_state, &done_cb); + + const CryptoHandshakeMessage& client_hello = client_hello_state->client_hello; + ClientHelloInfo* info = &(client_hello_state->info); + + if (client_hello.GetStringPiece(kSNI, &info->sni) && + !QuicHostnameUtils::IsValidSNI(info->sni)) { + helper.ValidationComplete(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, + "Invalid SNI name", nullptr); + return; + } + + client_hello.GetStringPiece(kUAID, &info->user_agent_id); + + HandshakeFailureReason source_address_token_error = MAX_FAILURE_REASON; + if (validate_source_address_token_) { + absl::string_view srct; + if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct)) { + Config& config = + configs.requested != nullptr ? *configs.requested : *configs.primary; + source_address_token_error = + ParseSourceAddressToken(*config.source_address_token_boxer, srct, + info->source_address_tokens); + + if (source_address_token_error == HANDSHAKE_OK) { + source_address_token_error = ValidateSourceAddressTokens( + info->source_address_tokens, info->client_ip, info->now, + &client_hello_state->cached_network_params); + } + info->valid_source_address_token = + (source_address_token_error == HANDSHAKE_OK); + } else { + source_address_token_error = SOURCE_ADDRESS_TOKEN_INVALID_FAILURE; + } + } else { + source_address_token_error = HANDSHAKE_OK; + info->valid_source_address_token = true; + } + + if (!configs.requested) { + absl::string_view requested_scid; + if (client_hello.GetStringPiece(kSCID, &requested_scid)) { + info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE); + } else { + info->reject_reasons.push_back(SERVER_CONFIG_INCHOATE_HELLO_FAILURE); + } + // No server config with the requested ID. + helper.ValidationComplete(QUIC_NO_ERROR, "", nullptr); + return; + } + + if (!client_hello.GetStringPiece(kNONC, &info->client_nonce)) { + info->reject_reasons.push_back(SERVER_CONFIG_INCHOATE_HELLO_FAILURE); + // Report no client nonce as INCHOATE_HELLO_FAILURE. + helper.ValidationComplete(QUIC_NO_ERROR, "", nullptr); + return; + } + + if (source_address_token_error != HANDSHAKE_OK) { + info->reject_reasons.push_back(source_address_token_error); + // No valid source address token. + } + + if (info->client_nonce.size() != kNonceSize) { + info->reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE); + // Invalid client nonce. + QUIC_LOG_FIRST_N(ERROR, 2) + << "Invalid client nonce: " << client_hello.DebugString(); + QUIC_DLOG(INFO) << "Invalid client nonce."; + } + + // Server nonce is optional, and used for key derivation if present. + client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce); + + // If the server nonce is empty and we're requiring handshake confirmation + // for DoS reasons then we must reject the CHLO. + if (GetQuicReloadableFlag(quic_require_handshake_confirmation) && + info->server_nonce.empty()) { + info->reject_reasons.push_back(SERVER_NONCE_REQUIRED_FAILURE); + } + helper.ValidationComplete(QUIC_NO_ERROR, "", + std::unique_ptr<ProofSource::Details>()); +} + +void QuicCryptoServerConfig::BuildServerConfigUpdateMessage( + QuicTransportVersion version, + absl::string_view chlo_hash, + const SourceAddressTokens& previous_source_address_tokens, + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + const QuicClock* clock, + QuicRandom* rand, + QuicCompressedCertsCache* compressed_certs_cache, + const QuicCryptoNegotiatedParameters& params, + const CachedNetworkParameters* cached_network_params, + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) const { + std::string serialized; + std::string source_address_token; + { + QuicReaderMutexLock locked(&configs_lock_); + serialized = primary_config_->serialized; + source_address_token = NewSourceAddressToken( + *primary_config_->source_address_token_boxer, + previous_source_address_tokens, client_address.host(), rand, + clock->WallNow(), cached_network_params); + } + + CryptoHandshakeMessage message; + message.set_tag(kSCUP); + message.SetStringPiece(kSCFG, serialized); + message.SetStringPiece(kSourceAddressTokenTag, source_address_token); + + auto proof_source_cb = + std::make_unique<BuildServerConfigUpdateMessageProofSourceCallback>( + this, compressed_certs_cache, params, std::move(message), + std::move(cb)); + + proof_source_->GetProof(server_address, client_address, params.sni, + serialized, version, chlo_hash, + std::move(proof_source_cb)); +} + +QuicCryptoServerConfig::BuildServerConfigUpdateMessageProofSourceCallback:: + ~BuildServerConfigUpdateMessageProofSourceCallback() {} + +QuicCryptoServerConfig::BuildServerConfigUpdateMessageProofSourceCallback:: + BuildServerConfigUpdateMessageProofSourceCallback( + const QuicCryptoServerConfig* config, + QuicCompressedCertsCache* compressed_certs_cache, + const QuicCryptoNegotiatedParameters& params, + CryptoHandshakeMessage message, + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) + : config_(config), + compressed_certs_cache_(compressed_certs_cache), + client_cached_cert_hashes_(params.client_cached_cert_hashes), + sct_supported_by_client_(params.sct_supported_by_client), + sni_(params.sni), + message_(std::move(message)), + cb_(std::move(cb)) {} + +void QuicCryptoServerConfig::BuildServerConfigUpdateMessageProofSourceCallback:: + Run(bool ok, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const QuicCryptoProof& proof, + std::unique_ptr<ProofSource::Details> details) { + config_->FinishBuildServerConfigUpdateMessage( + compressed_certs_cache_, client_cached_cert_hashes_, + sct_supported_by_client_, sni_, ok, chain, proof.signature, + proof.leaf_cert_scts, std::move(details), std::move(message_), + std::move(cb_)); +} + +void QuicCryptoServerConfig::FinishBuildServerConfigUpdateMessage( + QuicCompressedCertsCache* compressed_certs_cache, + const std::string& client_cached_cert_hashes, bool sct_supported_by_client, + const std::string& sni, bool ok, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& signature, const std::string& leaf_cert_sct, + std::unique_ptr<ProofSource::Details> /*details*/, + CryptoHandshakeMessage message, + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) const { + if (!ok) { + cb->Run(false, message); + return; + } + + const std::string compressed = + CompressChain(compressed_certs_cache, chain, client_cached_cert_hashes); + + message.SetStringPiece(kCertificateTag, compressed); + message.SetStringPiece(kPROF, signature); + if (sct_supported_by_client && enable_serving_sct_) { + if (leaf_cert_sct.empty()) { + QUIC_LOG_EVERY_N_SEC(WARNING, 60) + << "SCT is expected but it is empty. SNI: " << sni; + } else { + message.SetStringPiece(kCertificateSCTTag, leaf_cert_sct); + } + } + + cb->Run(true, message); +} + +void QuicCryptoServerConfig::BuildRejectionAndRecordStats( + const ProcessClientHelloContext& context, + const Config& config, + const std::vector<uint32_t>& reject_reasons, + CryptoHandshakeMessage* out) const { + BuildRejection(context, config, reject_reasons, out); + if (rejection_observer_ != nullptr) { + rejection_observer_->OnRejectionBuilt(reject_reasons, out); + } +} + +void QuicCryptoServerConfig::BuildRejection( + const ProcessClientHelloContext& context, + const Config& config, + const std::vector<uint32_t>& reject_reasons, + CryptoHandshakeMessage* out) const { + const QuicWallTime now = context.clock()->WallNow(); + + out->set_tag(kREJ); + out->SetStringPiece(kSCFG, config.serialized); + out->SetStringPiece( + kSourceAddressTokenTag, + NewSourceAddressToken( + *config.source_address_token_boxer, + context.info().source_address_tokens, context.info().client_ip, + context.rand(), context.info().now, + &context.validate_chlo_result()->cached_network_params)); + out->SetValue(kSTTL, config.expiry_time.AbsoluteDifference(now).ToSeconds()); + if (replay_protection_) { + out->SetStringPiece(kServerNonceTag, + NewServerNonce(context.rand(), context.info().now)); + } + + // Send client the reject reason for debugging purposes. + QUICHE_DCHECK_LT(0u, reject_reasons.size()); + out->SetVector(kRREJ, reject_reasons); + + // The client may have requested a certificate chain. + if (!ClientDemandsX509Proof(context.client_hello())) { + QUIC_BUG(quic_bug_10630_4) + << "x509 certificates not supported in proof demand"; + return; + } + + absl::string_view client_cached_cert_hashes; + if (context.client_hello().GetStringPiece(kCCRT, + &client_cached_cert_hashes)) { + context.params()->client_cached_cert_hashes = + std::string(client_cached_cert_hashes); + } else { + context.params()->client_cached_cert_hashes.clear(); + } + + const std::string compressed = CompressChain( + context.compressed_certs_cache(), context.signed_config()->chain, + context.params()->client_cached_cert_hashes); + + QUICHE_DCHECK_GT(context.chlo_packet_size(), context.client_hello().size()); + // kREJOverheadBytes is a very rough estimate of how much of a REJ + // message is taken up by things other than the certificates. + // STK: 56 bytes + // SNO: 56 bytes + // SCFG + // SCID: 16 bytes + // PUBS: 38 bytes + const size_t kREJOverheadBytes = 166; + // max_unverified_size is the number of bytes that the certificate chain, + // signature, and (optionally) signed certificate timestamp can consume before + // we will demand a valid source-address token. + const size_t max_unverified_size = + chlo_multiplier_ * + (context.chlo_packet_size() - context.total_framing_overhead()) - + kREJOverheadBytes; + static_assert(kClientHelloMinimumSize * kMultiplier >= kREJOverheadBytes, + "overhead calculation may underflow"); + bool should_return_sct = + context.params()->sct_supported_by_client && enable_serving_sct_; + const std::string& cert_sct = context.signed_config()->proof.leaf_cert_scts; + const size_t sct_size = should_return_sct ? cert_sct.size() : 0; + const size_t total_size = context.signed_config()->proof.signature.size() + + compressed.size() + sct_size; + if (context.info().valid_source_address_token || + total_size < max_unverified_size) { + out->SetStringPiece(kCertificateTag, compressed); + out->SetStringPiece(kPROF, context.signed_config()->proof.signature); + if (should_return_sct) { + if (cert_sct.empty()) { + // Log SNI and subject name for the leaf cert if its SCT is empty. + // This is for debugging b/28342827. + const std::vector<std::string>& certs = + context.signed_config()->chain->certs; + std::string ca_subject; + if (!certs.empty()) { + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(certs[0]); + if (view != nullptr) { + absl::optional<std::string> maybe_ca_subject = + view->GetHumanReadableSubject(); + if (maybe_ca_subject.has_value()) { + ca_subject = *maybe_ca_subject; + } + } + } + QUIC_LOG_EVERY_N_SEC(WARNING, 60) + << "SCT is expected but it is empty. sni: '" + << context.params()->sni << "' cert subject: '" << ca_subject + << "'"; + } else { + out->SetStringPiece(kCertificateSCTTag, cert_sct); + } + } + } else { + QUIC_LOG_EVERY_N_SEC(WARNING, 60) + << "Sending inchoate REJ for hostname: " << context.info().sni + << " signature: " << context.signed_config()->proof.signature.size() + << " cert: " << compressed.size() << " sct:" << sct_size + << " total: " << total_size << " max: " << max_unverified_size; + } +} + +std::string QuicCryptoServerConfig::CompressChain( + QuicCompressedCertsCache* compressed_certs_cache, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& client_cached_cert_hashes) { + // Check whether the compressed certs is available in the cache. + QUICHE_DCHECK(compressed_certs_cache); + const std::string* cached_value = compressed_certs_cache->GetCompressedCert( + chain, client_cached_cert_hashes); + if (cached_value) { + return *cached_value; + } + std::string compressed = + CertCompressor::CompressChain(chain->certs, client_cached_cert_hashes); + // Insert the newly compressed cert to cache. + compressed_certs_cache->Insert(chain, client_cached_cert_hashes, compressed); + return compressed; +} + +quiche::QuicheReferenceCountedPointer<QuicCryptoServerConfig::Config> +QuicCryptoServerConfig::ParseConfigProtobuf( + const QuicServerConfigProtobuf& protobuf, bool is_fallback) const { + std::unique_ptr<CryptoHandshakeMessage> msg = + CryptoFramer::ParseMessage(protobuf.config()); + + if (!msg) { + QUIC_LOG(WARNING) << "Failed to parse server config message"; + return nullptr; + } + + if (msg->tag() != kSCFG) { + QUIC_LOG(WARNING) << "Server config message has tag " << msg->tag() + << ", but expected " << kSCFG; + return nullptr; + } + + quiche::QuicheReferenceCountedPointer<Config> config(new Config); + config->serialized = protobuf.config(); + config->source_address_token_boxer = &source_address_token_boxer_; + + if (protobuf.has_primary_time()) { + config->primary_time = + QuicWallTime::FromUNIXSeconds(protobuf.primary_time()); + } + + config->priority = protobuf.priority(); + + absl::string_view scid; + if (!msg->GetStringPiece(kSCID, &scid)) { + QUIC_LOG(WARNING) << "Server config message is missing SCID"; + return nullptr; + } + QUICHE_DCHECK(!scid.empty()); + config->id = std::string(scid); + + if (msg->GetTaglist(kAEAD, &config->aead) != QUIC_NO_ERROR) { + QUIC_LOG(WARNING) << "Server config message is missing AEAD"; + return nullptr; + } + + QuicTagVector kexs_tags; + if (msg->GetTaglist(kKEXS, &kexs_tags) != QUIC_NO_ERROR) { + QUIC_LOG(WARNING) << "Server config message is missing KEXS"; + return nullptr; + } + + absl::string_view orbit; + if (!msg->GetStringPiece(kORBT, &orbit)) { + QUIC_LOG(WARNING) << "Server config message is missing ORBT"; + return nullptr; + } + + if (orbit.size() != kOrbitSize) { + QUIC_LOG(WARNING) << "Orbit value in server config is the wrong length." + " Got " + << orbit.size() << " want " << kOrbitSize; + return nullptr; + } + static_assert(sizeof(config->orbit) == kOrbitSize, "incorrect orbit size"); + memcpy(config->orbit, orbit.data(), sizeof(config->orbit)); + + QuicTagVector proof_demand_tags; + if (msg->GetTaglist(kPDMD, &proof_demand_tags) == QUIC_NO_ERROR) { + for (QuicTag tag : proof_demand_tags) { + if (tag == kCHID) { + config->channel_id_enabled = true; + break; + } + } + } + + for (size_t i = 0; i < kexs_tags.size(); i++) { + const QuicTag tag = kexs_tags[i]; + std::string private_key; + + config->kexs.push_back(tag); + + for (int j = 0; j < protobuf.key_size(); j++) { + const QuicServerConfigProtobuf::PrivateKey& key = protobuf.key(i); + if (key.tag() == tag) { + private_key = key.private_key(); + break; + } + } + + std::unique_ptr<AsynchronousKeyExchange> ka = + key_exchange_source_->Create(config->id, is_fallback, tag, private_key); + if (!ka) { + return nullptr; + } + for (const auto& key_exchange : config->key_exchanges) { + if (key_exchange->type() == tag) { + QUIC_LOG(WARNING) << "Duplicate key exchange in config: " << tag; + return nullptr; + } + } + + config->key_exchanges.push_back(std::move(ka)); + } + + uint64_t expiry_seconds; + if (msg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) { + QUIC_LOG(WARNING) << "Server config message is missing EXPY"; + return nullptr; + } + config->expiry_time = QuicWallTime::FromUNIXSeconds(expiry_seconds); + + return config; +} + +void QuicCryptoServerConfig::set_replay_protection(bool on) { + replay_protection_ = on; +} + +void QuicCryptoServerConfig::set_chlo_multiplier(size_t multiplier) { + chlo_multiplier_ = multiplier; +} + +void QuicCryptoServerConfig::set_source_address_token_future_secs( + uint32_t future_secs) { + source_address_token_future_secs_ = future_secs; +} + +void QuicCryptoServerConfig::set_source_address_token_lifetime_secs( + uint32_t lifetime_secs) { + source_address_token_lifetime_secs_ = lifetime_secs; +} + +void QuicCryptoServerConfig::set_enable_serving_sct(bool enable_serving_sct) { + enable_serving_sct_ = enable_serving_sct; +} + +void QuicCryptoServerConfig::AcquirePrimaryConfigChangedCb( + std::unique_ptr<PrimaryConfigChangedCallback> cb) { + QuicWriterMutexLock locked(&configs_lock_); + primary_config_changed_cb_ = std::move(cb); +} + +std::string QuicCryptoServerConfig::NewSourceAddressToken( + const CryptoSecretBoxer& crypto_secret_boxer, + const SourceAddressTokens& previous_tokens, + const QuicIpAddress& ip, + QuicRandom* rand, + QuicWallTime now, + const CachedNetworkParameters* cached_network_params) const { + SourceAddressTokens source_address_tokens; + SourceAddressToken* source_address_token = source_address_tokens.add_tokens(); + source_address_token->set_ip(ip.DualStacked().ToPackedString()); + source_address_token->set_timestamp(now.ToUNIXSeconds()); + if (cached_network_params != nullptr) { + *(source_address_token->mutable_cached_network_parameters()) = + *cached_network_params; + } + + // Append previous tokens. + for (const SourceAddressToken& token : previous_tokens.tokens()) { + if (source_address_tokens.tokens_size() > kMaxTokenAddresses) { + break; + } + + if (token.ip() == source_address_token->ip()) { + // It's for the same IP address. + continue; + } + + if (ValidateSourceAddressTokenTimestamp(token, now) != HANDSHAKE_OK) { + continue; + } + + *(source_address_tokens.add_tokens()) = token; + } + + return crypto_secret_boxer.Box(rand, + source_address_tokens.SerializeAsString()); +} + +int QuicCryptoServerConfig::NumberOfConfigs() const { + QuicReaderMutexLock locked(&configs_lock_); + return configs_.size(); +} + +ProofSource* QuicCryptoServerConfig::proof_source() const { + return proof_source_.get(); +} + +SSL_CTX* QuicCryptoServerConfig::ssl_ctx() const { + return ssl_ctx_.get(); +} + +HandshakeFailureReason QuicCryptoServerConfig::ParseSourceAddressToken( + const CryptoSecretBoxer& crypto_secret_boxer, absl::string_view token, + SourceAddressTokens& tokens) const { + std::string storage; + absl::string_view plaintext; + if (!crypto_secret_boxer.Unbox(token, &storage, &plaintext)) { + return SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE; + } + + if (!tokens.ParseFromArray(plaintext.data(), plaintext.size())) { + // Some clients might still be using the old source token format so + // attempt to parse that format. + // TODO(rch): remove this code once the new format is ubiquitous. + SourceAddressToken token; + if (!token.ParseFromArray(plaintext.data(), plaintext.size())) { + return SOURCE_ADDRESS_TOKEN_PARSE_FAILURE; + } + *tokens.add_tokens() = token; + } + + return HANDSHAKE_OK; +} + +HandshakeFailureReason QuicCryptoServerConfig::ValidateSourceAddressTokens( + const SourceAddressTokens& source_address_tokens, + const QuicIpAddress& ip, + QuicWallTime now, + CachedNetworkParameters* cached_network_params) const { + HandshakeFailureReason reason = + SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE; + for (const SourceAddressToken& token : source_address_tokens.tokens()) { + reason = ValidateSingleSourceAddressToken(token, ip, now); + if (reason == HANDSHAKE_OK) { + if (cached_network_params != nullptr && + token.has_cached_network_parameters()) { + *cached_network_params = token.cached_network_parameters(); + } + break; + } + } + return reason; +} + +HandshakeFailureReason QuicCryptoServerConfig::ValidateSingleSourceAddressToken( + const SourceAddressToken& source_address_token, + const QuicIpAddress& ip, + QuicWallTime now) const { + if (source_address_token.ip() != ip.DualStacked().ToPackedString()) { + // It's for a different IP address. + return SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE; + } + + return ValidateSourceAddressTokenTimestamp(source_address_token, now); +} + +HandshakeFailureReason +QuicCryptoServerConfig::ValidateSourceAddressTokenTimestamp( + const SourceAddressToken& source_address_token, + QuicWallTime now) const { + const QuicWallTime timestamp( + QuicWallTime::FromUNIXSeconds(source_address_token.timestamp())); + const QuicTime::Delta delta(now.AbsoluteDifference(timestamp)); + + if (now.IsBefore(timestamp) && + delta.ToSeconds() > source_address_token_future_secs_) { + return SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE; + } + + if (now.IsAfter(timestamp) && + delta.ToSeconds() > source_address_token_lifetime_secs_) { + return SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE; + } + + return HANDSHAKE_OK; +} + +// kServerNoncePlaintextSize is the number of bytes in an unencrypted server +// nonce. +static const size_t kServerNoncePlaintextSize = + 4 /* timestamp */ + 20 /* random bytes */; + +std::string QuicCryptoServerConfig::NewServerNonce(QuicRandom* rand, + QuicWallTime now) const { + const uint32_t timestamp = static_cast<uint32_t>(now.ToUNIXSeconds()); + + uint8_t server_nonce[kServerNoncePlaintextSize]; + static_assert(sizeof(server_nonce) > sizeof(timestamp), "nonce too small"); + server_nonce[0] = static_cast<uint8_t>(timestamp >> 24); + server_nonce[1] = static_cast<uint8_t>(timestamp >> 16); + server_nonce[2] = static_cast<uint8_t>(timestamp >> 8); + server_nonce[3] = static_cast<uint8_t>(timestamp); + rand->RandBytes(&server_nonce[sizeof(timestamp)], + sizeof(server_nonce) - sizeof(timestamp)); + + return server_nonce_boxer_.Box( + rand, absl::string_view(reinterpret_cast<char*>(server_nonce), + sizeof(server_nonce))); +} + +bool QuicCryptoServerConfig::ValidateExpectedLeafCertificate( + const CryptoHandshakeMessage& client_hello, + const std::vector<std::string>& certs) const { + if (certs.empty()) { + return false; + } + + uint64_t hash_from_client; + if (client_hello.GetUint64(kXLCT, &hash_from_client) != QUIC_NO_ERROR) { + return false; + } + return CryptoUtils::ComputeLeafCertHash(certs.at(0)) == hash_from_client; +} + +bool QuicCryptoServerConfig::IsNextConfigReady(QuicWallTime now) const { + return !next_config_promotion_time_.IsZero() && + !next_config_promotion_time_.IsAfter(now); +} + +QuicCryptoServerConfig::Config::Config() + : channel_id_enabled(false), + is_primary(false), + primary_time(QuicWallTime::Zero()), + expiry_time(QuicWallTime::Zero()), + priority(0), + source_address_token_boxer(nullptr) {} + +QuicCryptoServerConfig::Config::~Config() {} + +QuicSignedServerConfig::QuicSignedServerConfig() {} +QuicSignedServerConfig::~QuicSignedServerConfig() {} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_crypto_server_config.h b/quiche/quic/core/crypto/quic_crypto_server_config.h new file mode 100644 index 0000000..3d79ad7 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_server_config.h
@@ -0,0 +1,965 @@ +// 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_ + +#include <cstddef> +#include <cstdint> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/base.h" +#include "quiche/quic/core/crypto/crypto_handshake.h" +#include "quiche/quic/core/crypto/crypto_handshake_message.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/crypto_secret_boxer.h" +#include "quiche/quic/core/crypto/key_exchange.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/crypto/quic_compressed_certs_cache.h" +#include "quiche/quic/core/crypto/quic_crypto_proof.h" +#include "quiche/quic/core/proto/cached_network_parameters_proto.h" +#include "quiche/quic/core/proto/source_address_token_proto.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/quic/platform/api/quic_mutex.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/common/platform/api/quiche_reference_counted.h" + +namespace quic { + +class CryptoHandshakeMessage; +class ProofSource; +class QuicClock; +class QuicRandom; +class QuicServerConfigProtobuf; +struct QuicSignedServerConfig; + +// ClientHelloInfo contains information about a client hello message that is +// only kept for as long as it's being processed. +struct QUIC_EXPORT_PRIVATE ClientHelloInfo { + ClientHelloInfo(const QuicIpAddress& in_client_ip, QuicWallTime in_now); + ClientHelloInfo(const ClientHelloInfo& other); + ~ClientHelloInfo(); + + // Inputs to EvaluateClientHello. + const QuicIpAddress client_ip; + const QuicWallTime now; + + // Outputs from EvaluateClientHello. + bool valid_source_address_token; + absl::string_view sni; + absl::string_view client_nonce; + absl::string_view server_nonce; + absl::string_view user_agent_id; + SourceAddressTokens source_address_tokens; + + // Errors from EvaluateClientHello. + std::vector<uint32_t> reject_reasons; + static_assert(sizeof(QuicTag) == sizeof(uint32_t), "header out of sync"); +}; + +namespace test { +class QuicCryptoServerConfigPeer; +} // namespace test + +// Hook that allows application code to subscribe to primary config changes. +class QUIC_EXPORT_PRIVATE PrimaryConfigChangedCallback { + public: + PrimaryConfigChangedCallback(); + PrimaryConfigChangedCallback(const PrimaryConfigChangedCallback&) = delete; + PrimaryConfigChangedCallback& operator=(const PrimaryConfigChangedCallback&) = + delete; + virtual ~PrimaryConfigChangedCallback(); + virtual void Run(const std::string& scid) = 0; +}; + +// Callback used to accept the result of the |client_hello| validation step. +class QUIC_EXPORT_PRIVATE ValidateClientHelloResultCallback { + public: + // Opaque token that holds information about the client_hello and + // its validity. Can be interpreted by calling ProcessClientHello. + struct QUIC_EXPORT_PRIVATE Result : public quiche::QuicheReferenceCounted { + Result(const CryptoHandshakeMessage& in_client_hello, + QuicIpAddress in_client_ip, + QuicWallTime in_now); + + CryptoHandshakeMessage client_hello; + ClientHelloInfo info; + QuicErrorCode error_code; + std::string error_details; + + // Populated if the CHLO STK contained a CachedNetworkParameters proto. + CachedNetworkParameters cached_network_params; + + protected: + ~Result() override; + }; + + ValidateClientHelloResultCallback(); + ValidateClientHelloResultCallback(const ValidateClientHelloResultCallback&) = + delete; + ValidateClientHelloResultCallback& operator=( + const ValidateClientHelloResultCallback&) = delete; + virtual ~ValidateClientHelloResultCallback(); + virtual void Run(quiche::QuicheReferenceCountedPointer<Result> result, + std::unique_ptr<ProofSource::Details> details) = 0; +}; + +// Callback used to accept the result of the ProcessClientHello method. +class QUIC_EXPORT_PRIVATE ProcessClientHelloResultCallback { + public: + ProcessClientHelloResultCallback(); + ProcessClientHelloResultCallback(const ProcessClientHelloResultCallback&) = + delete; + ProcessClientHelloResultCallback& operator=( + const ProcessClientHelloResultCallback&) = delete; + virtual ~ProcessClientHelloResultCallback(); + virtual void Run(QuicErrorCode error, + const std::string& error_details, + std::unique_ptr<CryptoHandshakeMessage> message, + std::unique_ptr<DiversificationNonce> diversification_nonce, + std::unique_ptr<ProofSource::Details> details) = 0; +}; + +// Callback used to receive the results of a call to +// BuildServerConfigUpdateMessage. +class QUIC_EXPORT_PRIVATE BuildServerConfigUpdateMessageResultCallback { + public: + BuildServerConfigUpdateMessageResultCallback() = default; + virtual ~BuildServerConfigUpdateMessageResultCallback() {} + BuildServerConfigUpdateMessageResultCallback( + const BuildServerConfigUpdateMessageResultCallback&) = delete; + BuildServerConfigUpdateMessageResultCallback& operator=( + const BuildServerConfigUpdateMessageResultCallback&) = delete; + virtual void Run(bool ok, const CryptoHandshakeMessage& message) = 0; +}; + +// Object that is interested in built rejections (which include REJ, SREJ and +// cheap SREJ). +class QUIC_EXPORT_PRIVATE RejectionObserver { + public: + RejectionObserver() = default; + virtual ~RejectionObserver() {} + RejectionObserver(const RejectionObserver&) = delete; + RejectionObserver& operator=(const RejectionObserver&) = delete; + // Called after a rejection is built. + virtual void OnRejectionBuilt(const std::vector<uint32_t>& reasons, + CryptoHandshakeMessage* out) const = 0; +}; + +// Factory for creating KeyExchange objects. +class QUIC_EXPORT_PRIVATE KeyExchangeSource { + public: + virtual ~KeyExchangeSource() = default; + + // Returns the default KeyExchangeSource. + static std::unique_ptr<KeyExchangeSource> Default(); + + // Create a new KeyExchange using the curve specified by |type| using the + // specified private key. |private_key| may be empty for key-exchange + // mechanisms which do not hold the private key in-process. If |is_fallback| + // is set, |private_key| is required to be set, and a local key-exchange + // object should be returned. + virtual std::unique_ptr<AsynchronousKeyExchange> Create( + std::string server_config_id, + bool is_fallback, + QuicTag type, + absl::string_view private_key) = 0; +}; + +// QuicCryptoServerConfig contains the crypto configuration of a QUIC server. +// Unlike a client, a QUIC server can have multiple configurations active in +// order to support clients resuming with a previous configuration. +// TODO(agl): when adding configurations at runtime is added, this object will +// need to consider locking. +class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { + public: + // ConfigOptions contains options for generating server configs. + struct QUIC_EXPORT_PRIVATE ConfigOptions { + ConfigOptions(); + ConfigOptions(const ConfigOptions& other); + ~ConfigOptions(); + + // expiry_time is the time, in UNIX seconds, when the server config will + // expire. If unset, it defaults to the current time plus six months. + QuicWallTime expiry_time; + // channel_id_enabled controls whether the server config will indicate + // support for ChannelIDs. + bool channel_id_enabled; + // id contains the server config id for the resulting config. If empty, a + // random id is generated. + std::string id; + // orbit contains the kOrbitSize bytes of the orbit value for the server + // config. If |orbit| is empty then a random orbit is generated. + std::string orbit; + // p256 determines whether a P-256 public key will be included in the + // server config. Note that this breaks deterministic server-config + // generation since P-256 key generation doesn't use the QuicRandom given + // to GenerateConfig(). + bool p256; + }; + + // |source_address_token_secret|: secret key material used for encrypting and + // decrypting source address tokens. It can be of any length as it is fed + // into a KDF before use. In tests, use TESTING. + // |server_nonce_entropy|: an entropy source used to generate the orbit and + // key for server nonces, which are always local to a given instance of a + // server. Not owned. + // |proof_source|: provides certificate chains and signatures. + // |key_exchange_source|: provides key-exchange functionality. + QuicCryptoServerConfig( + absl::string_view source_address_token_secret, + QuicRandom* server_nonce_entropy, + std::unique_ptr<ProofSource> proof_source, + std::unique_ptr<KeyExchangeSource> key_exchange_source); + QuicCryptoServerConfig(const QuicCryptoServerConfig&) = delete; + QuicCryptoServerConfig& operator=(const QuicCryptoServerConfig&) = delete; + ~QuicCryptoServerConfig(); + + // TESTING is a magic parameter for passing to the constructor in tests. + static const char TESTING[]; + + // Generates a QuicServerConfigProtobuf protobuf suitable for + // AddConfig and SetConfigs. + static QuicServerConfigProtobuf GenerateConfig(QuicRandom* rand, + const QuicClock* clock, + const ConfigOptions& options); + + // AddConfig adds a QuicServerConfigProtobuf to the available configurations. + // It returns the SCFG message from the config if successful. |now| is used in + // conjunction with |protobuf->primary_time()| to determine whether the + // config should be made primary. + std::unique_ptr<CryptoHandshakeMessage> AddConfig( + const QuicServerConfigProtobuf& protobuf, + QuicWallTime now); + + // AddDefaultConfig calls GenerateConfig to create a config and then calls + // AddConfig to add it. See the comment for |GenerateConfig| for details of + // the arguments. + std::unique_ptr<CryptoHandshakeMessage> AddDefaultConfig( + QuicRandom* rand, + const QuicClock* clock, + const ConfigOptions& options); + + // SetConfigs takes a vector of config protobufs and the current time. + // Configs are assumed to be uniquely identified by their server config ID. + // Previously unknown configs are added and possibly made the primary config + // depending on their |primary_time| and the value of |now|. Configs that are + // known, but are missing from the protobufs are deleted, unless they are + // currently the primary config. SetConfigs returns false if any errors were + // encountered and no changes to the QuicCryptoServerConfig will occur. + bool SetConfigs(const std::vector<QuicServerConfigProtobuf>& protobufs, + const QuicServerConfigProtobuf* fallback_protobuf, + QuicWallTime now); + + // SetSourceAddressTokenKeys sets the keys to be tried, in order, when + // decrypting a source address token. Note that these keys are used *without* + // passing them through a KDF, in contradistinction to the + // |source_address_token_secret| argument to the constructor. + void SetSourceAddressTokenKeys(const std::vector<std::string>& keys); + + // Get the server config ids for all known configs. + std::vector<std::string> GetConfigIds() const; + + // Checks |client_hello| for gross errors and determines whether it can be + // shown to be fresh (i.e. not a replay). The result of the validation step + // must be interpreted by calling QuicCryptoServerConfig::ProcessClientHello + // from the done_cb. + // + // ValidateClientHello may invoke the done_cb before unrolling the + // stack if it is able to assess the validity of the client_nonce + // without asynchronous operations. + // + // client_hello: the incoming client hello message. + // client_ip: the IP address of the client, which is used to generate and + // validate source-address tokens. + // server_address: the IP address and port of the server. The IP address and + // port may be used for certificate selection. + // version: protocol version used for this connection. + // clock: used to validate client nonces and ephemeral keys. + // signed_config: in/out parameter to which will be written the crypto proof + // used in reply to a proof demand. The pointed-to-object must live until + // the callback is invoked. + // done_cb: single-use callback that accepts an opaque + // ValidatedClientHelloMsg token that holds information about + // the client hello. The callback will always be called exactly + // once, either under the current call stack, or after the + // completion of an asynchronous operation. + void ValidateClientHello( + const CryptoHandshakeMessage& client_hello, + const QuicSocketAddress& client_address, + const QuicSocketAddress& server_address, QuicTransportVersion version, + const QuicClock* clock, + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> + signed_config, + std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const; + + // ProcessClientHello processes |client_hello| and decides whether to accept + // or reject the connection. If the connection is to be accepted, |done_cb| is + // invoked with the contents of the ServerHello and QUIC_NO_ERROR. Otherwise + // |done_cb| is called with a REJ or SREJ message and QUIC_NO_ERROR. + // + // validate_chlo_result: Output from the asynchronous call to + // ValidateClientHello. Contains the client hello message and + // information about it. + // reject_only: Only generate rejections, not server hello messages. + // connection_id: the ConnectionId for the connection, which is used in key + // derivation. + // server_ip: the IP address of the server. The IP address may be used for + // certificate selection. + // client_address: the IP address and port of the client. The IP address is + // used to generate and validate source-address tokens. + // version: version of the QUIC protocol in use for this connection + // supported_versions: versions of the QUIC protocol that this server + // supports. + // clock: used to validate client nonces and ephemeral keys. + // rand: an entropy source + // compressed_certs_cache: the cache that caches a set of most recently used + // certs. Owned by QuicDispatcher. + // params: the state of the handshake. This may be updated with a server + // nonce when we send a rejection. + // signed_config: output structure containing the crypto proof used in reply + // to a proof demand. + // total_framing_overhead: the total per-packet overhead for a stream frame + // chlo_packet_size: the size, in bytes, of the CHLO packet + // done_cb: the callback invoked on completion + void ProcessClientHello( + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + validate_chlo_result, + bool reject_only, QuicConnectionId connection_id, + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, ParsedQuicVersion version, + const ParsedQuicVersionVector& supported_versions, const QuicClock* clock, + QuicRandom* rand, QuicCompressedCertsCache* compressed_certs_cache, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + params, + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> + signed_config, + QuicByteCount total_framing_overhead, QuicByteCount chlo_packet_size, + std::unique_ptr<ProcessClientHelloResultCallback> done_cb) const; + + // BuildServerConfigUpdateMessage invokes |cb| with a SCUP message containing + // the current primary config, an up to date source-address token, and cert + // chain and proof in the case of secure QUIC. Passes true to |cb| if the + // message was generated successfully, and false otherwise. This method + // assumes ownership of |cb|. + // + // |cached_network_params| is optional, and can be nullptr. + void BuildServerConfigUpdateMessage( + QuicTransportVersion version, + absl::string_view chlo_hash, + const SourceAddressTokens& previous_source_address_tokens, + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, + const QuicClock* clock, + QuicRandom* rand, + QuicCompressedCertsCache* compressed_certs_cache, + const QuicCryptoNegotiatedParameters& params, + const CachedNetworkParameters* cached_network_params, + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) const; + + // set_replay_protection controls whether replay protection is enabled. If + // replay protection is disabled then no strike registers are needed and + // frontends can share an orbit value without a shared strike-register. + // However, an attacker can duplicate a handshake and cause a client's + // request to be processed twice. + void set_replay_protection(bool on); + + // set_chlo_multiplier specifies 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. + void set_chlo_multiplier(size_t multiplier); + + // When sender is allowed to not pad client hello (not standards compliant), + // we need to disable the client hello check. + void set_validate_chlo_size(bool new_value) { + validate_chlo_size_ = new_value; + } + + // Returns whether the sender is allowed to not pad the client hello. + bool validate_chlo_size() const { return validate_chlo_size_; } + + // When QUIC is tunneled through some other mechanism, source token validation + // may be disabled. Do not disable it if you are not providing other + // protection. (|true| protects against UDP amplification attack.). + void set_validate_source_address_token(bool new_value) { + validate_source_address_token_ = new_value; + } + + // set_source_address_token_future_secs sets the number of seconds into the + // future that source-address tokens will be accepted from. Since + // source-address tokens are authenticated, this should only happen if + // another, valid server has clock-skew. + void set_source_address_token_future_secs(uint32_t future_secs); + + // set_source_address_token_lifetime_secs sets the number of seconds that a + // source-address token will be valid for. + void set_source_address_token_lifetime_secs(uint32_t lifetime_secs); + + // set_enable_serving_sct enables or disables serving signed cert timestamp + // (RFC6962) in server hello. + void set_enable_serving_sct(bool enable_serving_sct); + + // Set and take ownership of the callback to invoke on primary config changes. + void AcquirePrimaryConfigChangedCb( + std::unique_ptr<PrimaryConfigChangedCallback> cb); + + // Returns the number of configs this object owns. + int NumberOfConfigs() const; + + // NewSourceAddressToken returns a fresh source address token for the given + // IP address. |previous_tokens| is the received tokens, and can be empty. + // |cached_network_params| is optional, and can be nullptr. + std::string NewSourceAddressToken( + const CryptoSecretBoxer& crypto_secret_boxer, + const SourceAddressTokens& previous_tokens, + const QuicIpAddress& ip, + QuicRandom* rand, + QuicWallTime now, + const CachedNetworkParameters* cached_network_params) const; + + // ParseSourceAddressToken parses the source address tokens contained in + // the encrypted |token|, and populates |tokens| with the parsed tokens. + // Returns HANDSHAKE_OK if |token| could be parsed, or the reason for the + // failure. + HandshakeFailureReason ParseSourceAddressToken( + const CryptoSecretBoxer& crypto_secret_boxer, absl::string_view token, + SourceAddressTokens& tokens) const; + + // ValidateSourceAddressTokens returns HANDSHAKE_OK if the source address + // tokens in |tokens| contain a valid and timely token for the IP address + // |ip| given that the current time is |now|. Otherwise it returns the + // reason for failure. |cached_network_params| is populated if the valid + // token contains a CachedNetworkParameters proto. + HandshakeFailureReason ValidateSourceAddressTokens( + const SourceAddressTokens& tokens, + const QuicIpAddress& ip, + QuicWallTime now, + CachedNetworkParameters* cached_network_params) const; + + // Callers retain the ownership of |rejection_observer| which must outlive the + // config. + void set_rejection_observer(RejectionObserver* rejection_observer) { + rejection_observer_ = rejection_observer; + } + + ProofSource* proof_source() const; + + SSL_CTX* ssl_ctx() const; + + // Pre-shared key used during the handshake. + const std::string& pre_shared_key() const { return pre_shared_key_; } + void set_pre_shared_key(absl::string_view psk) { + pre_shared_key_ = std::string(psk); + } + + bool pad_rej() const { return pad_rej_; } + void set_pad_rej(bool new_value) { pad_rej_ = new_value; } + + bool pad_shlo() const { return pad_shlo_; } + void set_pad_shlo(bool new_value) { pad_shlo_ = new_value; } + + const CryptoSecretBoxer& source_address_token_boxer() const { + return source_address_token_boxer_; + } + + private: + friend class test::QuicCryptoServerConfigPeer; + friend struct QuicSignedServerConfig; + + // Config represents a server config: a collection of preferences and + // Diffie-Hellman public values. + class QUIC_EXPORT_PRIVATE Config : public QuicCryptoConfig, + public quiche::QuicheReferenceCounted { + public: + Config(); + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + + // TODO(rtenneti): since this is a class, we should probably do + // getters/setters here. + // |serialized| contains the bytes of this server config, suitable for + // sending on the wire. + std::string serialized; + // id contains the SCID of this server config. + std::string id; + // orbit contains the orbit value for this config: an opaque identifier + // used to identify clusters of server frontends. + unsigned char orbit[kOrbitSize]; + + // key_exchanges contains key exchange objects. The values correspond, + // one-to-one, with the tags in |kexs| from the parent class. + std::vector<std::unique_ptr<AsynchronousKeyExchange>> key_exchanges; + + // channel_id_enabled is true if the config in |serialized| specifies that + // ChannelIDs are supported. + bool channel_id_enabled; + + // is_primary is true if this config is the one that we'll give out to + // clients as the current one. + bool is_primary; + + // primary_time contains the timestamp when this config should become the + // primary config. A value of QuicWallTime::Zero() means that this config + // will not be promoted at a specific time. + QuicWallTime primary_time; + + // expiry_time contains the timestamp when this config expires. + QuicWallTime expiry_time; + + // Secondary sort key for use when selecting primary configs and + // there are multiple configs with the same primary time. + // Smaller numbers mean higher priority. + uint64_t priority; + + // source_address_token_boxer_ is used to protect the + // source-address tokens that are given to clients. + // Points to either source_address_token_boxer_storage or the + // default boxer provided by QuicCryptoServerConfig. + const CryptoSecretBoxer* source_address_token_boxer; + + // Holds the override source_address_token_boxer instance if the + // Config is not using the default source address token boxer + // instance provided by QuicCryptoServerConfig. + std::unique_ptr<CryptoSecretBoxer> source_address_token_boxer_storage; + + private: + ~Config() override; + }; + + using ConfigMap = + std::map<ServerConfigID, quiche::QuicheReferenceCountedPointer<Config>>; + + // Get a ref to the config with a given server config id. + quiche::QuicheReferenceCountedPointer<Config> GetConfigWithScid( + absl::string_view requested_scid) const + QUIC_SHARED_LOCKS_REQUIRED(configs_lock_); + + // A snapshot of the configs associated with an in-progress handshake. + struct QUIC_EXPORT_PRIVATE Configs { + quiche::QuicheReferenceCountedPointer<Config> requested; + quiche::QuicheReferenceCountedPointer<Config> primary; + quiche::QuicheReferenceCountedPointer<Config> fallback; + }; + + // Get a snapshot of the current configs associated with a handshake. If this + // method was called earlier in this handshake |old_primary_config| should be + // set to the primary config returned from that invocation, otherwise nullptr. + // + // Returns true if any configs are loaded. If false is returned, |configs| is + // not modified. + bool GetCurrentConfigs( + const QuicWallTime& now, absl::string_view requested_scid, + quiche::QuicheReferenceCountedPointer<Config> old_primary_config, + Configs* configs) const; + + // ConfigPrimaryTimeLessThan returns true if a->primary_time < + // b->primary_time. + static bool ConfigPrimaryTimeLessThan( + const quiche::QuicheReferenceCountedPointer<Config>& a, + const quiche::QuicheReferenceCountedPointer<Config>& b); + + // SelectNewPrimaryConfig reevaluates the primary config based on the + // "primary_time" deadlines contained in each. + void SelectNewPrimaryConfig(QuicWallTime now) const + QUIC_EXCLUSIVE_LOCKS_REQUIRED(configs_lock_); + + // EvaluateClientHello checks |client_hello_state->client_hello| for gross + // errors and determines whether it is fresh (i.e. not a replay). The results + // are written to |client_hello_state->info|. + void EvaluateClientHello( + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, QuicTransportVersion version, + const Configs& configs, + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + client_hello_state, + std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const; + + // Convenience class which carries the arguments passed to + // |ProcessClientHellp| along. + class QUIC_EXPORT_PRIVATE ProcessClientHelloContext { + public: + ProcessClientHelloContext( + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + validate_chlo_result, + bool reject_only, QuicConnectionId connection_id, + const QuicSocketAddress& server_address, + const QuicSocketAddress& client_address, ParsedQuicVersion version, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock, QuicRandom* rand, + QuicCompressedCertsCache* compressed_certs_cache, + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + params, + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> + signed_config, + QuicByteCount total_framing_overhead, QuicByteCount chlo_packet_size, + std::unique_ptr<ProcessClientHelloResultCallback> done_cb) + : validate_chlo_result_(validate_chlo_result), + reject_only_(reject_only), + connection_id_(connection_id), + server_address_(server_address), + client_address_(client_address), + version_(version), + supported_versions_(supported_versions), + clock_(clock), + rand_(rand), + compressed_certs_cache_(compressed_certs_cache), + params_(params), + signed_config_(signed_config), + total_framing_overhead_(total_framing_overhead), + chlo_packet_size_(chlo_packet_size), + done_cb_(std::move(done_cb)) {} + + ~ProcessClientHelloContext(); + + // Invoke |done_cb_| with an error status + void Fail(QuicErrorCode error, const std::string& error_details); + + // Invoke |done_cb_| with a success status + void Succeed(std::unique_ptr<CryptoHandshakeMessage> message, + std::unique_ptr<DiversificationNonce> diversification_nonce, + std::unique_ptr<ProofSource::Details> proof_source_details); + + // Member accessors + quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + validate_chlo_result() const { + return validate_chlo_result_; + } + bool reject_only() const { return reject_only_; } + QuicConnectionId connection_id() const { return connection_id_; } + QuicSocketAddress server_address() const { return server_address_; } + QuicSocketAddress client_address() const { return client_address_; } + ParsedQuicVersion version() const { return version_; } + ParsedQuicVersionVector supported_versions() const { + return supported_versions_; + } + const QuicClock* clock() const { return clock_; } + QuicRandom* rand() const { return rand_; } // NOLINT + QuicCompressedCertsCache* compressed_certs_cache() const { + return compressed_certs_cache_; + } + quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + params() const { + return params_; + } + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> + signed_config() const { + return signed_config_; + } + QuicByteCount total_framing_overhead() const { + return total_framing_overhead_; + } + QuicByteCount chlo_packet_size() const { return chlo_packet_size_; } + + // Derived value accessors + const CryptoHandshakeMessage& client_hello() const { + return validate_chlo_result()->client_hello; + } + const ClientHelloInfo& info() const { return validate_chlo_result()->info; } + QuicTransportVersion transport_version() const { + return version().transport_version; + } + + private: + const quiche::QuicheReferenceCountedPointer< + ValidateClientHelloResultCallback::Result> + validate_chlo_result_; + const bool reject_only_; + const QuicConnectionId connection_id_; + const QuicSocketAddress server_address_; + const QuicSocketAddress client_address_; + const ParsedQuicVersion version_; + const ParsedQuicVersionVector supported_versions_; + const QuicClock* const clock_; + QuicRandom* const rand_; + QuicCompressedCertsCache* const compressed_certs_cache_; + const quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> + params_; + const quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> + signed_config_; + const QuicByteCount total_framing_overhead_; + const QuicByteCount chlo_packet_size_; + std::unique_ptr<ProcessClientHelloResultCallback> done_cb_; + }; + + // Callback class for bridging between ProcessClientHello and + // ProcessClientHelloAfterGetProof. + class ProcessClientHelloCallback; + friend class ProcessClientHelloCallback; + + // Portion of ProcessClientHello which executes after GetProof. + void ProcessClientHelloAfterGetProof( + bool found_error, + std::unique_ptr<ProofSource::Details> proof_source_details, + std::unique_ptr<ProcessClientHelloContext> context, + const Configs& configs) const; + + // Callback class for bridging between ProcessClientHelloAfterGetProof and + // ProcessClientHelloAfterCalculateSharedKeys. + class ProcessClientHelloAfterGetProofCallback; + friend class ProcessClientHelloAfterGetProofCallback; + + // Portion of ProcessClientHello which executes after CalculateSharedKeys. + void ProcessClientHelloAfterCalculateSharedKeys( + bool found_error, + std::unique_ptr<ProofSource::Details> proof_source_details, + QuicTag key_exchange_type, + std::unique_ptr<CryptoHandshakeMessage> out, + absl::string_view public_value, + std::unique_ptr<ProcessClientHelloContext> context, + const Configs& configs) const; + + // Send a REJ which contains a different ServerConfig than the one the client + // originally used. This is necessary in cases where we discover in the + // middle of the handshake that the private key for the ServerConfig the + // client used is not accessible. + void SendRejectWithFallbackConfig( + std::unique_ptr<ProcessClientHelloContext> context, + quiche::QuicheReferenceCountedPointer<Config> fallback_config) const; + + // Callback class for bridging between SendRejectWithFallbackConfig and + // SendRejectWithFallbackConfigAfterGetProof. + class SendRejectWithFallbackConfigCallback; + friend class SendRejectWithFallbackConfigCallback; + + // Portion of ProcessClientHello which executes after GetProof in the case + // where we have received a CHLO but need to reject it due to the ServerConfig + // private keys being inaccessible. + void SendRejectWithFallbackConfigAfterGetProof( + bool found_error, + std::unique_ptr<ProofSource::Details> proof_source_details, + std::unique_ptr<ProcessClientHelloContext> context, + quiche::QuicheReferenceCountedPointer<Config> fallback_config) const; + + // BuildRejectionAndRecordStats calls |BuildRejection| below and also informs + // the RejectionObserver. + void BuildRejectionAndRecordStats(const ProcessClientHelloContext& context, + const Config& config, + const std::vector<uint32_t>& reject_reasons, + CryptoHandshakeMessage* out) const; + + // BuildRejection sets |out| to be a REJ message in reply to |client_hello|. + void BuildRejection(const ProcessClientHelloContext& context, + const Config& config, + const std::vector<uint32_t>& reject_reasons, + CryptoHandshakeMessage* out) const; + + // CompressChain compresses the certificates in |chain->certs| and returns a + // compressed representation. |client_cached_cert_hashes| contains + // 64-bit, FNV-1a hashes of certificates that the peer already possesses. + static std::string CompressChain( + QuicCompressedCertsCache* compressed_certs_cache, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& client_cached_cert_hashes); + + // ParseConfigProtobuf parses the given config protobuf and returns a + // quiche::QuicheReferenceCountedPointer<Config> if successful. The caller + // adopts the reference to the Config. On error, ParseConfigProtobuf returns + // nullptr. + quiche::QuicheReferenceCountedPointer<Config> ParseConfigProtobuf( + const QuicServerConfigProtobuf& protobuf, bool is_fallback) const; + + // ValidateSingleSourceAddressToken returns HANDSHAKE_OK if the source + // address token in |token| is a timely token for the IP address |ip| + // given that the current time is |now|. Otherwise it returns the reason + // for failure. + HandshakeFailureReason ValidateSingleSourceAddressToken( + const SourceAddressToken& token, + const QuicIpAddress& ip, + QuicWallTime now) const; + + // Returns HANDSHAKE_OK if the source address token in |token| is a timely + // token given that the current time is |now|. Otherwise it returns the + // reason for failure. + HandshakeFailureReason ValidateSourceAddressTokenTimestamp( + const SourceAddressToken& token, + QuicWallTime now) const; + + // NewServerNonce generates and encrypts a random nonce. + std::string NewServerNonce(QuicRandom* rand, QuicWallTime now) const; + + // ValidateExpectedLeafCertificate checks the |client_hello| to see if it has + // an XLCT tag, and if so, verifies that its value matches the hash of the + // server's leaf certificate. |certs| is used to compare against the XLCT + // value. This method returns true if the XLCT tag is not present, or if the + // XLCT tag is present and valid. It returns false otherwise. + bool ValidateExpectedLeafCertificate( + const CryptoHandshakeMessage& client_hello, + const std::vector<std::string>& certs) const; + + // Callback to receive the results of ProofSource::GetProof. Note: this + // callback has no cancellation support, since the lifetime of the ProofSource + // is controlled by this object via unique ownership. If that ownership + // stricture changes, this decision may need to be revisited. + class BuildServerConfigUpdateMessageProofSourceCallback + : public ProofSource::Callback { + public: + BuildServerConfigUpdateMessageProofSourceCallback( + const BuildServerConfigUpdateMessageProofSourceCallback&) = delete; + ~BuildServerConfigUpdateMessageProofSourceCallback() override; + void operator=(const BuildServerConfigUpdateMessageProofSourceCallback&) = + delete; + BuildServerConfigUpdateMessageProofSourceCallback( + const QuicCryptoServerConfig* config, + QuicCompressedCertsCache* compressed_certs_cache, + const QuicCryptoNegotiatedParameters& params, + CryptoHandshakeMessage message, + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb); + + void Run( + bool ok, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const QuicCryptoProof& proof, + std::unique_ptr<ProofSource::Details> details) override; + + private: + const QuicCryptoServerConfig* config_; + QuicCompressedCertsCache* compressed_certs_cache_; + const std::string client_cached_cert_hashes_; + const bool sct_supported_by_client_; + const std::string sni_; + CryptoHandshakeMessage message_; + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb_; + }; + + // Invoked by BuildServerConfigUpdateMessageProofSourceCallback::Run once + // the proof has been acquired. Finishes building the server config update + // message and invokes |cb|. + void FinishBuildServerConfigUpdateMessage( + QuicCompressedCertsCache* compressed_certs_cache, + const std::string& client_cached_cert_hashes, + bool sct_supported_by_client, const std::string& sni, bool ok, + const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, + const std::string& signature, const std::string& leaf_cert_sct, + std::unique_ptr<ProofSource::Details> details, + CryptoHandshakeMessage message, + std::unique_ptr<BuildServerConfigUpdateMessageResultCallback> cb) const; + + // Returns true if the next config promotion should happen now. + bool IsNextConfigReady(QuicWallTime now) const + QUIC_SHARED_LOCKS_REQUIRED(configs_lock_); + + // replay_protection_ controls whether the server enforces that handshakes + // aren't replays. + bool replay_protection_; + + // The multiple of the CHLO message size that a REJ message must stay under + // when the client doesn't present a valid source-address token. This is + // used to protect QUIC from amplification attacks. + size_t chlo_multiplier_; + + // configs_ satisfies the following invariants: + // 1) configs_.empty() <-> primary_config_ == nullptr + // 2) primary_config_ != nullptr -> primary_config_->is_primary + // 3) ∀ c∈configs_, c->is_primary <-> c == primary_config_ + mutable QuicMutex configs_lock_; + + // configs_ contains all active server configs. It's expected that there are + // about half-a-dozen configs active at any one time. + ConfigMap configs_ QUIC_GUARDED_BY(configs_lock_); + + // primary_config_ points to a Config (which is also in |configs_|) which is + // the primary config - i.e. the one that we'll give out to new clients. + mutable quiche::QuicheReferenceCountedPointer<Config> primary_config_ + QUIC_GUARDED_BY(configs_lock_); + + // fallback_config_ points to a Config (which is also in |configs_|) which is + // the fallback config, which will be used if the other configs are unuseable + // for some reason. + // + // TODO(b/112548056): This is currently always nullptr. + quiche::QuicheReferenceCountedPointer<Config> fallback_config_ + QUIC_GUARDED_BY(configs_lock_); + + // next_config_promotion_time_ contains the nearest, future time when an + // active config will be promoted to primary. + mutable QuicWallTime next_config_promotion_time_ + QUIC_GUARDED_BY(configs_lock_); + + // Callback to invoke when the primary config changes. + std::unique_ptr<PrimaryConfigChangedCallback> primary_config_changed_cb_ + QUIC_GUARDED_BY(configs_lock_); + + // Used to protect the source-address tokens that are given to clients. + CryptoSecretBoxer source_address_token_boxer_; + + // server_nonce_boxer_ is used to encrypt and validate suggested server + // nonces. + CryptoSecretBoxer server_nonce_boxer_; + + // server_nonce_orbit_ contains the random, per-server orbit values that this + // server will use to generate server nonces (the moral equivalent of a SYN + // cookies). + uint8_t server_nonce_orbit_[8]; + + // proof_source_ contains an object that can provide certificate chains and + // signatures. + std::unique_ptr<ProofSource> proof_source_; + + // key_exchange_source_ contains an object that can provide key exchange + // objects. + std::unique_ptr<KeyExchangeSource> key_exchange_source_; + + // ssl_ctx_ contains the server configuration for doing TLS handshakes. + bssl::UniquePtr<SSL_CTX> ssl_ctx_; + + // These fields store configuration values. See the comments for their + // respective setter functions. + uint32_t source_address_token_future_secs_; + uint32_t source_address_token_lifetime_secs_; + + // Enable serving SCT or not. + bool enable_serving_sct_; + + // Does not own this observer. + RejectionObserver* rejection_observer_; + + // If non-empty, the server will operate in the pre-shared key mode by + // incorporating |pre_shared_key_| into the key schedule. + std::string pre_shared_key_; + + // Whether REJ message should be padded to max packet size. + bool pad_rej_; + + // Whether SHLO message should be padded to max packet size. + bool pad_shlo_; + + // If client is allowed to send a small client hello (by disabling padding), + // server MUST not check for the client hello size. + // DO NOT disable this unless you have some other way of validating client. + // (e.g. in realtime scenarios, where quic is tunneled through ICE, ICE will + // do its own peer validation using STUN pings with ufrag/upass). + bool validate_chlo_size_; + + // When source address is validated by some other means (e.g. when using ICE), + // source address token validation may be disabled. + bool validate_source_address_token_; +}; + +struct QUIC_EXPORT_PRIVATE QuicSignedServerConfig + : public quiche::QuicheReferenceCounted { + QuicSignedServerConfig(); + + QuicCryptoProof proof; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain; + // The server config that is used for this proof (and the rest of the + // request). + quiche::QuicheReferenceCountedPointer<QuicCryptoServerConfig::Config> config; + std::string primary_scid; + + protected: + ~QuicSignedServerConfig() override; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_
diff --git a/quiche/quic/core/crypto/quic_crypto_server_config_test.cc b/quiche/quic/core/crypto/quic_crypto_server_config_test.cc new file mode 100644 index 0000000..5a24881 --- /dev/null +++ b/quiche/quic/core/crypto/quic_crypto_server_config_test.cc
@@ -0,0 +1,500 @@ +// 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 "quiche/quic/core/crypto/quic_crypto_server_config.h" + +#include <stdarg.h> + +#include <memory> +#include <string> + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/cert_compressor.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_encrypter.h" +#include "quiche/quic/core/crypto/crypto_handshake_message.h" +#include "quiche/quic/core/crypto/crypto_secret_boxer.h" +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/proto/crypto_server_config_proto.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/mock_clock.h" +#include "quiche/quic/test_tools/quic_crypto_server_config_peer.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +namespace quic { +namespace test { +using ::testing::Not; + +// NOTE: This matcher depends on the wire format of serialzied protocol buffers, +// which may change in the future. +// Switch to ::testing::EqualsProto once it is available in Chromium. +MATCHER_P(SerializedProtoEquals, message, "") { + std::string expected_serialized, actual_serialized; + message.SerializeToString(&expected_serialized); + arg.SerializeToString(&actual_serialized); + return expected_serialized == actual_serialized; +} + +class QuicCryptoServerConfigTest : public QuicTest {}; + +TEST_F(QuicCryptoServerConfigTest, ServerConfig) { + QuicRandom* rand = QuicRandom::GetInstance(); + QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + MockClock clock; + + std::unique_ptr<CryptoHandshakeMessage> message(server.AddDefaultConfig( + rand, &clock, QuicCryptoServerConfig::ConfigOptions())); + + // The default configuration should have AES-GCM and at least one ChaCha20 + // cipher. + QuicTagVector aead; + ASSERT_THAT(message->GetTaglist(kAEAD, &aead), IsQuicNoError()); + EXPECT_THAT(aead, ::testing::Contains(kAESG)); + EXPECT_LE(1u, aead.size()); +} + +TEST_F(QuicCryptoServerConfigTest, CompressCerts) { + QuicCompressedCertsCache compressed_certs_cache( + QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); + + QuicRandom* rand = QuicRandom::GetInstance(); + QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + QuicCryptoServerConfigPeer peer(&server); + + std::vector<std::string> certs = {"testcert"}; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain( + new ProofSource::Chain(certs)); + + std::string compressed = QuicCryptoServerConfigPeer::CompressChain( + &compressed_certs_cache, chain, ""); + + EXPECT_EQ(compressed_certs_cache.Size(), 1u); +} + +TEST_F(QuicCryptoServerConfigTest, CompressSameCertsTwice) { + QuicCompressedCertsCache compressed_certs_cache( + QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); + + QuicRandom* rand = QuicRandom::GetInstance(); + QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + QuicCryptoServerConfigPeer peer(&server); + + // Compress the certs for the first time. + std::vector<std::string> certs = {"testcert"}; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain( + new ProofSource::Chain(certs)); + std::string cached_certs = ""; + + std::string compressed = QuicCryptoServerConfigPeer::CompressChain( + &compressed_certs_cache, chain, cached_certs); + EXPECT_EQ(compressed_certs_cache.Size(), 1u); + + // Compress the same certs, should use cache if available. + std::string compressed2 = QuicCryptoServerConfigPeer::CompressChain( + &compressed_certs_cache, chain, cached_certs); + EXPECT_EQ(compressed, compressed2); + EXPECT_EQ(compressed_certs_cache.Size(), 1u); +} + +TEST_F(QuicCryptoServerConfigTest, CompressDifferentCerts) { + // This test compresses a set of similar but not identical certs. Cache if + // used should return cache miss and add all the compressed certs. + QuicCompressedCertsCache compressed_certs_cache( + QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); + + QuicRandom* rand = QuicRandom::GetInstance(); + QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()); + QuicCryptoServerConfigPeer peer(&server); + + std::vector<std::string> certs = {"testcert"}; + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain( + new ProofSource::Chain(certs)); + std::string cached_certs = ""; + + std::string compressed = QuicCryptoServerConfigPeer::CompressChain( + &compressed_certs_cache, chain, cached_certs); + EXPECT_EQ(compressed_certs_cache.Size(), 1u); + + // Compress a similar certs which only differs in the chain. + quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain2( + new ProofSource::Chain(certs)); + + std::string compressed2 = QuicCryptoServerConfigPeer::CompressChain( + &compressed_certs_cache, chain2, cached_certs); + EXPECT_EQ(compressed_certs_cache.Size(), 2u); +} + +class SourceAddressTokenTest : public QuicTest { + public: + SourceAddressTokenTest() + : ip4_(QuicIpAddress::Loopback4()), + ip4_dual_(ip4_.DualStacked()), + ip6_(QuicIpAddress::Loopback6()), + original_time_(QuicWallTime::Zero()), + rand_(QuicRandom::GetInstance()), + server_(QuicCryptoServerConfig::TESTING, + rand_, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()), + peer_(&server_) { + // Advance the clock to some non-zero time. + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1000000)); + original_time_ = clock_.WallNow(); + + primary_config_ = server_.AddDefaultConfig( + rand_, &clock_, QuicCryptoServerConfig::ConfigOptions()); + } + + std::string NewSourceAddressToken(std::string config_id, + const QuicIpAddress& ip) { + return NewSourceAddressToken(config_id, ip, nullptr); + } + + std::string NewSourceAddressToken( + std::string config_id, + const QuicIpAddress& ip, + const SourceAddressTokens& previous_tokens) { + return peer_.NewSourceAddressToken(config_id, previous_tokens, ip, rand_, + clock_.WallNow(), nullptr); + } + + std::string NewSourceAddressToken( + std::string config_id, + const QuicIpAddress& ip, + CachedNetworkParameters* cached_network_params) { + SourceAddressTokens previous_tokens; + return peer_.NewSourceAddressToken(config_id, previous_tokens, ip, rand_, + clock_.WallNow(), cached_network_params); + } + + HandshakeFailureReason ValidateSourceAddressTokens(std::string config_id, + absl::string_view srct, + const QuicIpAddress& ip) { + return ValidateSourceAddressTokens(config_id, srct, ip, nullptr); + } + + HandshakeFailureReason ValidateSourceAddressTokens( + std::string config_id, + absl::string_view srct, + const QuicIpAddress& ip, + CachedNetworkParameters* cached_network_params) { + return peer_.ValidateSourceAddressTokens( + config_id, srct, ip, clock_.WallNow(), cached_network_params); + } + + const std::string kPrimary = "<primary>"; + const std::string kOverride = "Config with custom source address token key"; + + QuicIpAddress ip4_; + QuicIpAddress ip4_dual_; + QuicIpAddress ip6_; + + MockClock clock_; + QuicWallTime original_time_; + QuicRandom* rand_ = QuicRandom::GetInstance(); + QuicCryptoServerConfig server_; + QuicCryptoServerConfigPeer peer_; + // Stores the primary config. + std::unique_ptr<CryptoHandshakeMessage> primary_config_; + std::unique_ptr<QuicServerConfigProtobuf> override_config_protobuf_; +}; + +// Test basic behavior of source address tokens including being specific +// to a single IP address and server config. +TEST_F(SourceAddressTokenTest, SourceAddressToken) { + // Primary config generates configs that validate successfully. + const std::string token4 = NewSourceAddressToken(kPrimary, ip4_); + const std::string token4d = NewSourceAddressToken(kPrimary, ip4_dual_); + const std::string token6 = NewSourceAddressToken(kPrimary, ip6_); + EXPECT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4, ip4_)); + ASSERT_EQ(HANDSHAKE_OK, + ValidateSourceAddressTokens(kPrimary, token4, ip4_dual_)); + ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, + ValidateSourceAddressTokens(kPrimary, token4, ip6_)); + ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4d, ip4_)); + ASSERT_EQ(HANDSHAKE_OK, + ValidateSourceAddressTokens(kPrimary, token4d, ip4_dual_)); + ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, + ValidateSourceAddressTokens(kPrimary, token4d, ip6_)); + ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token6, ip6_)); +} + +TEST_F(SourceAddressTokenTest, SourceAddressTokenExpiration) { + const std::string token = NewSourceAddressToken(kPrimary, ip4_); + + // Validation fails if the token is from the future. + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(-3600 * 2)); + ASSERT_EQ(SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE, + ValidateSourceAddressTokens(kPrimary, token, ip4_)); + + // Validation fails after tokens expire. + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(86400 * 7)); + ASSERT_EQ(SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE, + ValidateSourceAddressTokens(kPrimary, token, ip4_)); +} + +TEST_F(SourceAddressTokenTest, SourceAddressTokenWithNetworkParams) { + // Make sure that if the source address token contains CachedNetworkParameters + // that this gets written to ValidateSourceAddressToken output argument. + CachedNetworkParameters cached_network_params_input; + cached_network_params_input.set_bandwidth_estimate_bytes_per_second(1234); + const std::string token4_with_cached_network_params = + NewSourceAddressToken(kPrimary, ip4_, &cached_network_params_input); + + CachedNetworkParameters cached_network_params_output; + EXPECT_THAT(cached_network_params_output, + Not(SerializedProtoEquals(cached_network_params_input))); + ValidateSourceAddressTokens(kPrimary, token4_with_cached_network_params, ip4_, + &cached_network_params_output); + EXPECT_THAT(cached_network_params_output, + SerializedProtoEquals(cached_network_params_input)); +} + +// Test the ability for a source address token to be valid for multiple +// addresses. +TEST_F(SourceAddressTokenTest, SourceAddressTokenMultipleAddresses) { + QuicWallTime now = clock_.WallNow(); + + // Now create a token which is usable for both addresses. + SourceAddressToken previous_token; + previous_token.set_ip(ip6_.DualStacked().ToPackedString()); + previous_token.set_timestamp(now.ToUNIXSeconds()); + SourceAddressTokens previous_tokens; + (*previous_tokens.add_tokens()) = previous_token; + const std::string token4or6 = + NewSourceAddressToken(kPrimary, ip4_, previous_tokens); + + EXPECT_EQ(HANDSHAKE_OK, + ValidateSourceAddressTokens(kPrimary, token4or6, ip4_)); + ASSERT_EQ(HANDSHAKE_OK, + ValidateSourceAddressTokens(kPrimary, token4or6, ip6_)); +} + +class CryptoServerConfigsTest : public QuicTest { + public: + CryptoServerConfigsTest() + : rand_(QuicRandom::GetInstance()), + config_(QuicCryptoServerConfig::TESTING, + rand_, + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default()), + test_peer_(&config_) {} + + void SetUp() override { + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1000)); + } + + // SetConfigs constructs suitable config protobufs and calls SetConfigs on + // |config_|. + // Each struct in the input vector contains 3 elements. + // The first is the server config ID of a Config. The second is + // the |primary_time| of that Config, given in epoch seconds. (Although note + // that, in these tests, time is set to 1000 seconds since the epoch.). + // The third is the priority. + // + // For example: + // SetConfigs(std::vector<ServerConfigIDWithTimeAndPriority>()); // calls + // |config_.SetConfigs| with no protobufs. + // + // // Calls |config_.SetConfigs| with two protobufs: one for a Config with + // // a |primary_time| of 900 and priority 1, and another with + // // a |primary_time| of 1000 and priority 2. + + // CheckConfigs( + // {{"id1", 900, 1}, + // {"id2", 1000, 2}}); + // + // If the server config id starts with "INVALID" then the generated protobuf + // will be invalid. + struct ServerConfigIDWithTimeAndPriority { + ServerConfigID server_config_id; + int primary_time; + int priority; + }; + void SetConfigs(std::vector<ServerConfigIDWithTimeAndPriority> configs) { + const char kOrbit[] = "12345678"; + + bool has_invalid = false; + + std::vector<QuicServerConfigProtobuf> protobufs; + for (const auto& config : configs) { + const ServerConfigID& server_config_id = config.server_config_id; + const int primary_time = config.primary_time; + const int priority = config.priority; + + QuicCryptoServerConfig::ConfigOptions options; + options.id = server_config_id; + options.orbit = kOrbit; + QuicServerConfigProtobuf protobuf = + QuicCryptoServerConfig::GenerateConfig(rand_, &clock_, options); + protobuf.set_primary_time(primary_time); + protobuf.set_priority(priority); + if (absl::StartsWith(std::string(server_config_id), "INVALID")) { + protobuf.clear_key(); + has_invalid = true; + } + protobufs.push_back(std::move(protobuf)); + } + + ASSERT_EQ(!has_invalid && !configs.empty(), + config_.SetConfigs(protobufs, /* fallback_protobuf = */ nullptr, + clock_.WallNow())); + } + + protected: + QuicRandom* const rand_; + MockClock clock_; + QuicCryptoServerConfig config_; + QuicCryptoServerConfigPeer test_peer_; +}; + +TEST_F(CryptoServerConfigsTest, NoConfigs) { + test_peer_.CheckConfigs(std::vector<std::pair<std::string, bool>>()); +} + +TEST_F(CryptoServerConfigsTest, MakePrimaryFirst) { + // Make sure that "b" is primary even though "a" comes first. + SetConfigs({{"a", 1100, 1}, {"b", 900, 1}}); + test_peer_.CheckConfigs({{"a", false}, {"b", true}}); +} + +TEST_F(CryptoServerConfigsTest, MakePrimarySecond) { + // Make sure that a remains primary after b is added. + SetConfigs({{"a", 900, 1}, {"b", 1100, 1}}); + test_peer_.CheckConfigs({{"a", true}, {"b", false}}); +} + +TEST_F(CryptoServerConfigsTest, Delete) { + // Ensure that configs get deleted when removed. + SetConfigs({{"a", 800, 1}, {"b", 900, 1}, {"c", 1100, 1}}); + test_peer_.CheckConfigs({{"a", false}, {"b", true}, {"c", false}}); + SetConfigs({{"b", 900, 1}, {"c", 1100, 1}}); + test_peer_.CheckConfigs({{"b", true}, {"c", false}}); +} + +TEST_F(CryptoServerConfigsTest, DeletePrimary) { + // Ensure that deleting the primary config works. + SetConfigs({{"a", 800, 1}, {"b", 900, 1}, {"c", 1100, 1}}); + test_peer_.CheckConfigs({{"a", false}, {"b", true}, {"c", false}}); + SetConfigs({{"a", 800, 1}, {"c", 1100, 1}}); + test_peer_.CheckConfigs({{"a", true}, {"c", false}}); +} + +TEST_F(CryptoServerConfigsTest, FailIfDeletingAllConfigs) { + // Ensure that configs get deleted when removed. + SetConfigs({{"a", 800, 1}, {"b", 900, 1}}); + test_peer_.CheckConfigs({{"a", false}, {"b", true}}); + SetConfigs(std::vector<ServerConfigIDWithTimeAndPriority>()); + // Config change is rejected, still using old configs. + test_peer_.CheckConfigs({{"a", false}, {"b", true}}); +} + +TEST_F(CryptoServerConfigsTest, ChangePrimaryTime) { + // Check that updates to primary time get picked up. + SetConfigs({{"a", 400, 1}, {"b", 800, 1}, {"c", 1200, 1}}); + test_peer_.SelectNewPrimaryConfig(500); + test_peer_.CheckConfigs({{"a", true}, {"b", false}, {"c", false}}); + SetConfigs({{"a", 1200, 1}, {"b", 800, 1}, {"c", 400, 1}}); + test_peer_.SelectNewPrimaryConfig(500); + test_peer_.CheckConfigs({{"a", false}, {"b", false}, {"c", true}}); +} + +TEST_F(CryptoServerConfigsTest, AllConfigsInThePast) { + // Check that the most recent config is selected. + SetConfigs({{"a", 400, 1}, {"b", 800, 1}, {"c", 1200, 1}}); + test_peer_.SelectNewPrimaryConfig(1500); + test_peer_.CheckConfigs({{"a", false}, {"b", false}, {"c", true}}); +} + +TEST_F(CryptoServerConfigsTest, AllConfigsInTheFuture) { + // Check that the first config is selected. + SetConfigs({{"a", 400, 1}, {"b", 800, 1}, {"c", 1200, 1}}); + test_peer_.SelectNewPrimaryConfig(100); + test_peer_.CheckConfigs({{"a", true}, {"b", false}, {"c", false}}); +} + +TEST_F(CryptoServerConfigsTest, SortByPriority) { + // Check that priority is used to decide on a primary config when + // configs have the same primary time. + SetConfigs({{"a", 900, 1}, {"b", 900, 2}, {"c", 900, 3}}); + test_peer_.CheckConfigs({{"a", true}, {"b", false}, {"c", false}}); + test_peer_.SelectNewPrimaryConfig(800); + test_peer_.CheckConfigs({{"a", true}, {"b", false}, {"c", false}}); + test_peer_.SelectNewPrimaryConfig(1000); + test_peer_.CheckConfigs({{"a", true}, {"b", false}, {"c", false}}); + + // Change priorities and expect sort order to change. + SetConfigs({{"a", 900, 2}, {"b", 900, 1}, {"c", 900, 0}}); + test_peer_.CheckConfigs({{"a", false}, {"b", false}, {"c", true}}); + test_peer_.SelectNewPrimaryConfig(800); + test_peer_.CheckConfigs({{"a", false}, {"b", false}, {"c", true}}); + test_peer_.SelectNewPrimaryConfig(1000); + test_peer_.CheckConfigs({{"a", false}, {"b", false}, {"c", true}}); +} + +TEST_F(CryptoServerConfigsTest, AdvancePrimary) { + // Check that a new primary config is enabled at the right time. + SetConfigs({{"a", 900, 1}, {"b", 1100, 1}}); + test_peer_.SelectNewPrimaryConfig(1000); + test_peer_.CheckConfigs({{"a", true}, {"b", false}}); + test_peer_.SelectNewPrimaryConfig(1101); + test_peer_.CheckConfigs({{"a", false}, {"b", true}}); +} + +class ValidateCallback : public ValidateClientHelloResultCallback { + public: + void Run(quiche::QuicheReferenceCountedPointer<Result> /*result*/, + std::unique_ptr<ProofSource::Details> /*details*/) override {} +}; + +TEST_F(CryptoServerConfigsTest, AdvancePrimaryViaValidate) { + // Check that a new primary config is enabled at the right time. + SetConfigs({{"a", 900, 1}, {"b", 1100, 1}}); + test_peer_.SelectNewPrimaryConfig(1000); + test_peer_.CheckConfigs({{"a", true}, {"b", false}}); + CryptoHandshakeMessage client_hello; + QuicSocketAddress client_address; + QuicSocketAddress server_address; + QuicTransportVersion transport_version = QUIC_VERSION_UNSUPPORTED; + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + if (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO) { + transport_version = version.transport_version; + break; + } + } + ASSERT_NE(transport_version, QUIC_VERSION_UNSUPPORTED); + MockClock clock; + quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config( + new QuicSignedServerConfig); + std::unique_ptr<ValidateClientHelloResultCallback> done_cb( + new ValidateCallback); + clock.AdvanceTime(QuicTime::Delta::FromSeconds(1100)); + config_.ValidateClientHello(client_hello, client_address, server_address, + transport_version, &clock, signed_config, + std::move(done_cb)); + test_peer_.CheckConfigs({{"a", false}, {"b", true}}); +} + +TEST_F(CryptoServerConfigsTest, InvalidConfigs) { + // Ensure that invalid configs don't change anything. + SetConfigs({{"a", 800, 1}, {"b", 900, 1}, {"c", 1100, 1}}); + test_peer_.CheckConfigs({{"a", false}, {"b", true}, {"c", false}}); + SetConfigs({{"a", 800, 1}, {"c", 1100, 1}, {"INVALID1", 1000, 1}}); + test_peer_.CheckConfigs({{"a", false}, {"b", true}, {"c", false}}); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_decrypter.cc b/quiche/quic/core/crypto/quic_decrypter.cc new file mode 100644 index 0000000..f91a786 --- /dev/null +++ b/quiche/quic/core/crypto/quic_decrypter.cc
@@ -0,0 +1,80 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_decrypter.h" + +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/tls1.h" +#include "quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h" +#include "quiche/quic/core/crypto/aes_128_gcm_decrypter.h" +#include "quiche/quic/core/crypto/aes_256_gcm_decrypter.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_decrypter.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/null_decrypter.h" +#include "quiche/quic/core/crypto/quic_hkdf.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +// static +std::unique_ptr<QuicDecrypter> QuicDecrypter::Create( + const ParsedQuicVersion& version, + QuicTag algorithm) { + switch (algorithm) { + case kAESG: + if (version.UsesInitialObfuscators()) { + return std::make_unique<Aes128GcmDecrypter>(); + } else { + return std::make_unique<Aes128Gcm12Decrypter>(); + } + case kCC20: + if (version.UsesInitialObfuscators()) { + return std::make_unique<ChaCha20Poly1305TlsDecrypter>(); + } else { + return std::make_unique<ChaCha20Poly1305Decrypter>(); + } + default: + QUIC_LOG(FATAL) << "Unsupported algorithm: " << algorithm; + return nullptr; + } +} + +// static +std::unique_ptr<QuicDecrypter> QuicDecrypter::CreateFromCipherSuite( + uint32_t cipher_suite) { + switch (cipher_suite) { + case TLS1_CK_AES_128_GCM_SHA256: + return std::make_unique<Aes128GcmDecrypter>(); + case TLS1_CK_AES_256_GCM_SHA384: + return std::make_unique<Aes256GcmDecrypter>(); + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return std::make_unique<ChaCha20Poly1305TlsDecrypter>(); + default: + QUIC_BUG(quic_bug_10660_1) << "TLS cipher suite is unknown to QUIC"; + return nullptr; + } +} + +// static +void QuicDecrypter::DiversifyPreliminaryKey(absl::string_view preliminary_key, + absl::string_view nonce_prefix, + const DiversificationNonce& nonce, + size_t key_size, + size_t nonce_prefix_size, + std::string* out_key, + std::string* out_nonce_prefix) { + QuicHKDF hkdf((std::string(preliminary_key)) + (std::string(nonce_prefix)), + absl::string_view(nonce.data(), nonce.size()), + "QUIC key diversification", 0, key_size, 0, nonce_prefix_size, + 0); + *out_key = std::string(hkdf.server_write_key()); + *out_nonce_prefix = std::string(hkdf.server_write_iv()); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_decrypter.h b/quiche/quic/core/crypto/quic_decrypter.h new file mode 100644 index 0000000..2cd799c --- /dev/null +++ b/quiche/quic/core/crypto/quic_decrypter.h
@@ -0,0 +1,95 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_DECRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_DECRYPTER_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/quic_crypter.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE QuicDecrypter : public QuicCrypter { + public: + virtual ~QuicDecrypter() {} + + static std::unique_ptr<QuicDecrypter> Create(const ParsedQuicVersion& version, + QuicTag algorithm); + + // Creates an IETF QuicDecrypter based on |cipher_suite| which must be an id + // returned by SSL_CIPHER_get_id. The caller is responsible for taking + // ownership of the new QuicDecrypter. + static std::unique_ptr<QuicDecrypter> CreateFromCipherSuite( + uint32_t cipher_suite); + + // Sets the encryption key. Returns true on success, false on failure. + // |DecryptPacket| may not be called until |SetDiversificationNonce| is + // called and the preliminary keying material will be combined with that + // nonce in order to create the actual key and nonce-prefix. + // + // If this function is called, neither |SetKey| nor |SetNoncePrefix| may be + // called. + virtual bool SetPreliminaryKey(absl::string_view key) = 0; + + // SetDiversificationNonce uses |nonce| to derive final keys based on the + // input keying material given by calling |SetPreliminaryKey|. + // + // Calling this function is a no-op if |SetPreliminaryKey| hasn't been + // called. + virtual bool SetDiversificationNonce(const DiversificationNonce& nonce) = 0; + + // Populates |output| with the decrypted |ciphertext| and populates + // |output_length| with the length. Returns 0 if there is an error. + // |output| size is specified by |max_output_length| and must be + // at least as large as the ciphertext. |packet_number| is + // appended to the |nonce_prefix| value provided in SetNoncePrefix() + // to form the nonce. + // TODO(wtc): add a way for DecryptPacket to report decryption failure due + // to non-authentic inputs, as opposed to other reasons for failure. + virtual bool DecryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view ciphertext, + char* output, + size_t* output_length, + size_t max_output_length) = 0; + + // Reads a sample of ciphertext from |sample_reader| and uses the header + // protection key to generate a mask to use for header protection. If + // successful, this function returns this mask, which is at least 5 bytes + // long. Callers can detect failure by checking if the output string is empty. + virtual std::string GenerateHeaderProtectionMask( + QuicDataReader* sample_reader) = 0; + + // The ID of the cipher. Return 0x03000000 ORed with the 'cryptographic suite + // selector'. + virtual uint32_t cipher_id() const = 0; + + // Returns the maximum number of packets that can safely fail decryption with + // this decrypter. + virtual QuicPacketCount GetIntegrityLimit() const = 0; + + // For use by unit tests only. + virtual absl::string_view GetKey() const = 0; + virtual absl::string_view GetNoncePrefix() const = 0; + + static void DiversifyPreliminaryKey(absl::string_view preliminary_key, + absl::string_view nonce_prefix, + const DiversificationNonce& nonce, + size_t key_size, + size_t nonce_prefix_size, + std::string* out_key, + std::string* out_nonce_prefix); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_DECRYPTER_H_
diff --git a/quiche/quic/core/crypto/quic_encrypter.cc b/quiche/quic/core/crypto/quic_encrypter.cc new file mode 100644 index 0000000..4ae5232 --- /dev/null +++ b/quiche/quic/core/crypto/quic_encrypter.cc
@@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_encrypter.h" + +#include <utility> + +#include "third_party/boringssl/src/include/openssl/tls1.h" +#include "quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h" +#include "quiche/quic/core/crypto/aes_128_gcm_encrypter.h" +#include "quiche/quic/core/crypto/aes_256_gcm_encrypter.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_encrypter.h" +#include "quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/crypto/null_encrypter.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +// static +std::unique_ptr<QuicEncrypter> QuicEncrypter::Create( + const ParsedQuicVersion& version, + QuicTag algorithm) { + switch (algorithm) { + case kAESG: + if (version.UsesInitialObfuscators()) { + return std::make_unique<Aes128GcmEncrypter>(); + } else { + return std::make_unique<Aes128Gcm12Encrypter>(); + } + case kCC20: + if (version.UsesInitialObfuscators()) { + return std::make_unique<ChaCha20Poly1305TlsEncrypter>(); + } else { + return std::make_unique<ChaCha20Poly1305Encrypter>(); + } + default: + QUIC_LOG(FATAL) << "Unsupported algorithm: " << algorithm; + return nullptr; + } +} + +// static +std::unique_ptr<QuicEncrypter> QuicEncrypter::CreateFromCipherSuite( + uint32_t cipher_suite) { + switch (cipher_suite) { + case TLS1_CK_AES_128_GCM_SHA256: + return std::make_unique<Aes128GcmEncrypter>(); + case TLS1_CK_AES_256_GCM_SHA384: + return std::make_unique<Aes256GcmEncrypter>(); + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return std::make_unique<ChaCha20Poly1305TlsEncrypter>(); + default: + QUIC_BUG(quic_bug_10711_1) << "TLS cipher suite is unknown to QUIC"; + return nullptr; + } +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_encrypter.h b/quiche/quic/core/crypto/quic_encrypter.h new file mode 100644 index 0000000..6caf51b --- /dev/null +++ b/quiche/quic/core/crypto/quic_encrypter.h
@@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_ENCRYPTER_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_ENCRYPTER_H_ + +#include <cstddef> +#include <memory> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/quic_crypter.h" +#include "quiche/quic/core/quic_packets.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +class QUIC_EXPORT_PRIVATE QuicEncrypter : public QuicCrypter { + public: + virtual ~QuicEncrypter() {} + + static std::unique_ptr<QuicEncrypter> Create(const ParsedQuicVersion& version, + QuicTag algorithm); + + // Creates an IETF QuicEncrypter based on |cipher_suite| which must be an id + // returned by SSL_CIPHER_get_id. The caller is responsible for taking + // ownership of the new QuicEncrypter. + static std::unique_ptr<QuicEncrypter> CreateFromCipherSuite( + uint32_t cipher_suite); + + // Writes encrypted |plaintext| and a MAC over |plaintext| and + // |associated_data| into output. Sets |output_length| to the number of + // bytes written. Returns true on success or false if there was an error. + // |packet_number| is appended to the |nonce_prefix| value provided in + // SetNoncePrefix() to form the nonce. |output| must not overlap with + // |associated_data|. If |output| overlaps with |plaintext| then + // |plaintext| must be <= |output|. + virtual bool EncryptPacket(uint64_t packet_number, + absl::string_view associated_data, + absl::string_view plaintext, + char* output, + size_t* output_length, + size_t max_output_length) = 0; + + // Takes a |sample| of ciphertext and uses the header protection key to + // generate a mask to use for header protection, and returns that mask. On + // success, the mask will be at least 5 bytes long; on failure the string will + // be empty. + virtual std::string GenerateHeaderProtectionMask( + absl::string_view sample) = 0; + + // Returns the maximum length of plaintext that can be encrypted + // to ciphertext no larger than |ciphertext_size|. + virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const = 0; + + // Returns the length of the ciphertext that would be generated by encrypting + // to plaintext of size |plaintext_size|. + virtual size_t GetCiphertextSize(size_t plaintext_size) const = 0; + + // Returns the maximum number of packets that can be safely encrypted with + // this encrypter. + virtual QuicPacketCount GetConfidentialityLimit() const = 0; + + // For use by unit tests only. + virtual absl::string_view GetKey() const = 0; + virtual absl::string_view GetNoncePrefix() const = 0; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_ENCRYPTER_H_
diff --git a/quiche/quic/core/crypto/quic_hkdf.cc b/quiche/quic/core/crypto/quic_hkdf.cc new file mode 100644 index 0000000..2ad00f2 --- /dev/null +++ b/quiche/quic/core/crypto/quic_hkdf.cc
@@ -0,0 +1,107 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_hkdf.h" + +#include <memory> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "quiche/quic/platform/api/quic_logging.h" + +namespace quic { + +const size_t kSHA256HashLength = 32; +const size_t kMaxKeyMaterialSize = kSHA256HashLength * 256; + +QuicHKDF::QuicHKDF(absl::string_view secret, + absl::string_view salt, + absl::string_view info, + size_t key_bytes_to_generate, + size_t iv_bytes_to_generate, + size_t subkey_secret_bytes_to_generate) + : QuicHKDF(secret, + salt, + info, + key_bytes_to_generate, + key_bytes_to_generate, + iv_bytes_to_generate, + iv_bytes_to_generate, + subkey_secret_bytes_to_generate) {} + +QuicHKDF::QuicHKDF(absl::string_view secret, + absl::string_view salt, + absl::string_view info, + size_t client_key_bytes_to_generate, + size_t server_key_bytes_to_generate, + size_t client_iv_bytes_to_generate, + size_t server_iv_bytes_to_generate, + size_t subkey_secret_bytes_to_generate) { + const size_t material_length = + 2 * client_key_bytes_to_generate + client_iv_bytes_to_generate + + 2 * server_key_bytes_to_generate + server_iv_bytes_to_generate + + subkey_secret_bytes_to_generate; + QUICHE_DCHECK_LT(material_length, kMaxKeyMaterialSize); + + output_.resize(material_length); + // On Windows, when the size of output_ is zero, dereference of 0'th element + // results in a crash. C++11 solves this problem by adding a data() getter + // method to std::vector. + if (output_.empty()) { + return; + } + + ::HKDF(&output_[0], output_.size(), ::EVP_sha256(), + reinterpret_cast<const uint8_t*>(secret.data()), secret.size(), + reinterpret_cast<const uint8_t*>(salt.data()), salt.size(), + reinterpret_cast<const uint8_t*>(info.data()), info.size()); + + size_t j = 0; + if (client_key_bytes_to_generate) { + client_write_key_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + client_key_bytes_to_generate); + j += client_key_bytes_to_generate; + } + + if (server_key_bytes_to_generate) { + server_write_key_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + server_key_bytes_to_generate); + j += server_key_bytes_to_generate; + } + + if (client_iv_bytes_to_generate) { + client_write_iv_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + client_iv_bytes_to_generate); + j += client_iv_bytes_to_generate; + } + + if (server_iv_bytes_to_generate) { + server_write_iv_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + server_iv_bytes_to_generate); + j += server_iv_bytes_to_generate; + } + + if (subkey_secret_bytes_to_generate) { + subkey_secret_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + subkey_secret_bytes_to_generate); + j += subkey_secret_bytes_to_generate; + } + // Repeat client and server key bytes for header protection keys. + if (client_key_bytes_to_generate) { + client_hp_key_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + client_key_bytes_to_generate); + j += client_key_bytes_to_generate; + } + + if (server_key_bytes_to_generate) { + server_hp_key_ = absl::string_view(reinterpret_cast<char*>(&output_[j]), + server_key_bytes_to_generate); + j += server_key_bytes_to_generate; + } +} + +QuicHKDF::~QuicHKDF() {} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_hkdf.h b/quiche/quic/core/crypto/quic_hkdf.h new file mode 100644 index 0000000..0040447 --- /dev/null +++ b/quiche/quic/core/crypto/quic_hkdf.h
@@ -0,0 +1,76 @@ +// Copyright 2018 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_HKDF_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_HKDF_H_ + +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// QuicHKDF implements the key derivation function specified in RFC 5869 +// (using SHA-256) and outputs key material, as needed by QUIC. +// See https://tools.ietf.org/html/rfc5869 for details. +class QUIC_EXPORT_PRIVATE QuicHKDF { + public: + // |secret|: the input shared secret (or, from RFC 5869, the IKM). + // |salt|: an (optional) public salt / non-secret random value. While + // optional, callers are strongly recommended to provide a salt. There is no + // added security value in making this larger than the SHA-256 block size of + // 64 bytes. + // |info|: an (optional) label to distinguish different uses of HKDF. It is + // optional context and application specific information (can be a zero-length + // string). + // |key_bytes_to_generate|: the number of bytes of key material to generate + // for both client and server. + // |iv_bytes_to_generate|: the number of bytes of IV to generate for both + // client and server. + // |subkey_secret_bytes_to_generate|: the number of bytes of subkey secret to + // generate, shared between client and server. + QuicHKDF(absl::string_view secret, + absl::string_view salt, + absl::string_view info, + size_t key_bytes_to_generate, + size_t iv_bytes_to_generate, + size_t subkey_secret_bytes_to_generate); + + // An alternative constructor that allows the client and server key/IV + // lengths to be different. + QuicHKDF(absl::string_view secret, + absl::string_view salt, + absl::string_view info, + size_t client_key_bytes_to_generate, + size_t server_key_bytes_to_generate, + size_t client_iv_bytes_to_generate, + size_t server_iv_bytes_to_generate, + size_t subkey_secret_bytes_to_generate); + + ~QuicHKDF(); + + absl::string_view client_write_key() const { return client_write_key_; } + absl::string_view client_write_iv() const { return client_write_iv_; } + absl::string_view server_write_key() const { return server_write_key_; } + absl::string_view server_write_iv() const { return server_write_iv_; } + absl::string_view subkey_secret() const { return subkey_secret_; } + absl::string_view client_hp_key() const { return client_hp_key_; } + absl::string_view server_hp_key() const { return server_hp_key_; } + + private: + std::vector<uint8_t> output_; + + absl::string_view client_write_key_; + absl::string_view server_write_key_; + absl::string_view client_write_iv_; + absl::string_view server_write_iv_; + absl::string_view subkey_secret_; + absl::string_view client_hp_key_; + absl::string_view server_hp_key_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_HKDF_H_
diff --git a/quiche/quic/core/crypto/quic_hkdf_test.cc b/quiche/quic/core/crypto/quic_hkdf_test.cc new file mode 100644 index 0000000..48f041f --- /dev/null +++ b/quiche/quic/core/crypto/quic_hkdf_test.cc
@@ -0,0 +1,91 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_hkdf.h" + +#include <string> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { +namespace { + +struct HKDFInput { + const char* key_hex; + const char* salt_hex; + const char* info_hex; + const char* output_hex; +}; + +// These test cases are taken from +// https://tools.ietf.org/html/rfc5869#appendix-A. +static const HKDFInput kHKDFInputs[] = { + { + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "000102030405060708090a0b0c", + "f0f1f2f3f4f5f6f7f8f9", + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf340072" + "08d5" + "b887185865", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122" + "2324" + "25262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344454647" + "4849" + "4a4b4c4d4e4f", + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182" + "8384" + "85868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7" + "a8a9" + "aaabacadaeaf", + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2" + "d3d4" + "d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7" + "f8f9" + "fafbfcfdfeff", + "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a" + "99ca" + "c7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87" + "c14c" + "01d5c1f3434f1d87", + }, + { + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "", + "", + "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d2013" + "95fa" + "a4b61a96c8", + }, +}; + +class QuicHKDFTest : public QuicTest {}; + +TEST_F(QuicHKDFTest, HKDF) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(kHKDFInputs); i++) { + const HKDFInput& test(kHKDFInputs[i]); + SCOPED_TRACE(i); + + const std::string key = absl::HexStringToBytes(test.key_hex); + const std::string salt = absl::HexStringToBytes(test.salt_hex); + const std::string info = absl::HexStringToBytes(test.info_hex); + const std::string expected = absl::HexStringToBytes(test.output_hex); + + // We set the key_length to the length of the expected output and then take + // the result from the first key, which is the client write key. + QuicHKDF hkdf(key, salt, info, expected.size(), 0, 0); + + ASSERT_EQ(expected.size(), hkdf.client_write_key().size()); + EXPECT_EQ(0, memcmp(expected.data(), hkdf.client_write_key().data(), + expected.size())); + } +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_random.cc b/quiche/quic/core/crypto/quic_random.cc new file mode 100644 index 0000000..22eed48 --- /dev/null +++ b/quiche/quic/core/crypto/quic_random.cc
@@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_random.h" +#include <cstdint> +#include <cstring> + +#include "third_party/boringssl/src/include/openssl/rand.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/common/platform/api/quiche_logging.h" + +namespace quic { + +namespace { + +// Insecure randomness in DefaultRandom uses an implementation of +// xoshiro256++ 1.0 based on code in the public domain from +// <http://prng.di.unimi.it/xoshiro256plusplus.c>. + +inline uint64_t Xoshiro256InitializeRngStateMember() { + uint64_t result; + RAND_bytes(reinterpret_cast<uint8_t*>(&result), sizeof(result)); + return result; +} + +inline uint64_t Xoshiro256PlusPlusRotLeft(uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t Xoshiro256PlusPlus() { + static thread_local uint64_t rng_state[4] = { + Xoshiro256InitializeRngStateMember(), + Xoshiro256InitializeRngStateMember(), + Xoshiro256InitializeRngStateMember(), + Xoshiro256InitializeRngStateMember()}; + const uint64_t result = + Xoshiro256PlusPlusRotLeft(rng_state[0] + rng_state[3], 23) + rng_state[0]; + const uint64_t t = rng_state[1] << 17; + rng_state[2] ^= rng_state[0]; + rng_state[3] ^= rng_state[1]; + rng_state[1] ^= rng_state[2]; + rng_state[0] ^= rng_state[3]; + rng_state[2] ^= t; + rng_state[3] = Xoshiro256PlusPlusRotLeft(rng_state[3], 45); + return result; +} + +class DefaultRandom : public QuicRandom { + public: + DefaultRandom() {} + DefaultRandom(const DefaultRandom&) = delete; + DefaultRandom& operator=(const DefaultRandom&) = delete; + ~DefaultRandom() override {} + + // QuicRandom implementation + void RandBytes(void* data, size_t len) override; + uint64_t RandUint64() override; + void InsecureRandBytes(void* data, size_t len) override; + uint64_t InsecureRandUint64() override; +}; + +void DefaultRandom::RandBytes(void* data, size_t len) { + RAND_bytes(reinterpret_cast<uint8_t*>(data), len); +} + +uint64_t DefaultRandom::RandUint64() { + uint64_t value; + RandBytes(&value, sizeof(value)); + return value; +} + +void DefaultRandom::InsecureRandBytes(void* data, size_t len) { + while (len >= sizeof(uint64_t)) { + uint64_t random_bytes64 = Xoshiro256PlusPlus(); + memcpy(data, &random_bytes64, sizeof(uint64_t)); + data = reinterpret_cast<char*>(data) + sizeof(uint64_t); + len -= sizeof(uint64_t); + } + if (len > 0) { + QUICHE_DCHECK_LT(len, sizeof(uint64_t)); + uint64_t random_bytes64 = Xoshiro256PlusPlus(); + memcpy(data, &random_bytes64, len); + } +} + +uint64_t DefaultRandom::InsecureRandUint64() { + return Xoshiro256PlusPlus(); +} + +} // namespace + +// static +QuicRandom* QuicRandom::GetInstance() { + static DefaultRandom* random = new DefaultRandom(); + return random; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/quic_random.h b/quiche/quic/core/crypto/quic_random.h new file mode 100644 index 0000000..47722cb --- /dev/null +++ b/quiche/quic/core/crypto/quic_random.h
@@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_QUIC_RANDOM_H_ +#define QUICHE_QUIC_CORE_CRYPTO_QUIC_RANDOM_H_ + +#include <cstddef> +#include <cstdint> + +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// The interface for a random number generator. +class QUIC_EXPORT_PRIVATE QuicRandom { + public: + virtual ~QuicRandom() {} + + // Returns the default random number generator, which is cryptographically + // secure and thread-safe. + static QuicRandom* GetInstance(); + + // Generates |len| random bytes in the |data| buffer. + virtual void RandBytes(void* data, size_t len) = 0; + + // Returns a random number in the range [0, kuint64max]. + virtual uint64_t RandUint64() = 0; + + // Generates |len| random bytes in the |data| buffer. This MUST NOT be used + // for any application that requires cryptographically-secure randomness. + virtual void InsecureRandBytes(void* data, size_t len) = 0; + + // Returns a random number in the range [0, kuint64max]. This MUST NOT be used + // for any application that requires cryptographically-secure randomness. + virtual uint64_t InsecureRandUint64() = 0; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_QUIC_RANDOM_H_
diff --git a/quiche/quic/core/crypto/quic_random_test.cc b/quiche/quic/core/crypto/quic_random_test.cc new file mode 100644 index 0000000..2a43c29 --- /dev/null +++ b/quiche/quic/core/crypto/quic_random_test.cc
@@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/quic_random.h" + +#include "quiche/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { + +class QuicRandomTest : public QuicTest {}; + +TEST_F(QuicRandomTest, RandBytes) { + unsigned char buf1[16]; + unsigned char buf2[16]; + memset(buf1, 0xaf, sizeof(buf1)); + memset(buf2, 0xaf, sizeof(buf2)); + ASSERT_EQ(0, memcmp(buf1, buf2, sizeof(buf1))); + + QuicRandom* rng = QuicRandom::GetInstance(); + rng->RandBytes(buf1, sizeof(buf1)); + EXPECT_NE(0, memcmp(buf1, buf2, sizeof(buf1))); +} + +TEST_F(QuicRandomTest, RandUint64) { + QuicRandom* rng = QuicRandom::GetInstance(); + uint64_t value1 = rng->RandUint64(); + uint64_t value2 = rng->RandUint64(); + EXPECT_NE(value1, value2); +} + +TEST_F(QuicRandomTest, InsecureRandBytes) { + unsigned char buf1[16]; + unsigned char buf2[16]; + memset(buf1, 0xaf, sizeof(buf1)); + memset(buf2, 0xaf, sizeof(buf2)); + ASSERT_EQ(0, memcmp(buf1, buf2, sizeof(buf1))); + + QuicRandom* rng = QuicRandom::GetInstance(); + rng->InsecureRandBytes(buf1, sizeof(buf1)); + EXPECT_NE(0, memcmp(buf1, buf2, sizeof(buf1))); +} + +TEST_F(QuicRandomTest, InsecureRandUint64) { + QuicRandom* rng = QuicRandom::GetInstance(); + uint64_t value1 = rng->InsecureRandUint64(); + uint64_t value2 = rng->InsecureRandUint64(); + EXPECT_NE(value1, value2); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/tls_client_connection.cc b/quiche/quic/core/crypto/tls_client_connection.cc new file mode 100644 index 0000000..9140527 --- /dev/null +++ b/quiche/quic/core/crypto/tls_client_connection.cc
@@ -0,0 +1,50 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/tls_client_connection.h" + +namespace quic { + +TlsClientConnection::TlsClientConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config) + : TlsConnection(ssl_ctx, + delegate->ConnectionDelegate(), + std::move(ssl_config)), + delegate_(delegate) {} + +// static +bssl::UniquePtr<SSL_CTX> TlsClientConnection::CreateSslCtx( + bool enable_early_data) { + bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx(); + // Configure certificate verification. + SSL_CTX_set_custom_verify(ssl_ctx.get(), SSL_VERIFY_PEER, &VerifyCallback); + int reverify_on_resume_enabled = 1; + SSL_CTX_set_reverify_on_resume(ssl_ctx.get(), reverify_on_resume_enabled); + + // Configure session caching. + SSL_CTX_set_session_cache_mode( + ssl_ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback); + + // TODO(wub): Always enable early data on the SSL_CTX, but allow it to be + // overridden on the SSL object, via QuicSSLConfig. + SSL_CTX_set_early_data_enabled(ssl_ctx.get(), enable_early_data); + return ssl_ctx; +} + +void TlsClientConnection::SetCertChain( + const std::vector<CRYPTO_BUFFER*>& cert_chain, EVP_PKEY* privkey) { + SSL_set_chain_and_key(ssl(), cert_chain.data(), cert_chain.size(), privkey, + /*privkey_method=*/nullptr); +} + +// static +int TlsClientConnection::NewSessionCallback(SSL* ssl, SSL_SESSION* session) { + static_cast<TlsClientConnection*>(ConnectionFromSsl(ssl)) + ->delegate_->InsertSession(bssl::UniquePtr<SSL_SESSION>(session)); + return 1; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/tls_client_connection.h b/quiche/quic/core/crypto/tls_client_connection.h new file mode 100644 index 0000000..1c98a70 --- /dev/null +++ b/quiche/quic/core/crypto/tls_client_connection.h
@@ -0,0 +1,55 @@ +// Copyright (c) 2019 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_TLS_CLIENT_CONNECTION_H_ +#define QUICHE_QUIC_CORE_CRYPTO_TLS_CLIENT_CONNECTION_H_ + +#include "quiche/quic/core/crypto/tls_connection.h" + +namespace quic { + +// TlsClientConnection receives calls for client-specific BoringSSL callbacks +// and calls its Delegate for the implementation of those callbacks. +class QUIC_EXPORT_PRIVATE TlsClientConnection : public TlsConnection { + public: + // A TlsClientConnection::Delegate implements the client-specific methods that + // are set as callbacks for an SSL object. + class QUIC_EXPORT_PRIVATE Delegate { + public: + virtual ~Delegate() {} + + protected: + // Called when a NewSessionTicket is received from the server. + virtual void InsertSession(bssl::UniquePtr<SSL_SESSION> session) = 0; + + // Provides the delegate for callbacks that are shared between client and + // server. + virtual TlsConnection::Delegate* ConnectionDelegate() = 0; + + friend class TlsClientConnection; + }; + + TlsClientConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config); + + // Creates and configures an SSL_CTX that is appropriate for clients to use. + static bssl::UniquePtr<SSL_CTX> CreateSslCtx(bool enable_early_data); + + // Set the client cert and private key to be used on this connection, if + // requested by the server. + void SetCertChain(const std::vector<CRYPTO_BUFFER*>& cert_chain, + EVP_PKEY* privkey); + + private: + // Registered as the callback for SSL_CTX_sess_set_new_cb, which calls + // Delegate::InsertSession. + static int NewSessionCallback(SSL* ssl, SSL_SESSION* session); + + Delegate* delegate_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_TLS_CLIENT_CONNECTION_H_
diff --git a/quiche/quic/core/crypto/tls_connection.cc b/quiche/quic/core/crypto/tls_connection.cc new file mode 100644 index 0000000..d1be900 --- /dev/null +++ b/quiche/quic/core/crypto/tls_connection.cc
@@ -0,0 +1,212 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/tls_connection.h" + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +namespace { + +// BoringSSL allows storing extra data off of some of its data structures, +// including the SSL struct. To allow for multiple callers to store data, each +// caller can use a different index for setting and getting data. These indices +// are globals handed out by calling SSL_get_ex_new_index. +// +// SslIndexSingleton calls SSL_get_ex_new_index on its construction, and then +// provides this index to be used in calls to SSL_get_ex_data/SSL_set_ex_data. +// This is used to store in the SSL struct a pointer to the TlsConnection which +// owns it. +class SslIndexSingleton { + public: + static SslIndexSingleton* GetInstance() { + static SslIndexSingleton* instance = new SslIndexSingleton(); + return instance; + } + + int ssl_ex_data_index_connection() const { + return ssl_ex_data_index_connection_; + } + + private: + SslIndexSingleton() { + CRYPTO_library_init(); + ssl_ex_data_index_connection_ = + SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + QUICHE_CHECK_LE(0, ssl_ex_data_index_connection_); + } + + SslIndexSingleton(const SslIndexSingleton&) = delete; + SslIndexSingleton& operator=(const SslIndexSingleton&) = delete; + + // The index to supply to SSL_get_ex_data/SSL_set_ex_data for getting/setting + // the TlsConnection pointer. + int ssl_ex_data_index_connection_; +}; + +} // namespace + +// static +EncryptionLevel TlsConnection::QuicEncryptionLevel( + enum ssl_encryption_level_t level) { + switch (level) { + case ssl_encryption_initial: + return ENCRYPTION_INITIAL; + case ssl_encryption_early_data: + return ENCRYPTION_ZERO_RTT; + case ssl_encryption_handshake: + return ENCRYPTION_HANDSHAKE; + case ssl_encryption_application: + return ENCRYPTION_FORWARD_SECURE; + default: + QUIC_BUG(quic_bug_10698_1) + << "Invalid ssl_encryption_level_t " << static_cast<int>(level); + return ENCRYPTION_INITIAL; + } +} + +// static +enum ssl_encryption_level_t TlsConnection::BoringEncryptionLevel( + EncryptionLevel level) { + switch (level) { + case ENCRYPTION_INITIAL: + return ssl_encryption_initial; + case ENCRYPTION_HANDSHAKE: + return ssl_encryption_handshake; + case ENCRYPTION_ZERO_RTT: + return ssl_encryption_early_data; + case ENCRYPTION_FORWARD_SECURE: + return ssl_encryption_application; + default: + QUIC_BUG(quic_bug_10698_2) + << "Invalid encryption level " << static_cast<int>(level); + return ssl_encryption_initial; + } +} + +TlsConnection::TlsConnection(SSL_CTX* ssl_ctx, + TlsConnection::Delegate* delegate, + QuicSSLConfig ssl_config) + : delegate_(delegate), + ssl_(SSL_new(ssl_ctx)), + ssl_config_(std::move(ssl_config)) { + SSL_set_ex_data( + ssl(), SslIndexSingleton::GetInstance()->ssl_ex_data_index_connection(), + this); + if (ssl_config_.early_data_enabled.has_value()) { + const int early_data_enabled = *ssl_config_.early_data_enabled ? 1 : 0; + SSL_set_early_data_enabled(ssl(), early_data_enabled); + } + if (ssl_config_.signing_algorithm_prefs.has_value()) { + SSL_set_signing_algorithm_prefs( + ssl(), ssl_config_.signing_algorithm_prefs->data(), + ssl_config_.signing_algorithm_prefs->size()); + } + if (ssl_config_.disable_ticket_support.has_value()) { + if (*ssl_config_.disable_ticket_support) { + SSL_set_options(ssl(), SSL_OP_NO_TICKET); + } + } +} + +void TlsConnection::EnableInfoCallback() { + SSL_set_info_callback( + ssl(), +[](const SSL* ssl, int type, int value) { + ConnectionFromSsl(ssl)->delegate_->InfoCallback(type, value); + }); +} + +void TlsConnection::DisableTicketSupport() { + ssl_config_.disable_ticket_support = true; + SSL_set_options(ssl(), SSL_OP_NO_TICKET); +} + +// static +bssl::UniquePtr<SSL_CTX> TlsConnection::CreateSslCtx() { + CRYPTO_library_init(); + bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_with_buffers_method())); + SSL_CTX_set_min_proto_version(ssl_ctx.get(), TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION); + SSL_CTX_set_quic_method(ssl_ctx.get(), &kSslQuicMethod); + return ssl_ctx; +} + +// static +TlsConnection* TlsConnection::ConnectionFromSsl(const SSL* ssl) { + return reinterpret_cast<TlsConnection*>(SSL_get_ex_data( + ssl, SslIndexSingleton::GetInstance()->ssl_ex_data_index_connection())); +} + +// static +enum ssl_verify_result_t TlsConnection::VerifyCallback(SSL* ssl, + uint8_t* out_alert) { + return ConnectionFromSsl(ssl)->delegate_->VerifyCert(out_alert); +} + +const SSL_QUIC_METHOD TlsConnection::kSslQuicMethod{ + TlsConnection::SetReadSecretCallback, TlsConnection::SetWriteSecretCallback, + TlsConnection::WriteMessageCallback, TlsConnection::FlushFlightCallback, + TlsConnection::SendAlertCallback}; + +// static +int TlsConnection::SetReadSecretCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const SSL_CIPHER* cipher, + const uint8_t* secret, + size_t secret_length) { + // TODO(nharper): replace this vector with a span (which unfortunately doesn't + // yet exist in quic/platform/api). + std::vector<uint8_t> secret_vec(secret, secret + secret_length); + TlsConnection::Delegate* delegate = ConnectionFromSsl(ssl)->delegate_; + if (!delegate->SetReadSecret(QuicEncryptionLevel(level), cipher, + secret_vec)) { + return 0; + } + return 1; +} + +// static +int TlsConnection::SetWriteSecretCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const SSL_CIPHER* cipher, + const uint8_t* secret, + size_t secret_length) { + // TODO(nharper): replace this vector with a span (which unfortunately doesn't + // yet exist in quic/platform/api). + std::vector<uint8_t> secret_vec(secret, secret + secret_length); + TlsConnection::Delegate* delegate = ConnectionFromSsl(ssl)->delegate_; + delegate->SetWriteSecret(QuicEncryptionLevel(level), cipher, secret_vec); + return 1; +} + +// static +int TlsConnection::WriteMessageCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const uint8_t* data, + size_t len) { + ConnectionFromSsl(ssl)->delegate_->WriteMessage( + QuicEncryptionLevel(level), + absl::string_view(reinterpret_cast<const char*>(data), len)); + return 1; +} + +// static +int TlsConnection::FlushFlightCallback(SSL* ssl) { + ConnectionFromSsl(ssl)->delegate_->FlushFlight(); + return 1; +} + +// static +int TlsConnection::SendAlertCallback(SSL* ssl, + enum ssl_encryption_level_t level, + uint8_t desc) { + ConnectionFromSsl(ssl)->delegate_->SendAlert(QuicEncryptionLevel(level), + desc); + return 1; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/tls_connection.h b/quiche/quic/core/crypto/tls_connection.h new file mode 100644 index 0000000..c7f7758 --- /dev/null +++ b/quiche/quic/core/crypto/tls_connection.h
@@ -0,0 +1,162 @@ +// Copyright (c) 2019 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_TLS_CONNECTION_H_ +#define QUICHE_QUIC_CORE_CRYPTO_TLS_CONNECTION_H_ + +#include <vector> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/quic_types.h" + +namespace quic { + +// TlsConnection wraps BoringSSL's SSL object which represents a single TLS +// connection. Callbacks set in BoringSSL which are called with an SSL* argument +// will get dispatched to the TlsConnection object owning that SSL. In turn, the +// TlsConnection will delegate the implementation of that callback to its +// Delegate. +// +// The owner of the TlsConnection is responsible for driving the TLS handshake +// (and other interactions with the SSL*). This class only handles mapping +// callbacks to the correct instance. +class QUIC_EXPORT_PRIVATE TlsConnection { + public: + // A TlsConnection::Delegate implements the methods that are set as callbacks + // of TlsConnection. + class QUIC_EXPORT_PRIVATE Delegate { + public: + virtual ~Delegate() {} + + protected: + // Certificate management functions: + + // Verifies the peer's certificate chain. It may use + // SSL_get0_peer_certificates to get the cert chain. This method returns + // ssl_verify_ok if the cert is valid, ssl_verify_invalid if it is invalid, + // or ssl_verify_retry if verification is happening asynchronously. + virtual enum ssl_verify_result_t VerifyCert(uint8_t* out_alert) = 0; + + // QUIC-TLS interface functions: + + // SetWriteSecret provides the encryption secret used to encrypt messages at + // encryption level |level|. The secret provided here is the one from the + // TLS 1.3 key schedule (RFC 8446 section 7.1), in particular the handshake + // traffic secrets and application traffic secrets. The provided write + // secret must be used with the provided cipher suite |cipher|. + virtual void SetWriteSecret(EncryptionLevel level, + const SSL_CIPHER* cipher, + const std::vector<uint8_t>& write_secret) = 0; + + // SetReadSecret is similar to SetWriteSecret, except that it is used for + // decrypting messages. SetReadSecret at a particular level is always called + // after SetWriteSecret for that level, except for ENCRYPTION_ZERO_RTT, + // where the EncryptionLevel for SetWriteSecret is + // ENCRYPTION_FORWARD_SECURE. + virtual bool SetReadSecret(EncryptionLevel level, + const SSL_CIPHER* cipher, + const std::vector<uint8_t>& read_secret) = 0; + + // WriteMessage is called when there is |data| from the TLS stack ready for + // the QUIC stack to write in a crypto frame. The data must be transmitted + // at encryption level |level|. + virtual void WriteMessage(EncryptionLevel level, + absl::string_view data) = 0; + + // FlushFlight is called to signal that the current flight of messages have + // all been written (via calls to WriteMessage) and can be flushed to the + // underlying transport. + virtual void FlushFlight() = 0; + + // SendAlert causes this TlsConnection to close the QUIC connection with an + // error code corersponding to the TLS alert description |desc| sent at + // level |level|. + virtual void SendAlert(EncryptionLevel level, uint8_t desc) = 0; + + // Informational callback from BoringSSL. This callback is disabled by + // default, but can be enabled by TlsConnection::EnableInfoCallback. + // + // See |SSL_CTX_set_info_callback| for the meaning of |type| and |value|. + virtual void InfoCallback(int type, int value) = 0; + + friend class TlsConnection; + }; + + TlsConnection(const TlsConnection&) = delete; + TlsConnection& operator=(const TlsConnection&) = delete; + + // Configure the SSL such that delegate_->InfoCallback will be called. + void EnableInfoCallback(); + + // Configure the SSL to disable session ticket support. Note that, this + // function simply sets the |SSL_OP_NO_TICKET| option on the SSL object, it + // does not check whether it is too late to do so. + void DisableTicketSupport(); + + // Functions to convert between BoringSSL's enum ssl_encryption_level_t and + // QUIC's EncryptionLevel. + static EncryptionLevel QuicEncryptionLevel(enum ssl_encryption_level_t level); + static enum ssl_encryption_level_t BoringEncryptionLevel( + EncryptionLevel level); + + SSL* ssl() const { return ssl_.get(); } + + const QuicSSLConfig& ssl_config() const { return ssl_config_; } + + protected: + // TlsConnection does not take ownership of |ssl_ctx| or |delegate|; they must + // outlive the TlsConnection object. + TlsConnection(SSL_CTX* ssl_ctx, Delegate* delegate, QuicSSLConfig ssl_config); + + // Creates an SSL_CTX and configures it with the options that are appropriate + // for both client and server. The caller is responsible for ownership of the + // newly created struct. + static bssl::UniquePtr<SSL_CTX> CreateSslCtx(); + + // From a given SSL* |ssl|, returns a pointer to the TlsConnection that it + // belongs to. This helper method allows the callbacks set in BoringSSL to be + // dispatched to the correct TlsConnection from the SSL* passed into the + // callback. + static TlsConnection* ConnectionFromSsl(const SSL* ssl); + + // Registered as the callback for SSL(_CTX)_set_custom_verify. The + // implementation is delegated to Delegate::VerifyCert. + static enum ssl_verify_result_t VerifyCallback(SSL* ssl, uint8_t* out_alert); + + QuicSSLConfig& mutable_ssl_config() { return ssl_config_; } + + private: + // TlsConnection implements SSL_QUIC_METHOD, which provides the interface + // between BoringSSL's TLS stack and a QUIC implementation. + static const SSL_QUIC_METHOD kSslQuicMethod; + + // The following static functions make up the members of kSslQuicMethod: + static int SetReadSecretCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const SSL_CIPHER* cipher, + const uint8_t* secret, + size_t secret_len); + static int SetWriteSecretCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const SSL_CIPHER* cipher, + const uint8_t* secret, + size_t secret_len); + static int WriteMessageCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const uint8_t* data, + size_t len); + static int FlushFlightCallback(SSL* ssl); + static int SendAlertCallback(SSL* ssl, + enum ssl_encryption_level_t level, + uint8_t desc); + + Delegate* delegate_; + bssl::UniquePtr<SSL> ssl_; + QuicSSLConfig ssl_config_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_TLS_CONNECTION_H_
diff --git a/quiche/quic/core/crypto/tls_server_connection.cc b/quiche/quic/core/crypto/tls_server_connection.cc new file mode 100644 index 0000000..1e63d3f --- /dev/null +++ b/quiche/quic/core/crypto/tls_server_connection.cc
@@ -0,0 +1,186 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/tls_server_connection.h" + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" + +namespace quic { + +TlsServerConnection::TlsServerConnection(SSL_CTX* ssl_ctx, Delegate* delegate, + QuicSSLConfig ssl_config) + : TlsConnection(ssl_ctx, delegate->ConnectionDelegate(), + std::move(ssl_config)), + delegate_(delegate) { + // By default, cert verify callback is not installed on ssl(), so only need to + // UpdateCertVerifyCallback() if client_cert_mode is not kNone. + if (TlsConnection::ssl_config().client_cert_mode != ClientCertMode::kNone) { + UpdateCertVerifyCallback(); + } +} + +// static +bssl::UniquePtr<SSL_CTX> TlsServerConnection::CreateSslCtx( + ProofSource* proof_source) { + bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx(); + + // Server does not request/verify client certs by default. Individual server + // connections may call SSL_set_custom_verify on their SSL object to request + // client certs. + + SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), + &TlsExtServernameCallback); + SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), &SelectAlpnCallback, nullptr); + // We don't actually need the TicketCrypter here, but we need to know + // whether it's set. + if (proof_source->GetTicketCrypter()) { + QUIC_CODE_COUNT(quic_session_tickets_enabled); + SSL_CTX_set_ticket_aead_method(ssl_ctx.get(), + &TlsServerConnection::kSessionTicketMethod); + } else { + QUIC_CODE_COUNT(quic_session_tickets_disabled); + } + + SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1); + + SSL_CTX_set_select_certificate_cb( + ssl_ctx.get(), &TlsServerConnection::EarlySelectCertCallback); + SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE); + return ssl_ctx; +} + +void TlsServerConnection::SetCertChain( + const std::vector<CRYPTO_BUFFER*>& cert_chain) { + SSL_set_chain_and_key(ssl(), cert_chain.data(), cert_chain.size(), nullptr, + &TlsServerConnection::kPrivateKeyMethod); +} + +void TlsServerConnection::SetClientCertMode(ClientCertMode client_cert_mode) { + if (ssl_config().client_cert_mode == client_cert_mode) { + return; + } + + mutable_ssl_config().client_cert_mode = client_cert_mode; + UpdateCertVerifyCallback(); +} + +void TlsServerConnection::UpdateCertVerifyCallback() { + const ClientCertMode client_cert_mode = ssl_config().client_cert_mode; + if (client_cert_mode == ClientCertMode::kNone) { + SSL_set_custom_verify(ssl(), SSL_VERIFY_NONE, nullptr); + return; + } + + int mode = SSL_VERIFY_PEER; + if (client_cert_mode == ClientCertMode::kRequire) { + mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } else { + QUICHE_DCHECK_EQ(client_cert_mode, ClientCertMode::kRequest); + } + SSL_set_custom_verify(ssl(), mode, &VerifyCallback); +} + +const SSL_PRIVATE_KEY_METHOD TlsServerConnection::kPrivateKeyMethod{ + &TlsServerConnection::PrivateKeySign, + nullptr, // decrypt + &TlsServerConnection::PrivateKeyComplete, +}; + +// static +TlsServerConnection* TlsServerConnection::ConnectionFromSsl(SSL* ssl) { + return static_cast<TlsServerConnection*>( + TlsConnection::ConnectionFromSsl(ssl)); +} + +// static +ssl_select_cert_result_t TlsServerConnection::EarlySelectCertCallback( + const SSL_CLIENT_HELLO* client_hello) { + return ConnectionFromSsl(client_hello->ssl) + ->delegate_->EarlySelectCertCallback(client_hello); +} + +// static +int TlsServerConnection::TlsExtServernameCallback(SSL* ssl, + int* out_alert, + void* /*arg*/) { + return ConnectionFromSsl(ssl)->delegate_->TlsExtServernameCallback(out_alert); +} + +// static +int TlsServerConnection::SelectAlpnCallback(SSL* ssl, + const uint8_t** out, + uint8_t* out_len, + const uint8_t* in, + unsigned in_len, + void* /*arg*/) { + return ConnectionFromSsl(ssl)->delegate_->SelectAlpn(out, out_len, in, + in_len); +} + +// static +ssl_private_key_result_t TlsServerConnection::PrivateKeySign(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out, + uint16_t sig_alg, + const uint8_t* in, + size_t in_len) { + return ConnectionFromSsl(ssl)->delegate_->PrivateKeySign( + out, out_len, max_out, sig_alg, + absl::string_view(reinterpret_cast<const char*>(in), in_len)); +} + +// static +ssl_private_key_result_t TlsServerConnection::PrivateKeyComplete( + SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out) { + return ConnectionFromSsl(ssl)->delegate_->PrivateKeyComplete(out, out_len, + max_out); +} + +// static +const SSL_TICKET_AEAD_METHOD TlsServerConnection::kSessionTicketMethod{ + TlsServerConnection::SessionTicketMaxOverhead, + TlsServerConnection::SessionTicketSeal, + TlsServerConnection::SessionTicketOpen, +}; + +// static +size_t TlsServerConnection::SessionTicketMaxOverhead(SSL* ssl) { + return ConnectionFromSsl(ssl)->delegate_->SessionTicketMaxOverhead(); +} + +// static +int TlsServerConnection::SessionTicketSeal(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len) { + return ConnectionFromSsl(ssl)->delegate_->SessionTicketSeal( + out, out_len, max_out_len, + absl::string_view(reinterpret_cast<const char*>(in), in_len)); +} + +// static +enum ssl_ticket_aead_result_t TlsServerConnection::SessionTicketOpen( + SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len) { + return ConnectionFromSsl(ssl)->delegate_->SessionTicketOpen( + out, out_len, max_out_len, + absl::string_view(reinterpret_cast<const char*>(in), in_len)); +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/tls_server_connection.h b/quiche/quic/core/crypto/tls_server_connection.h new file mode 100644 index 0000000..85dddd0 --- /dev/null +++ b/quiche/quic/core/crypto/tls_server_connection.h
@@ -0,0 +1,200 @@ +// Copyright (c) 2019 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_TLS_SERVER_CONNECTION_H_ +#define QUICHE_QUIC_CORE_CRYPTO_TLS_SERVER_CONNECTION_H_ + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/crypto/tls_connection.h" + +namespace quic { + +// TlsServerConnection receives calls for client-specific BoringSSL callbacks +// and calls its Delegate for the implementation of those callbacks. +class QUIC_EXPORT_PRIVATE TlsServerConnection : public TlsConnection { + public: + // A TlsServerConnection::Delegate implement the server-specific methods that + // are set as callbacks for an SSL object. + class QUIC_EXPORT_PRIVATE Delegate { + public: + virtual ~Delegate() {} + + protected: + // Called from BoringSSL right after SNI is extracted, which is very early + // in the handshake process. + virtual ssl_select_cert_result_t EarlySelectCertCallback( + const SSL_CLIENT_HELLO* client_hello) = 0; + + // Called after the ClientHello extensions have been successfully parsed. + // Returns an SSL_TLSEXT_ERR_* value (see + // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_servername_callback). + // + // On success, return SSL_TLSEXT_ERR_OK causes the server_name extension to + // be acknowledged in the ServerHello, or return SSL_TLSEXT_ERR_NOACK which + // causes it to be not acknowledged. + // + // If the function returns SSL_TLSEXT_ERR_ALERT_FATAL, then it puts in + // |*out_alert| the TLS alert value that the server will send. + // + virtual int TlsExtServernameCallback(int* out_alert) = 0; + + // Selects which ALPN to use based on the list sent by the client. + virtual int SelectAlpn(const uint8_t** out, + uint8_t* out_len, + const uint8_t* in, + unsigned in_len) = 0; + + // Signs |in| using the signature algorithm specified by |sig_alg| (an + // SSL_SIGN_* value). If the signing operation cannot be completed + // synchronously, ssl_private_key_retry is returned. If there is an error + // signing, or if the signature is longer than |max_out|, then + // ssl_private_key_failure is returned. Otherwise, ssl_private_key_success + // is returned with the signature put in |*out| and the length in + // |*out_len|. + virtual ssl_private_key_result_t PrivateKeySign(uint8_t* out, + size_t* out_len, + size_t max_out, + uint16_t sig_alg, + absl::string_view in) = 0; + + // When PrivateKeySign returns ssl_private_key_retry, PrivateKeyComplete + // will be called after the async sign operation has completed. + // PrivateKeyComplete puts the resulting signature in |*out| and length in + // |*out_len|. If the length is greater than |max_out| or if there was an + // error in signing, then ssl_private_key_failure is returned. Otherwise, + // ssl_private_key_success is returned. + virtual ssl_private_key_result_t PrivateKeyComplete(uint8_t* out, + size_t* out_len, + size_t max_out) = 0; + + // The following functions are used to implement an SSL_TICKET_AEAD_METHOD. + // See + // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_ticket_aead_result_t + // for details on the BoringSSL API. + + // SessionTicketMaxOverhead returns the maximum number of bytes of overhead + // that SessionTicketSeal may add when encrypting a session ticket. + virtual size_t SessionTicketMaxOverhead() = 0; + + // SessionTicketSeal encrypts the session ticket in |in|, putting the + // resulting encrypted ticket in |out|, writing the length of the bytes + // written to |*out_len|, which is no larger than |max_out_len|. It returns + // 1 on success and 0 on error. + virtual int SessionTicketSeal(uint8_t* out, + size_t* out_len, + size_t max_out_len, + absl::string_view in) = 0; + + // SessionTicketOpen is called when BoringSSL has an encrypted session + // ticket |in| and wants the ticket decrypted. This decryption operation can + // happen synchronously or asynchronously. + // + // If the decrypted ticket is not available at the time of the function + // call, this function returns ssl_ticket_aead_retry. If this function + // returns ssl_ticket_aead_retry, then SSL_do_handshake will return + // SSL_ERROR_PENDING_TICKET. Once the pending ticket decryption has + // completed, SSL_do_handshake needs to be called again. + // + // When this function is called and the decrypted ticket is available + // (either the ticket was decrypted synchronously, or an asynchronous + // operation has completed and SSL_do_handshake has been called again), the + // decrypted ticket is put in |out|, and the length of that output is + // written to |*out_len|, not to exceed |max_out_len|, and + // ssl_ticket_aead_success is returned. If the ticket cannot be decrypted + // and should be ignored, this function returns + // ssl_ticket_aead_ignore_ticket and a full handshake will be performed + // instead. If a fatal error occurs, ssl_ticket_aead_error can be returned + // which will terminate the handshake. + virtual enum ssl_ticket_aead_result_t SessionTicketOpen( + uint8_t* out, + size_t* out_len, + size_t max_out_len, + absl::string_view in) = 0; + + // Provides the delegate for callbacks that are shared between client and + // server. + virtual TlsConnection::Delegate* ConnectionDelegate() = 0; + + friend class TlsServerConnection; + }; + + TlsServerConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config); + + // Creates and configures an SSL_CTX that is appropriate for servers to use. + static bssl::UniquePtr<SSL_CTX> CreateSslCtx(ProofSource* proof_source); + + void SetCertChain(const std::vector<CRYPTO_BUFFER*>& cert_chain); + + // Set the client cert mode to be used on this connection. This should be + // called right after cert selection at the latest, otherwise it is too late + // to has an effect. + void SetClientCertMode(ClientCertMode client_cert_mode); + + private: + // Specialization of TlsConnection::ConnectionFromSsl. + static TlsServerConnection* ConnectionFromSsl(SSL* ssl); + + static ssl_select_cert_result_t EarlySelectCertCallback( + const SSL_CLIENT_HELLO* client_hello); + + // These functions are registered as callbacks in BoringSSL and delegate their + // implementation to the matching methods in Delegate above. + static int TlsExtServernameCallback(SSL* ssl, int* out_alert, void* arg); + static int SelectAlpnCallback(SSL* ssl, + const uint8_t** out, + uint8_t* out_len, + const uint8_t* in, + unsigned in_len, + void* arg); + + // |kPrivateKeyMethod| is a vtable pointing to PrivateKeySign and + // PrivateKeyComplete used by the TLS stack to compute the signature for the + // CertificateVerify message (using the server's private key). + static const SSL_PRIVATE_KEY_METHOD kPrivateKeyMethod; + + // The following functions make up the contents of |kPrivateKeyMethod|. + static ssl_private_key_result_t PrivateKeySign(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out, + uint16_t sig_alg, + const uint8_t* in, + size_t in_len); + static ssl_private_key_result_t PrivateKeyComplete(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out); + + // Implementation of SSL_TICKET_AEAD_METHOD which delegates to corresponding + // methods in TlsServerConnection::Delegate (a.k.a. TlsServerHandshaker). + static const SSL_TICKET_AEAD_METHOD kSessionTicketMethod; + + // The following functions make up the contents of |kSessionTicketMethod|. + static size_t SessionTicketMaxOverhead(SSL* ssl); + static int SessionTicketSeal(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len); + static enum ssl_ticket_aead_result_t SessionTicketOpen(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len); + + // Install custom verify callback on ssl() if |ssl_config().client_cert_mode| + // is not ClientCertMode::kNone. Uninstall otherwise. + void UpdateCertVerifyCallback(); + + Delegate* delegate_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_TLS_SERVER_CONNECTION_H_
diff --git a/quiche/quic/core/crypto/transport_parameters.cc b/quiche/quic/core/crypto/transport_parameters.cc new file mode 100644 index 0000000..04db561 --- /dev/null +++ b/quiche/quic/core/crypto/transport_parameters.cc
@@ -0,0 +1,1590 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/transport_parameters.h" + +#include <cstdint> +#include <cstring> +#include <forward_list> +#include <memory> +#include <utility> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/sha.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_ip_address.h" + +namespace quic { + +// Values of the TransportParameterId enum as defined in the +// "Transport Parameter Encoding" section of draft-ietf-quic-transport. +// When parameters are encoded, one of these enum values is used to indicate +// which parameter is encoded. The supported draft version is noted in +// transport_parameters.h. +enum TransportParameters::TransportParameterId : uint64_t { + kOriginalDestinationConnectionId = 0, + kMaxIdleTimeout = 1, + kStatelessResetToken = 2, + kMaxPacketSize = 3, + kInitialMaxData = 4, + kInitialMaxStreamDataBidiLocal = 5, + kInitialMaxStreamDataBidiRemote = 6, + kInitialMaxStreamDataUni = 7, + kInitialMaxStreamsBidi = 8, + kInitialMaxStreamsUni = 9, + kAckDelayExponent = 0xa, + kMaxAckDelay = 0xb, + kDisableActiveMigration = 0xc, + kPreferredAddress = 0xd, + kActiveConnectionIdLimit = 0xe, + kInitialSourceConnectionId = 0xf, + kRetrySourceConnectionId = 0x10, + + kMaxDatagramFrameSize = 0x20, + + kInitialRoundTripTime = 0x3127, + kGoogleConnectionOptions = 0x3128, + // 0x3129 was used to convey the user agent string. + // 0x312A was used only in T050 to indicate support for HANDSHAKE_DONE. + // 0x312B was used to indicate that QUIC+TLS key updates were not supported. + // 0x4751 was used for non-standard Google-specific parameters encoded as a + // Google QUIC_CRYPTO CHLO, it has been replaced by individual parameters. + kGoogleQuicVersion = + 0x4752, // Used to transmit version and supported_versions. + + kMinAckDelay = 0xDE1A, // draft-iyengar-quic-delayed-ack. + kVersionInformation = 0xFF73DB, // draft-ietf-quic-version-negotiation. +}; + +namespace { + +// The following constants define minimum and maximum allowed values for some of +// the parameters. These come from the "Transport Parameter Definitions" +// section of draft-ietf-quic-transport. +constexpr uint64_t kMinMaxPacketSizeTransportParam = 1200; +constexpr uint64_t kMaxAckDelayExponentTransportParam = 20; +constexpr uint64_t kDefaultAckDelayExponentTransportParam = 3; +constexpr uint64_t kMaxMaxAckDelayTransportParam = 16383; +constexpr uint64_t kDefaultMaxAckDelayTransportParam = 25; +constexpr uint64_t kMinActiveConnectionIdLimitTransportParam = 2; +constexpr uint64_t kDefaultActiveConnectionIdLimitTransportParam = 2; + +std::string TransportParameterIdToString( + TransportParameters::TransportParameterId param_id) { + switch (param_id) { + case TransportParameters::kOriginalDestinationConnectionId: + return "original_destination_connection_id"; + case TransportParameters::kMaxIdleTimeout: + return "max_idle_timeout"; + case TransportParameters::kStatelessResetToken: + return "stateless_reset_token"; + case TransportParameters::kMaxPacketSize: + return "max_udp_payload_size"; + case TransportParameters::kInitialMaxData: + return "initial_max_data"; + case TransportParameters::kInitialMaxStreamDataBidiLocal: + return "initial_max_stream_data_bidi_local"; + case TransportParameters::kInitialMaxStreamDataBidiRemote: + return "initial_max_stream_data_bidi_remote"; + case TransportParameters::kInitialMaxStreamDataUni: + return "initial_max_stream_data_uni"; + case TransportParameters::kInitialMaxStreamsBidi: + return "initial_max_streams_bidi"; + case TransportParameters::kInitialMaxStreamsUni: + return "initial_max_streams_uni"; + case TransportParameters::kAckDelayExponent: + return "ack_delay_exponent"; + case TransportParameters::kMaxAckDelay: + return "max_ack_delay"; + case TransportParameters::kDisableActiveMigration: + return "disable_active_migration"; + case TransportParameters::kPreferredAddress: + return "preferred_address"; + case TransportParameters::kActiveConnectionIdLimit: + return "active_connection_id_limit"; + case TransportParameters::kInitialSourceConnectionId: + return "initial_source_connection_id"; + case TransportParameters::kRetrySourceConnectionId: + return "retry_source_connection_id"; + case TransportParameters::kMaxDatagramFrameSize: + return "max_datagram_frame_size"; + case TransportParameters::kInitialRoundTripTime: + return "initial_round_trip_time"; + case TransportParameters::kGoogleConnectionOptions: + return "google_connection_options"; + case TransportParameters::kGoogleQuicVersion: + return "google-version"; + case TransportParameters::kMinAckDelay: + return "min_ack_delay_us"; + case TransportParameters::kVersionInformation: + return "version_information"; + } + return absl::StrCat("Unknown(", param_id, ")"); +} + +bool TransportParameterIdIsKnown( + TransportParameters::TransportParameterId param_id) { + switch (param_id) { + case TransportParameters::kOriginalDestinationConnectionId: + case TransportParameters::kMaxIdleTimeout: + case TransportParameters::kStatelessResetToken: + case TransportParameters::kMaxPacketSize: + case TransportParameters::kInitialMaxData: + case TransportParameters::kInitialMaxStreamDataBidiLocal: + case TransportParameters::kInitialMaxStreamDataBidiRemote: + case TransportParameters::kInitialMaxStreamDataUni: + case TransportParameters::kInitialMaxStreamsBidi: + case TransportParameters::kInitialMaxStreamsUni: + case TransportParameters::kAckDelayExponent: + case TransportParameters::kMaxAckDelay: + case TransportParameters::kDisableActiveMigration: + case TransportParameters::kPreferredAddress: + case TransportParameters::kActiveConnectionIdLimit: + case TransportParameters::kInitialSourceConnectionId: + case TransportParameters::kRetrySourceConnectionId: + case TransportParameters::kMaxDatagramFrameSize: + case TransportParameters::kInitialRoundTripTime: + case TransportParameters::kGoogleConnectionOptions: + case TransportParameters::kGoogleQuicVersion: + case TransportParameters::kMinAckDelay: + case TransportParameters::kVersionInformation: + return true; + } + return false; +} + +} // namespace + +TransportParameters::IntegerParameter::IntegerParameter( + TransportParameters::TransportParameterId param_id, + uint64_t default_value, + uint64_t min_value, + uint64_t max_value) + : param_id_(param_id), + value_(default_value), + default_value_(default_value), + min_value_(min_value), + max_value_(max_value), + has_been_read_(false) { + QUICHE_DCHECK_LE(min_value, default_value); + QUICHE_DCHECK_LE(default_value, max_value); + QUICHE_DCHECK_LE(max_value, kVarInt62MaxValue); +} + +TransportParameters::IntegerParameter::IntegerParameter( + TransportParameters::TransportParameterId param_id) + : TransportParameters::IntegerParameter::IntegerParameter( + param_id, + 0, + 0, + kVarInt62MaxValue) {} + +void TransportParameters::IntegerParameter::set_value(uint64_t value) { + value_ = value; +} + +uint64_t TransportParameters::IntegerParameter::value() const { + return value_; +} + +bool TransportParameters::IntegerParameter::IsValid() const { + return min_value_ <= value_ && value_ <= max_value_; +} + +bool TransportParameters::IntegerParameter::Write( + QuicDataWriter* writer) const { + QUICHE_DCHECK(IsValid()); + if (value_ == default_value_) { + // Do not write if the value is default. + return true; + } + if (!writer->WriteVarInt62(param_id_)) { + QUIC_BUG(quic_bug_10743_1) << "Failed to write param_id for " << *this; + return false; + } + const QuicVariableLengthIntegerLength value_length = + QuicDataWriter::GetVarInt62Len(value_); + if (!writer->WriteVarInt62(value_length)) { + QUIC_BUG(quic_bug_10743_2) << "Failed to write value_length for " << *this; + return false; + } + if (!writer->WriteVarInt62(value_, value_length)) { + QUIC_BUG(quic_bug_10743_3) << "Failed to write value for " << *this; + return false; + } + return true; +} + +bool TransportParameters::IntegerParameter::Read(QuicDataReader* reader, + std::string* error_details) { + if (has_been_read_) { + *error_details = + "Received a second " + TransportParameterIdToString(param_id_); + return false; + } + has_been_read_ = true; + + if (!reader->ReadVarInt62(&value_)) { + *error_details = + "Failed to parse value for " + TransportParameterIdToString(param_id_); + return false; + } + if (!reader->IsDoneReading()) { + *error_details = + absl::StrCat("Received unexpected ", reader->BytesRemaining(), + " bytes after parsing ", this->ToString(false)); + return false; + } + return true; +} + +std::string TransportParameters::IntegerParameter::ToString( + bool for_use_in_list) const { + if (for_use_in_list && value_ == default_value_) { + return ""; + } + std::string rv = for_use_in_list ? " " : ""; + absl::StrAppend(&rv, TransportParameterIdToString(param_id_), " ", value_); + if (!IsValid()) { + rv += " (Invalid)"; + } + return rv; +} + +std::ostream& operator<<(std::ostream& os, + const TransportParameters::IntegerParameter& param) { + os << param.ToString(/*for_use_in_list=*/false); + return os; +} + +TransportParameters::PreferredAddress::PreferredAddress() + : ipv4_socket_address(QuicIpAddress::Any4(), 0), + ipv6_socket_address(QuicIpAddress::Any6(), 0), + connection_id(EmptyQuicConnectionId()), + stateless_reset_token(kStatelessResetTokenLength, 0) {} + +TransportParameters::PreferredAddress::~PreferredAddress() {} + +bool TransportParameters::PreferredAddress::operator==( + const PreferredAddress& rhs) const { + return ipv4_socket_address == rhs.ipv4_socket_address && + ipv6_socket_address == rhs.ipv6_socket_address && + connection_id == rhs.connection_id && + stateless_reset_token == rhs.stateless_reset_token; +} + +bool TransportParameters::PreferredAddress::operator!=( + const PreferredAddress& rhs) const { + return !(*this == rhs); +} + +std::ostream& operator<<( + std::ostream& os, + const TransportParameters::PreferredAddress& preferred_address) { + os << preferred_address.ToString(); + return os; +} + +std::string TransportParameters::PreferredAddress::ToString() const { + return "[" + ipv4_socket_address.ToString() + " " + + ipv6_socket_address.ToString() + " connection_id " + + connection_id.ToString() + " stateless_reset_token " + + absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char*>(stateless_reset_token.data()), + stateless_reset_token.size())) + + "]"; +} + +TransportParameters::LegacyVersionInformation::LegacyVersionInformation() + : version(0) {} + +bool TransportParameters::LegacyVersionInformation::operator==( + const LegacyVersionInformation& rhs) const { + return version == rhs.version && supported_versions == rhs.supported_versions; +} + +bool TransportParameters::LegacyVersionInformation::operator!=( + const LegacyVersionInformation& rhs) const { + return !(*this == rhs); +} + +std::string TransportParameters::LegacyVersionInformation::ToString() const { + std::string rv = + absl::StrCat("legacy[version ", QuicVersionLabelToString(version)); + if (!supported_versions.empty()) { + absl::StrAppend(&rv, + " supported_versions " + + QuicVersionLabelVectorToString(supported_versions)); + } + absl::StrAppend(&rv, "]"); + return rv; +} + +std::ostream& operator<<(std::ostream& os, + const TransportParameters::LegacyVersionInformation& + legacy_version_information) { + os << legacy_version_information.ToString(); + return os; +} + +TransportParameters::VersionInformation::VersionInformation() + : chosen_version(0) {} + +bool TransportParameters::VersionInformation::operator==( + const VersionInformation& rhs) const { + return chosen_version == rhs.chosen_version && + other_versions == rhs.other_versions; +} + +bool TransportParameters::VersionInformation::operator!=( + const VersionInformation& rhs) const { + return !(*this == rhs); +} + +std::string TransportParameters::VersionInformation::ToString() const { + std::string rv = absl::StrCat("[chosen_version ", + QuicVersionLabelToString(chosen_version)); + if (!other_versions.empty()) { + absl::StrAppend(&rv, " other_versions " + + QuicVersionLabelVectorToString(other_versions)); + } + absl::StrAppend(&rv, "]"); + return rv; +} + +std::ostream& operator<<( + std::ostream& os, + const TransportParameters::VersionInformation& version_information) { + os << version_information.ToString(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const TransportParameters& params) { + os << params.ToString(); + return os; +} + +std::string TransportParameters::ToString() const { + std::string rv = "["; + if (perspective == Perspective::IS_SERVER) { + rv += "Server"; + } else { + rv += "Client"; + } + if (legacy_version_information.has_value()) { + rv += " " + legacy_version_information.value().ToString(); + } + if (version_information.has_value()) { + rv += " " + version_information.value().ToString(); + } + if (original_destination_connection_id.has_value()) { + rv += " " + TransportParameterIdToString(kOriginalDestinationConnectionId) + + " " + original_destination_connection_id.value().ToString(); + } + rv += max_idle_timeout_ms.ToString(/*for_use_in_list=*/true); + if (!stateless_reset_token.empty()) { + rv += " " + TransportParameterIdToString(kStatelessResetToken) + " " + + absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char*>(stateless_reset_token.data()), + stateless_reset_token.size())); + } + rv += max_udp_payload_size.ToString(/*for_use_in_list=*/true); + rv += initial_max_data.ToString(/*for_use_in_list=*/true); + rv += initial_max_stream_data_bidi_local.ToString(/*for_use_in_list=*/true); + rv += initial_max_stream_data_bidi_remote.ToString(/*for_use_in_list=*/true); + rv += initial_max_stream_data_uni.ToString(/*for_use_in_list=*/true); + rv += initial_max_streams_bidi.ToString(/*for_use_in_list=*/true); + rv += initial_max_streams_uni.ToString(/*for_use_in_list=*/true); + rv += ack_delay_exponent.ToString(/*for_use_in_list=*/true); + rv += max_ack_delay.ToString(/*for_use_in_list=*/true); + rv += min_ack_delay_us.ToString(/*for_use_in_list=*/true); + if (disable_active_migration) { + rv += " " + TransportParameterIdToString(kDisableActiveMigration); + } + if (preferred_address) { + rv += " " + TransportParameterIdToString(kPreferredAddress) + " " + + preferred_address->ToString(); + } + rv += active_connection_id_limit.ToString(/*for_use_in_list=*/true); + if (initial_source_connection_id.has_value()) { + rv += " " + TransportParameterIdToString(kInitialSourceConnectionId) + " " + + initial_source_connection_id.value().ToString(); + } + if (retry_source_connection_id.has_value()) { + rv += " " + TransportParameterIdToString(kRetrySourceConnectionId) + " " + + retry_source_connection_id.value().ToString(); + } + rv += max_datagram_frame_size.ToString(/*for_use_in_list=*/true); + rv += initial_round_trip_time_us.ToString(/*for_use_in_list=*/true); + if (google_connection_options.has_value()) { + rv += " " + TransportParameterIdToString(kGoogleConnectionOptions) + " "; + bool first = true; + for (const QuicTag& connection_option : google_connection_options.value()) { + if (first) { + first = false; + } else { + rv += ","; + } + rv += QuicTagToString(connection_option); + } + } + for (const auto& kv : custom_parameters) { + absl::StrAppend(&rv, " 0x", absl::Hex(static_cast<uint32_t>(kv.first)), + "="); + static constexpr size_t kMaxPrintableLength = 32; + if (kv.second.length() <= kMaxPrintableLength) { + rv += absl::BytesToHexString(kv.second); + } else { + absl::string_view truncated(kv.second.data(), kMaxPrintableLength); + rv += absl::StrCat(absl::BytesToHexString(truncated), "...(length ", + kv.second.length(), ")"); + } + } + rv += "]"; + return rv; +} + +TransportParameters::TransportParameters() + : max_idle_timeout_ms(kMaxIdleTimeout), + max_udp_payload_size(kMaxPacketSize, kDefaultMaxPacketSizeTransportParam, + kMinMaxPacketSizeTransportParam, kVarInt62MaxValue), + initial_max_data(kInitialMaxData), + initial_max_stream_data_bidi_local(kInitialMaxStreamDataBidiLocal), + initial_max_stream_data_bidi_remote(kInitialMaxStreamDataBidiRemote), + initial_max_stream_data_uni(kInitialMaxStreamDataUni), + initial_max_streams_bidi(kInitialMaxStreamsBidi), + initial_max_streams_uni(kInitialMaxStreamsUni), + ack_delay_exponent(kAckDelayExponent, + kDefaultAckDelayExponentTransportParam, 0, + kMaxAckDelayExponentTransportParam), + max_ack_delay(kMaxAckDelay, kDefaultMaxAckDelayTransportParam, 0, + kMaxMaxAckDelayTransportParam), + min_ack_delay_us(kMinAckDelay, 0, 0, + kMaxMaxAckDelayTransportParam * kNumMicrosPerMilli), + disable_active_migration(false), + active_connection_id_limit(kActiveConnectionIdLimit, + kDefaultActiveConnectionIdLimitTransportParam, + kMinActiveConnectionIdLimitTransportParam, + kVarInt62MaxValue), + max_datagram_frame_size(kMaxDatagramFrameSize), + initial_round_trip_time_us(kInitialRoundTripTime) +// Important note: any new transport parameters must be added +// to TransportParameters::AreValid, SerializeTransportParameters and +// ParseTransportParameters, TransportParameters's custom copy constructor, the +// operator==, and TransportParametersTest.Comparator. +{} + +TransportParameters::TransportParameters(const TransportParameters& other) + : perspective(other.perspective), + legacy_version_information(other.legacy_version_information), + version_information(other.version_information), + original_destination_connection_id( + other.original_destination_connection_id), + max_idle_timeout_ms(other.max_idle_timeout_ms), + stateless_reset_token(other.stateless_reset_token), + max_udp_payload_size(other.max_udp_payload_size), + initial_max_data(other.initial_max_data), + initial_max_stream_data_bidi_local( + other.initial_max_stream_data_bidi_local), + initial_max_stream_data_bidi_remote( + other.initial_max_stream_data_bidi_remote), + initial_max_stream_data_uni(other.initial_max_stream_data_uni), + initial_max_streams_bidi(other.initial_max_streams_bidi), + initial_max_streams_uni(other.initial_max_streams_uni), + ack_delay_exponent(other.ack_delay_exponent), + max_ack_delay(other.max_ack_delay), + min_ack_delay_us(other.min_ack_delay_us), + disable_active_migration(other.disable_active_migration), + active_connection_id_limit(other.active_connection_id_limit), + initial_source_connection_id(other.initial_source_connection_id), + retry_source_connection_id(other.retry_source_connection_id), + max_datagram_frame_size(other.max_datagram_frame_size), + initial_round_trip_time_us(other.initial_round_trip_time_us), + google_connection_options(other.google_connection_options), + custom_parameters(other.custom_parameters) { + if (other.preferred_address) { + preferred_address = std::make_unique<TransportParameters::PreferredAddress>( + *other.preferred_address); + } +} + +bool TransportParameters::operator==(const TransportParameters& rhs) const { + if (!(perspective == rhs.perspective && + legacy_version_information == rhs.legacy_version_information && + version_information == rhs.version_information && + original_destination_connection_id == + rhs.original_destination_connection_id && + max_idle_timeout_ms.value() == rhs.max_idle_timeout_ms.value() && + stateless_reset_token == rhs.stateless_reset_token && + max_udp_payload_size.value() == rhs.max_udp_payload_size.value() && + initial_max_data.value() == rhs.initial_max_data.value() && + initial_max_stream_data_bidi_local.value() == + rhs.initial_max_stream_data_bidi_local.value() && + initial_max_stream_data_bidi_remote.value() == + rhs.initial_max_stream_data_bidi_remote.value() && + initial_max_stream_data_uni.value() == + rhs.initial_max_stream_data_uni.value() && + initial_max_streams_bidi.value() == + rhs.initial_max_streams_bidi.value() && + initial_max_streams_uni.value() == + rhs.initial_max_streams_uni.value() && + ack_delay_exponent.value() == rhs.ack_delay_exponent.value() && + max_ack_delay.value() == rhs.max_ack_delay.value() && + min_ack_delay_us.value() == rhs.min_ack_delay_us.value() && + disable_active_migration == rhs.disable_active_migration && + active_connection_id_limit.value() == + rhs.active_connection_id_limit.value() && + initial_source_connection_id == rhs.initial_source_connection_id && + retry_source_connection_id == rhs.retry_source_connection_id && + max_datagram_frame_size.value() == + rhs.max_datagram_frame_size.value() && + initial_round_trip_time_us.value() == + rhs.initial_round_trip_time_us.value() && + google_connection_options == rhs.google_connection_options && + custom_parameters == rhs.custom_parameters)) { + return false; + } + + if ((!preferred_address && rhs.preferred_address) || + (preferred_address && !rhs.preferred_address)) { + return false; + } + if (preferred_address && rhs.preferred_address && + *preferred_address != *rhs.preferred_address) { + return false; + } + + return true; +} + +bool TransportParameters::operator!=(const TransportParameters& rhs) const { + return !(*this == rhs); +} + +bool TransportParameters::AreValid(std::string* error_details) const { + QUICHE_DCHECK(perspective == Perspective::IS_CLIENT || + perspective == Perspective::IS_SERVER); + if (perspective == Perspective::IS_CLIENT && !stateless_reset_token.empty()) { + *error_details = "Client cannot send stateless reset token"; + return false; + } + if (perspective == Perspective::IS_CLIENT && + original_destination_connection_id.has_value()) { + *error_details = "Client cannot send original_destination_connection_id"; + return false; + } + if (!stateless_reset_token.empty() && + stateless_reset_token.size() != kStatelessResetTokenLength) { + *error_details = absl::StrCat("Stateless reset token has bad length ", + stateless_reset_token.size()); + return false; + } + if (perspective == Perspective::IS_CLIENT && preferred_address) { + *error_details = "Client cannot send preferred address"; + return false; + } + if (preferred_address && preferred_address->stateless_reset_token.size() != + kStatelessResetTokenLength) { + *error_details = + absl::StrCat("Preferred address stateless reset token has bad length ", + preferred_address->stateless_reset_token.size()); + return false; + } + if (preferred_address && + (!preferred_address->ipv4_socket_address.host().IsIPv4() || + !preferred_address->ipv6_socket_address.host().IsIPv6())) { + QUIC_BUG(quic_bug_10743_4) << "Preferred address family failure"; + *error_details = "Internal preferred address family failure"; + return false; + } + if (perspective == Perspective::IS_CLIENT && + retry_source_connection_id.has_value()) { + *error_details = "Client cannot send retry_source_connection_id"; + return false; + } + for (const auto& kv : custom_parameters) { + if (TransportParameterIdIsKnown(kv.first)) { + *error_details = absl::StrCat("Using custom_parameters with known ID ", + TransportParameterIdToString(kv.first), + " is not allowed"); + return false; + } + } + if (perspective == Perspective::IS_SERVER && + initial_round_trip_time_us.value() > 0) { + *error_details = "Server cannot send initial round trip time"; + return false; + } + if (version_information.has_value()) { + const QuicVersionLabel& chosen_version = + version_information.value().chosen_version; + const QuicVersionLabelVector& other_versions = + version_information.value().other_versions; + if (chosen_version == 0) { + *error_details = "Invalid chosen version"; + return false; + } + if (perspective == Perspective::IS_CLIENT && + std::find(other_versions.begin(), other_versions.end(), + chosen_version) == other_versions.end()) { + // When sent by the client, chosen_version needs to be present in + // other_versions because other_versions lists the compatible versions and + // the chosen version is part of that list. When sent by the server, + // other_version contains the list of fully-deployed versions which is + // generally equal to the list of supported versions but can slightly + // differ during removal of versions across a server fleet. See + // draft-ietf-quic-version-negotiation for details. + *error_details = "Client chosen version not in other versions"; + return false; + } + } + const bool ok = + max_idle_timeout_ms.IsValid() && max_udp_payload_size.IsValid() && + initial_max_data.IsValid() && + initial_max_stream_data_bidi_local.IsValid() && + initial_max_stream_data_bidi_remote.IsValid() && + initial_max_stream_data_uni.IsValid() && + initial_max_streams_bidi.IsValid() && initial_max_streams_uni.IsValid() && + ack_delay_exponent.IsValid() && max_ack_delay.IsValid() && + min_ack_delay_us.IsValid() && active_connection_id_limit.IsValid() && + max_datagram_frame_size.IsValid() && initial_round_trip_time_us.IsValid(); + if (!ok) { + *error_details = "Invalid transport parameters " + this->ToString(); + } + return ok; +} + +TransportParameters::~TransportParameters() = default; + +bool SerializeTransportParameters(ParsedQuicVersion /*version*/, + const TransportParameters& in, + std::vector<uint8_t>* out) { + std::string error_details; + if (!in.AreValid(&error_details)) { + QUIC_BUG(invalid transport parameters) + << "Not serializing invalid transport parameters: " << error_details; + return false; + } + if (!in.legacy_version_information.has_value() || + in.legacy_version_information.value().version == 0 || + (in.perspective == Perspective::IS_SERVER && + in.legacy_version_information.value().supported_versions.empty())) { + QUIC_BUG(missing versions) << "Refusing to serialize without versions"; + return false; + } + TransportParameters::ParameterMap custom_parameters = in.custom_parameters; + for (const auto& kv : custom_parameters) { + if (kv.first % 31 == 27) { + // See the "Reserved Transport Parameters" section of RFC 9000. + QUIC_BUG(custom_parameters with GREASE) + << "Serializing custom_parameters with GREASE ID " << kv.first + << " is not allowed"; + return false; + } + } + + // Maximum length of the GREASE transport parameter (see below). + static constexpr size_t kMaxGreaseLength = 16; + + // Empirically transport parameters generally fit within 128 bytes, but we + // need to allocate the size up front. Integer transport parameters + // have a maximum encoded length of 24 bytes (3 variable length integers), + // other transport parameters have a length of 16 + the maximum value length. + static constexpr size_t kTypeAndValueLength = 2 * sizeof(uint64_t); + static constexpr size_t kIntegerParameterLength = + kTypeAndValueLength + sizeof(uint64_t); + static constexpr size_t kStatelessResetParameterLength = + kTypeAndValueLength + 16 /* stateless reset token length */; + static constexpr size_t kConnectionIdParameterLength = + kTypeAndValueLength + 255 /* maximum connection ID length */; + static constexpr size_t kPreferredAddressParameterLength = + kTypeAndValueLength + 4 /*IPv4 address */ + 2 /* IPv4 port */ + + 16 /* IPv6 address */ + 1 /* Connection ID length */ + + 255 /* maximum connection ID length */ + 16 /* stateless reset token */; + static constexpr size_t kKnownTransportParamLength = + kConnectionIdParameterLength + // original_destination_connection_id + kIntegerParameterLength + // max_idle_timeout + kStatelessResetParameterLength + // stateless_reset_token + kIntegerParameterLength + // max_udp_payload_size + kIntegerParameterLength + // initial_max_data + kIntegerParameterLength + // initial_max_stream_data_bidi_local + kIntegerParameterLength + // initial_max_stream_data_bidi_remote + kIntegerParameterLength + // initial_max_stream_data_uni + kIntegerParameterLength + // initial_max_streams_bidi + kIntegerParameterLength + // initial_max_streams_uni + kIntegerParameterLength + // ack_delay_exponent + kIntegerParameterLength + // max_ack_delay + kIntegerParameterLength + // min_ack_delay_us + kTypeAndValueLength + // disable_active_migration + kPreferredAddressParameterLength + // preferred_address + kIntegerParameterLength + // active_connection_id_limit + kConnectionIdParameterLength + // initial_source_connection_id + kConnectionIdParameterLength + // retry_source_connection_id + kIntegerParameterLength + // max_datagram_frame_size + kIntegerParameterLength + // initial_round_trip_time_us + kTypeAndValueLength + // google_connection_options + kTypeAndValueLength; // google-version + + std::vector<TransportParameters::TransportParameterId> parameter_ids = { + TransportParameters::kOriginalDestinationConnectionId, + TransportParameters::kMaxIdleTimeout, + TransportParameters::kStatelessResetToken, + TransportParameters::kMaxPacketSize, + TransportParameters::kInitialMaxData, + TransportParameters::kInitialMaxStreamDataBidiLocal, + TransportParameters::kInitialMaxStreamDataBidiRemote, + TransportParameters::kInitialMaxStreamDataUni, + TransportParameters::kInitialMaxStreamsBidi, + TransportParameters::kInitialMaxStreamsUni, + TransportParameters::kAckDelayExponent, + TransportParameters::kMaxAckDelay, + TransportParameters::kMinAckDelay, + TransportParameters::kActiveConnectionIdLimit, + TransportParameters::kMaxDatagramFrameSize, + TransportParameters::kInitialRoundTripTime, + TransportParameters::kDisableActiveMigration, + TransportParameters::kPreferredAddress, + TransportParameters::kInitialSourceConnectionId, + TransportParameters::kRetrySourceConnectionId, + TransportParameters::kGoogleConnectionOptions, + TransportParameters::kGoogleQuicVersion, + TransportParameters::kVersionInformation, + }; + + size_t max_transport_param_length = kKnownTransportParamLength; + // google_connection_options. + if (in.google_connection_options.has_value()) { + max_transport_param_length += + in.google_connection_options.value().size() * sizeof(QuicTag); + } + // Google-specific version extension. + if (in.legacy_version_information.has_value()) { + max_transport_param_length += + sizeof(in.legacy_version_information.value().version) + + 1 /* versions length */ + + in.legacy_version_information.value().supported_versions.size() * + sizeof(QuicVersionLabel); + } + // version_information. + if (in.version_information.has_value()) { + max_transport_param_length += + sizeof(in.version_information.value().chosen_version) + + // Add one for the added GREASE version. + (in.version_information.value().other_versions.size() + 1) * + sizeof(QuicVersionLabel); + } + + // Add a random GREASE transport parameter, as defined in the + // "Reserved Transport Parameters" section of RFC 9000. + // This forces receivers to support unexpected input. + QuicRandom* random = QuicRandom::GetInstance(); + // Transport parameter identifiers are 62 bits long so we need to + // ensure that the output of the computation below fits in 62 bits. + uint64_t grease_id64 = random->RandUint64() % ((1ULL << 62) - 31); + // Make sure grease_id % 31 == 27. Note that this is not uniformely + // distributed but is acceptable since no security depends on this + // randomness. + grease_id64 = (grease_id64 / 31) * 31 + 27; + TransportParameters::TransportParameterId grease_id = + static_cast<TransportParameters::TransportParameterId>(grease_id64); + const size_t grease_length = random->RandUint64() % kMaxGreaseLength; + QUICHE_DCHECK_GE(kMaxGreaseLength, grease_length); + char grease_contents[kMaxGreaseLength]; + random->RandBytes(grease_contents, grease_length); + custom_parameters[grease_id] = std::string(grease_contents, grease_length); + + // Custom parameters. + for (const auto& kv : custom_parameters) { + max_transport_param_length += kTypeAndValueLength + kv.second.length(); + parameter_ids.push_back(kv.first); + } + + // Randomize order of sent transport parameters by walking the array + // backwards and swapping each element with a random earlier one. + for (size_t i = parameter_ids.size() - 1; i > 0; i--) { + std::swap(parameter_ids[i], + parameter_ids[random->InsecureRandUint64() % (i + 1)]); + } + + out->resize(max_transport_param_length); + QuicDataWriter writer(out->size(), reinterpret_cast<char*>(out->data())); + + for (TransportParameters::TransportParameterId parameter_id : parameter_ids) { + switch (parameter_id) { + // original_destination_connection_id + case TransportParameters::kOriginalDestinationConnectionId: { + if (in.original_destination_connection_id.has_value()) { + QUICHE_DCHECK_EQ(Perspective::IS_SERVER, in.perspective); + QuicConnectionId original_destination_connection_id = + in.original_destination_connection_id.value(); + if (!writer.WriteVarInt62( + TransportParameters::kOriginalDestinationConnectionId) || + !writer.WriteStringPieceVarInt62(absl::string_view( + original_destination_connection_id.data(), + original_destination_connection_id.length()))) { + QUIC_BUG(Failed to write original_destination_connection_id) + << "Failed to write original_destination_connection_id " + << original_destination_connection_id << " for " << in; + return false; + } + } + } break; + // max_idle_timeout + case TransportParameters::kMaxIdleTimeout: { + if (!in.max_idle_timeout_ms.Write(&writer)) { + QUIC_BUG(Failed to write idle_timeout) + << "Failed to write idle_timeout for " << in; + return false; + } + } break; + // stateless_reset_token + case TransportParameters::kStatelessResetToken: { + if (!in.stateless_reset_token.empty()) { + QUICHE_DCHECK_EQ(kStatelessResetTokenLength, + in.stateless_reset_token.size()); + QUICHE_DCHECK_EQ(Perspective::IS_SERVER, in.perspective); + if (!writer.WriteVarInt62( + TransportParameters::kStatelessResetToken) || + !writer.WriteStringPieceVarInt62( + absl::string_view(reinterpret_cast<const char*>( + in.stateless_reset_token.data()), + in.stateless_reset_token.size()))) { + QUIC_BUG(Failed to write stateless_reset_token) + << "Failed to write stateless_reset_token of length " + << in.stateless_reset_token.size() << " for " << in; + return false; + } + } + } break; + // max_udp_payload_size + case TransportParameters::kMaxPacketSize: { + if (!in.max_udp_payload_size.Write(&writer)) { + QUIC_BUG(Failed to write max_udp_payload_size) + << "Failed to write max_udp_payload_size for " << in; + return false; + } + } break; + // initial_max_data + case TransportParameters::kInitialMaxData: { + if (!in.initial_max_data.Write(&writer)) { + QUIC_BUG(Failed to write initial_max_data) + << "Failed to write initial_max_data for " << in; + return false; + } + } break; + // initial_max_stream_data_bidi_local + case TransportParameters::kInitialMaxStreamDataBidiLocal: { + if (!in.initial_max_stream_data_bidi_local.Write(&writer)) { + QUIC_BUG(Failed to write initial_max_stream_data_bidi_local) + << "Failed to write initial_max_stream_data_bidi_local for " + << in; + return false; + } + } break; + // initial_max_stream_data_bidi_remote + case TransportParameters::kInitialMaxStreamDataBidiRemote: { + if (!in.initial_max_stream_data_bidi_remote.Write(&writer)) { + QUIC_BUG(Failed to write initial_max_stream_data_bidi_remote) + << "Failed to write initial_max_stream_data_bidi_remote for " + << in; + return false; + } + } break; + // initial_max_stream_data_uni + case TransportParameters::kInitialMaxStreamDataUni: { + if (!in.initial_max_stream_data_uni.Write(&writer)) { + QUIC_BUG(Failed to write initial_max_stream_data_uni) + << "Failed to write initial_max_stream_data_uni for " << in; + return false; + } + } break; + // initial_max_streams_bidi + case TransportParameters::kInitialMaxStreamsBidi: { + if (!in.initial_max_streams_bidi.Write(&writer)) { + QUIC_BUG(Failed to write initial_max_streams_bidi) + << "Failed to write initial_max_streams_bidi for " << in; + return false; + } + } break; + // initial_max_streams_uni + case TransportParameters::kInitialMaxStreamsUni: { + if (!in.initial_max_streams_uni.Write(&writer)) { + QUIC_BUG(Failed to write initial_max_streams_uni) + << "Failed to write initial_max_streams_uni for " << in; + return false; + } + } break; + // ack_delay_exponent + case TransportParameters::kAckDelayExponent: { + if (!in.ack_delay_exponent.Write(&writer)) { + QUIC_BUG(Failed to write ack_delay_exponent) + << "Failed to write ack_delay_exponent for " << in; + return false; + } + } break; + // max_ack_delay + case TransportParameters::kMaxAckDelay: { + if (!in.max_ack_delay.Write(&writer)) { + QUIC_BUG(Failed to write max_ack_delay) + << "Failed to write max_ack_delay for " << in; + return false; + } + } break; + // min_ack_delay_us + case TransportParameters::kMinAckDelay: { + if (!in.min_ack_delay_us.Write(&writer)) { + QUIC_BUG(Failed to write min_ack_delay_us) + << "Failed to write min_ack_delay_us for " << in; + return false; + } + } break; + // active_connection_id_limit + case TransportParameters::kActiveConnectionIdLimit: { + if (!in.active_connection_id_limit.Write(&writer)) { + QUIC_BUG(Failed to write active_connection_id_limit) + << "Failed to write active_connection_id_limit for " << in; + return false; + } + } break; + // max_datagram_frame_size + case TransportParameters::kMaxDatagramFrameSize: { + if (!in.max_datagram_frame_size.Write(&writer)) { + QUIC_BUG(Failed to write max_datagram_frame_size) + << "Failed to write max_datagram_frame_size for " << in; + return false; + } + } break; + // initial_round_trip_time_us + case TransportParameters::kInitialRoundTripTime: { + if (!in.initial_round_trip_time_us.Write(&writer)) { + QUIC_BUG(Failed to write initial_round_trip_time_us) + << "Failed to write initial_round_trip_time_us for " << in; + return false; + } + } break; + // disable_active_migration + case TransportParameters::kDisableActiveMigration: { + if (in.disable_active_migration) { + if (!writer.WriteVarInt62( + TransportParameters::kDisableActiveMigration) || + !writer.WriteVarInt62(/* transport parameter length */ 0)) { + QUIC_BUG(Failed to write disable_active_migration) + << "Failed to write disable_active_migration for " << in; + return false; + } + } + } break; + // preferred_address + case TransportParameters::kPreferredAddress: { + if (in.preferred_address) { + std::string v4_address_bytes = + in.preferred_address->ipv4_socket_address.host().ToPackedString(); + std::string v6_address_bytes = + in.preferred_address->ipv6_socket_address.host().ToPackedString(); + if (v4_address_bytes.length() != 4 || + v6_address_bytes.length() != 16 || + in.preferred_address->stateless_reset_token.size() != + kStatelessResetTokenLength) { + QUIC_BUG(quic_bug_10743_12) + << "Bad lengths " << *in.preferred_address; + return false; + } + const uint64_t preferred_address_length = + v4_address_bytes.length() + /* IPv4 port */ sizeof(uint16_t) + + v6_address_bytes.length() + /* IPv6 port */ sizeof(uint16_t) + + /* connection ID length byte */ sizeof(uint8_t) + + in.preferred_address->connection_id.length() + + in.preferred_address->stateless_reset_token.size(); + if (!writer.WriteVarInt62(TransportParameters::kPreferredAddress) || + !writer.WriteVarInt62( + /* transport parameter length */ preferred_address_length) || + !writer.WriteStringPiece(v4_address_bytes) || + !writer.WriteUInt16( + in.preferred_address->ipv4_socket_address.port()) || + !writer.WriteStringPiece(v6_address_bytes) || + !writer.WriteUInt16( + in.preferred_address->ipv6_socket_address.port()) || + !writer.WriteUInt8( + in.preferred_address->connection_id.length()) || + !writer.WriteBytes( + in.preferred_address->connection_id.data(), + in.preferred_address->connection_id.length()) || + !writer.WriteBytes( + in.preferred_address->stateless_reset_token.data(), + in.preferred_address->stateless_reset_token.size())) { + QUIC_BUG(Failed to write preferred_address) + << "Failed to write preferred_address for " << in; + return false; + } + } + } break; + // initial_source_connection_id + case TransportParameters::kInitialSourceConnectionId: { + if (in.initial_source_connection_id.has_value()) { + QuicConnectionId initial_source_connection_id = + in.initial_source_connection_id.value(); + if (!writer.WriteVarInt62( + TransportParameters::kInitialSourceConnectionId) || + !writer.WriteStringPieceVarInt62( + absl::string_view(initial_source_connection_id.data(), + initial_source_connection_id.length()))) { + QUIC_BUG(Failed to write initial_source_connection_id) + << "Failed to write initial_source_connection_id " + << initial_source_connection_id << " for " << in; + return false; + } + } + } break; + // retry_source_connection_id + case TransportParameters::kRetrySourceConnectionId: { + if (in.retry_source_connection_id.has_value()) { + QUICHE_DCHECK_EQ(Perspective::IS_SERVER, in.perspective); + QuicConnectionId retry_source_connection_id = + in.retry_source_connection_id.value(); + if (!writer.WriteVarInt62( + TransportParameters::kRetrySourceConnectionId) || + !writer.WriteStringPieceVarInt62( + absl::string_view(retry_source_connection_id.data(), + retry_source_connection_id.length()))) { + QUIC_BUG(Failed to write retry_source_connection_id) + << "Failed to write retry_source_connection_id " + << retry_source_connection_id << " for " << in; + return false; + } + } + } break; + // Google-specific connection options. + case TransportParameters::kGoogleConnectionOptions: { + if (in.google_connection_options.has_value()) { + static_assert( + sizeof(in.google_connection_options.value().front()) == 4, + "bad size"); + uint64_t connection_options_length = + in.google_connection_options.value().size() * 4; + if (!writer.WriteVarInt62( + TransportParameters::kGoogleConnectionOptions) || + !writer.WriteVarInt62( + /* transport parameter length */ connection_options_length)) { + QUIC_BUG(Failed to write google_connection_options) + << "Failed to write google_connection_options of length " + << connection_options_length << " for " << in; + return false; + } + for (const QuicTag& connection_option : + in.google_connection_options.value()) { + if (!writer.WriteTag(connection_option)) { + QUIC_BUG(Failed to write google_connection_option) + << "Failed to write google_connection_option " + << QuicTagToString(connection_option) << " for " << in; + return false; + } + } + } + } break; + // Google-specific version extension. + case TransportParameters::kGoogleQuicVersion: { + if (!in.legacy_version_information.has_value()) { + break; + } + static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t), + "bad length"); + uint64_t google_version_length = + sizeof(in.legacy_version_information.value().version); + if (in.perspective == Perspective::IS_SERVER) { + google_version_length += + /* versions length */ sizeof(uint8_t) + + sizeof(QuicVersionLabel) * in.legacy_version_information.value() + .supported_versions.size(); + } + if (!writer.WriteVarInt62(TransportParameters::kGoogleQuicVersion) || + !writer.WriteVarInt62( + /* transport parameter length */ google_version_length) || + !writer.WriteUInt32( + in.legacy_version_information.value().version)) { + QUIC_BUG(Failed to write Google version extension) + << "Failed to write Google version extension for " << in; + return false; + } + if (in.perspective == Perspective::IS_SERVER) { + if (!writer.WriteUInt8(sizeof(QuicVersionLabel) * + in.legacy_version_information.value() + .supported_versions.size())) { + QUIC_BUG(Failed to write versions length) + << "Failed to write versions length for " << in; + return false; + } + for (QuicVersionLabel version_label : + in.legacy_version_information.value().supported_versions) { + if (!writer.WriteUInt32(version_label)) { + QUIC_BUG(Failed to write supported version) + << "Failed to write supported version for " << in; + return false; + } + } + } + } break; + // version_information. + case TransportParameters::kVersionInformation: { + if (!in.version_information.has_value()) { + break; + } + static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t), + "bad length"); + QuicVersionLabelVector other_versions = + in.version_information.value().other_versions; + // Insert one GREASE version at a random index. + const size_t grease_index = + random->InsecureRandUint64() % (other_versions.size() + 1); + other_versions.insert( + other_versions.begin() + grease_index, + CreateQuicVersionLabel(QuicVersionReservedForNegotiation())); + const uint64_t version_information_length = + sizeof(in.version_information.value().chosen_version) + + sizeof(QuicVersionLabel) * other_versions.size(); + if (!writer.WriteVarInt62(TransportParameters::kVersionInformation) || + !writer.WriteVarInt62( + /* transport parameter length */ version_information_length) || + !writer.WriteUInt32( + in.version_information.value().chosen_version)) { + QUIC_BUG(Failed to write chosen version) + << "Failed to write chosen version for " << in; + return false; + } + for (QuicVersionLabel version_label : other_versions) { + if (!writer.WriteUInt32(version_label)) { + QUIC_BUG(Failed to write other version) + << "Failed to write other version for " << in; + return false; + } + } + } break; + // Custom parameters and GREASE. + default: { + auto it = custom_parameters.find(parameter_id); + if (it == custom_parameters.end()) { + QUIC_BUG(Unknown parameter) << "Unknown parameter " << parameter_id; + return false; + } + if (!writer.WriteVarInt62(parameter_id) || + !writer.WriteStringPieceVarInt62(it->second)) { + QUIC_BUG(Failed to write custom parameter) + << "Failed to write custom parameter " << parameter_id; + return false; + } + } break; + } + } + + out->resize(writer.length()); + + QUIC_DLOG(INFO) << "Serialized " << in << " as " << writer.length() + << " bytes"; + + return true; +} + +bool ParseTransportParameters(ParsedQuicVersion version, + Perspective perspective, + const uint8_t* in, + size_t in_len, + TransportParameters* out, + std::string* error_details) { + out->perspective = perspective; + QuicDataReader reader(reinterpret_cast<const char*>(in), in_len); + + while (!reader.IsDoneReading()) { + uint64_t param_id64; + if (!reader.ReadVarInt62(¶m_id64)) { + *error_details = "Failed to parse transport parameter ID"; + return false; + } + TransportParameters::TransportParameterId param_id = + static_cast<TransportParameters::TransportParameterId>(param_id64); + absl::string_view value; + if (!reader.ReadStringPieceVarInt62(&value)) { + *error_details = + "Failed to read length and value of transport parameter " + + TransportParameterIdToString(param_id); + return false; + } + QuicDataReader value_reader(value); + bool parse_success = true; + switch (param_id) { + case TransportParameters::kOriginalDestinationConnectionId: { + if (out->original_destination_connection_id.has_value()) { + *error_details = + "Received a second original_destination_connection_id"; + return false; + } + const size_t connection_id_length = value_reader.BytesRemaining(); + if (!QuicUtils::IsConnectionIdLengthValidForVersion( + connection_id_length, version.transport_version)) { + *error_details = absl::StrCat( + "Received original_destination_connection_id of invalid length ", + connection_id_length); + return false; + } + QuicConnectionId original_destination_connection_id; + if (!value_reader.ReadConnectionId(&original_destination_connection_id, + connection_id_length)) { + *error_details = "Failed to read original_destination_connection_id"; + return false; + } + out->original_destination_connection_id = + original_destination_connection_id; + } break; + case TransportParameters::kMaxIdleTimeout: + parse_success = + out->max_idle_timeout_ms.Read(&value_reader, error_details); + break; + case TransportParameters::kStatelessResetToken: { + if (!out->stateless_reset_token.empty()) { + *error_details = "Received a second stateless_reset_token"; + return false; + } + absl::string_view stateless_reset_token = + value_reader.ReadRemainingPayload(); + if (stateless_reset_token.length() != kStatelessResetTokenLength) { + *error_details = + absl::StrCat("Received stateless_reset_token of invalid length ", + stateless_reset_token.length()); + return false; + } + out->stateless_reset_token.assign( + stateless_reset_token.data(), + stateless_reset_token.data() + stateless_reset_token.length()); + } break; + case TransportParameters::kMaxPacketSize: + parse_success = + out->max_udp_payload_size.Read(&value_reader, error_details); + break; + case TransportParameters::kInitialMaxData: + parse_success = + out->initial_max_data.Read(&value_reader, error_details); + break; + case TransportParameters::kInitialMaxStreamDataBidiLocal: + parse_success = out->initial_max_stream_data_bidi_local.Read( + &value_reader, error_details); + break; + case TransportParameters::kInitialMaxStreamDataBidiRemote: + parse_success = out->initial_max_stream_data_bidi_remote.Read( + &value_reader, error_details); + break; + case TransportParameters::kInitialMaxStreamDataUni: + parse_success = + out->initial_max_stream_data_uni.Read(&value_reader, error_details); + break; + case TransportParameters::kInitialMaxStreamsBidi: + parse_success = + out->initial_max_streams_bidi.Read(&value_reader, error_details); + break; + case TransportParameters::kInitialMaxStreamsUni: + parse_success = + out->initial_max_streams_uni.Read(&value_reader, error_details); + break; + case TransportParameters::kAckDelayExponent: + parse_success = + out->ack_delay_exponent.Read(&value_reader, error_details); + break; + case TransportParameters::kMaxAckDelay: + parse_success = out->max_ack_delay.Read(&value_reader, error_details); + break; + case TransportParameters::kDisableActiveMigration: + if (out->disable_active_migration) { + *error_details = "Received a second disable_active_migration"; + return false; + } + out->disable_active_migration = true; + break; + case TransportParameters::kPreferredAddress: { + TransportParameters::PreferredAddress preferred_address; + uint16_t ipv4_port, ipv6_port; + in_addr ipv4_address; + in6_addr ipv6_address; + preferred_address.stateless_reset_token.resize( + kStatelessResetTokenLength); + if (!value_reader.ReadBytes(&ipv4_address, sizeof(ipv4_address)) || + !value_reader.ReadUInt16(&ipv4_port) || + !value_reader.ReadBytes(&ipv6_address, sizeof(ipv6_address)) || + !value_reader.ReadUInt16(&ipv6_port) || + !value_reader.ReadLengthPrefixedConnectionId( + &preferred_address.connection_id) || + !value_reader.ReadBytes(&preferred_address.stateless_reset_token[0], + kStatelessResetTokenLength)) { + *error_details = "Failed to read preferred_address"; + return false; + } + preferred_address.ipv4_socket_address = + QuicSocketAddress(QuicIpAddress(ipv4_address), ipv4_port); + preferred_address.ipv6_socket_address = + QuicSocketAddress(QuicIpAddress(ipv6_address), ipv6_port); + if (!preferred_address.ipv4_socket_address.host().IsIPv4() || + !preferred_address.ipv6_socket_address.host().IsIPv6()) { + *error_details = "Received preferred_address of bad families " + + preferred_address.ToString(); + return false; + } + if (!QuicUtils::IsConnectionIdValidForVersion( + preferred_address.connection_id, version.transport_version)) { + *error_details = "Received invalid preferred_address connection ID " + + preferred_address.ToString(); + return false; + } + out->preferred_address = + std::make_unique<TransportParameters::PreferredAddress>( + preferred_address); + } break; + case TransportParameters::kActiveConnectionIdLimit: + parse_success = + out->active_connection_id_limit.Read(&value_reader, error_details); + break; + case TransportParameters::kInitialSourceConnectionId: { + if (out->initial_source_connection_id.has_value()) { + *error_details = "Received a second initial_source_connection_id"; + return false; + } + const size_t connection_id_length = value_reader.BytesRemaining(); + if (!QuicUtils::IsConnectionIdLengthValidForVersion( + connection_id_length, version.transport_version)) { + *error_details = absl::StrCat( + "Received initial_source_connection_id of invalid length ", + connection_id_length); + return false; + } + QuicConnectionId initial_source_connection_id; + if (!value_reader.ReadConnectionId(&initial_source_connection_id, + connection_id_length)) { + *error_details = "Failed to read initial_source_connection_id"; + return false; + } + out->initial_source_connection_id = initial_source_connection_id; + } break; + case TransportParameters::kRetrySourceConnectionId: { + if (out->retry_source_connection_id.has_value()) { + *error_details = "Received a second retry_source_connection_id"; + return false; + } + const size_t connection_id_length = value_reader.BytesRemaining(); + if (!QuicUtils::IsConnectionIdLengthValidForVersion( + connection_id_length, version.transport_version)) { + *error_details = absl::StrCat( + "Received retry_source_connection_id of invalid length ", + connection_id_length); + return false; + } + QuicConnectionId retry_source_connection_id; + if (!value_reader.ReadConnectionId(&retry_source_connection_id, + connection_id_length)) { + *error_details = "Failed to read retry_source_connection_id"; + return false; + } + out->retry_source_connection_id = retry_source_connection_id; + } break; + case TransportParameters::kMaxDatagramFrameSize: + parse_success = + out->max_datagram_frame_size.Read(&value_reader, error_details); + break; + case TransportParameters::kInitialRoundTripTime: + parse_success = + out->initial_round_trip_time_us.Read(&value_reader, error_details); + break; + case TransportParameters::kGoogleConnectionOptions: { + if (out->google_connection_options.has_value()) { + *error_details = "Received a second google_connection_options"; + return false; + } + out->google_connection_options = QuicTagVector{}; + while (!value_reader.IsDoneReading()) { + QuicTag connection_option; + if (!value_reader.ReadTag(&connection_option)) { + *error_details = "Failed to read a google_connection_options"; + return false; + } + out->google_connection_options.value().push_back(connection_option); + } + } break; + case TransportParameters::kGoogleQuicVersion: { + if (!out->legacy_version_information.has_value()) { + out->legacy_version_information = + TransportParameters::LegacyVersionInformation(); + } + if (!value_reader.ReadUInt32( + &out->legacy_version_information.value().version)) { + *error_details = "Failed to read Google version extension version"; + return false; + } + if (perspective == Perspective::IS_SERVER) { + uint8_t versions_length; + if (!value_reader.ReadUInt8(&versions_length)) { + *error_details = "Failed to parse Google supported versions length"; + return false; + } + const uint8_t num_versions = versions_length / sizeof(uint32_t); + for (uint8_t i = 0; i < num_versions; ++i) { + QuicVersionLabel version; + if (!value_reader.ReadUInt32(&version)) { + *error_details = "Failed to parse Google supported version"; + return false; + } + out->legacy_version_information.value() + .supported_versions.push_back(version); + } + } + } break; + case TransportParameters::kVersionInformation: { + if (out->version_information.has_value()) { + *error_details = "Received a second version_information"; + return false; + } + out->version_information = TransportParameters::VersionInformation(); + if (!value_reader.ReadUInt32( + &out->version_information.value().chosen_version)) { + *error_details = "Failed to read chosen version"; + return false; + } + while (!value_reader.IsDoneReading()) { + QuicVersionLabel other_version; + if (!value_reader.ReadUInt32(&other_version)) { + *error_details = "Failed to parse other version"; + return false; + } + out->version_information.value().other_versions.push_back( + other_version); + } + } break; + case TransportParameters::kMinAckDelay: + parse_success = + out->min_ack_delay_us.Read(&value_reader, error_details); + break; + default: + if (out->custom_parameters.find(param_id) != + out->custom_parameters.end()) { + *error_details = "Received a second unknown parameter" + + TransportParameterIdToString(param_id); + return false; + } + out->custom_parameters[param_id] = + std::string(value_reader.ReadRemainingPayload()); + break; + } + if (!parse_success) { + QUICHE_DCHECK(!error_details->empty()); + return false; + } + if (!value_reader.IsDoneReading()) { + *error_details = absl::StrCat( + "Received unexpected ", value_reader.BytesRemaining(), + " bytes after parsing ", TransportParameterIdToString(param_id)); + return false; + } + } + + if (!out->AreValid(error_details)) { + QUICHE_DCHECK(!error_details->empty()); + return false; + } + + QUIC_DLOG(INFO) << "Parsed transport parameters " << *out << " from " + << in_len << " bytes"; + + return true; +} + +namespace { + +bool DigestUpdateIntegerParam( + EVP_MD_CTX* hash_ctx, + const TransportParameters::IntegerParameter& param) { + uint64_t value = param.value(); + return EVP_DigestUpdate(hash_ctx, &value, sizeof(value)); +} + +} // namespace + +bool SerializeTransportParametersForTicket( + const TransportParameters& in, + const std::vector<uint8_t>& application_data, + std::vector<uint8_t>* out) { + std::string error_details; + if (!in.AreValid(&error_details)) { + QUIC_BUG(quic_bug_10743_26) + << "Not serializing invalid transport parameters: " << error_details; + return false; + } + + out->resize(SHA256_DIGEST_LENGTH + 1); + const uint8_t serialization_version = 0; + (*out)[0] = serialization_version; + + bssl::ScopedEVP_MD_CTX hash_ctx; + // Write application data: + uint64_t app_data_len = application_data.size(); + const uint64_t parameter_version = 0; + // The format of the input to the hash function is as follows: + // - The application data, prefixed with a 64-bit length field. + // - Transport parameters: + // - A 64-bit version field indicating which version of encoding is used + // for transport parameters. + // - A list of 64-bit integers representing the relevant parameters. + // + // When changing which parameters are included, additional parameters can be + // added to the end of the list without changing the version field. New + // parameters that are variable length must be length prefixed. If + // parameters are removed from the list, the version field must be + // incremented. + // + // Integers happen to be written in host byte order, not network byte order. + if (!EVP_DigestInit(hash_ctx.get(), EVP_sha256()) || + !EVP_DigestUpdate(hash_ctx.get(), &app_data_len, sizeof(app_data_len)) || + !EVP_DigestUpdate(hash_ctx.get(), application_data.data(), + application_data.size()) || + !EVP_DigestUpdate(hash_ctx.get(), ¶meter_version, + sizeof(parameter_version))) { + QUIC_BUG(quic_bug_10743_27) + << "Unexpected failure of EVP_Digest functions when hashing " + "Transport Parameters for ticket"; + return false; + } + + // Write transport parameters specified by draft-ietf-quic-transport-28, + // section 7.4.1, that are remembered for 0-RTT. + if (!DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_data) || + !DigestUpdateIntegerParam(hash_ctx.get(), + in.initial_max_stream_data_bidi_local) || + !DigestUpdateIntegerParam(hash_ctx.get(), + in.initial_max_stream_data_bidi_remote) || + !DigestUpdateIntegerParam(hash_ctx.get(), + in.initial_max_stream_data_uni) || + !DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_streams_bidi) || + !DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_streams_uni) || + !DigestUpdateIntegerParam(hash_ctx.get(), + in.active_connection_id_limit)) { + QUIC_BUG(quic_bug_10743_28) + << "Unexpected failure of EVP_Digest functions when hashing " + "Transport Parameters for ticket"; + return false; + } + uint8_t disable_active_migration = in.disable_active_migration ? 1 : 0; + if (!EVP_DigestUpdate(hash_ctx.get(), &disable_active_migration, + sizeof(disable_active_migration)) || + !EVP_DigestFinal(hash_ctx.get(), out->data() + 1, nullptr)) { + QUIC_BUG(quic_bug_10743_29) + << "Unexpected failure of EVP_Digest functions when hashing " + "Transport Parameters for ticket"; + return false; + } + return true; +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/transport_parameters.h b/quiche/quic/core/crypto/transport_parameters.h new file mode 100644 index 0000000..7ccd557 --- /dev/null +++ b/quiche/quic/core/crypto/transport_parameters.h
@@ -0,0 +1,314 @@ +// Copyright (c) 2018 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. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_TRANSPORT_PARAMETERS_H_ +#define QUICHE_QUIC_CORE_CRYPTO_TRANSPORT_PARAMETERS_H_ + +#include <memory> +#include <vector> + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_data_reader.h" +#include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/core/quic_tag.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_containers.h" +#include "quiche/quic/platform/api/quic_socket_address.h" + +namespace quic { + +// TransportParameters contains parameters for QUIC's transport layer that are +// exchanged during the TLS handshake. This struct is a mirror of the struct in +// the "Transport Parameter Encoding" section of draft-ietf-quic-transport. +// This struct currently uses the values from draft 29. +struct QUIC_EXPORT_PRIVATE TransportParameters { + // The identifier used to differentiate transport parameters. + enum TransportParameterId : uint64_t; + // A map used to specify custom parameters. + using ParameterMap = absl::flat_hash_map<TransportParameterId, std::string>; + // Represents an individual QUIC transport parameter that only encodes a + // variable length integer. Can only be created inside the constructor for + // TransportParameters. + class QUIC_EXPORT_PRIVATE IntegerParameter { + public: + // Forbid constructing and copying apart from TransportParameters. + IntegerParameter() = delete; + IntegerParameter& operator=(const IntegerParameter&) = delete; + // Sets the value of this transport parameter. + void set_value(uint64_t value); + // Gets the value of this transport parameter. + uint64_t value() const; + // Validates whether the current value is valid. + bool IsValid() const; + // Writes to a crypto byte buffer, used during serialization. Does not write + // anything if the value is equal to the parameter's default value. + // Returns whether the write was successful. + bool Write(QuicDataWriter* writer) const; + // Reads from a crypto byte string, used during parsing. + // Returns whether the read was successful. + // On failure, this method will write a human-readable error message to + // |error_details|. + bool Read(QuicDataReader* reader, std::string* error_details); + // operator<< allows easily logging integer transport parameters. + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, + const IntegerParameter& param); + + private: + friend struct TransportParameters; + // Constructors for initial setup used by TransportParameters only. + // This constructor sets |default_value| and |min_value| to 0, and + // |max_value| to kVarInt62MaxValue. + explicit IntegerParameter(TransportParameterId param_id); + IntegerParameter(TransportParameterId param_id, + uint64_t default_value, + uint64_t min_value, + uint64_t max_value); + IntegerParameter(const IntegerParameter& other) = default; + IntegerParameter(IntegerParameter&& other) = default; + // Human-readable string representation. + std::string ToString(bool for_use_in_list) const; + + // Number used to indicate this transport parameter. + TransportParameterId param_id_; + // Current value of the transport parameter. + uint64_t value_; + // Default value of this transport parameter, as per IETF specification. + const uint64_t default_value_; + // Minimum value of this transport parameter, as per IETF specification. + const uint64_t min_value_; + // Maximum value of this transport parameter, as per IETF specification. + const uint64_t max_value_; + // Ensures this parameter is not parsed twice in the same message. + bool has_been_read_; + }; + + // Represents the preferred_address transport parameter that a server can + // send to clients. + struct QUIC_EXPORT_PRIVATE PreferredAddress { + PreferredAddress(); + PreferredAddress(const PreferredAddress& other) = default; + PreferredAddress(PreferredAddress&& other) = default; + ~PreferredAddress(); + bool operator==(const PreferredAddress& rhs) const; + bool operator!=(const PreferredAddress& rhs) const; + + QuicSocketAddress ipv4_socket_address; + QuicSocketAddress ipv6_socket_address; + QuicConnectionId connection_id; + std::vector<uint8_t> stateless_reset_token; + + // Allows easily logging. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, + const TransportParameters& params); + }; + + // LegacyVersionInformation represents the Google QUIC downgrade prevention + // mechanism ported to QUIC+TLS. It is exchanged using transport parameter ID + // 0x4752 and will eventually be deprecated in favor of + // draft-ietf-quic-version-negotiation. + struct QUIC_EXPORT_PRIVATE LegacyVersionInformation { + LegacyVersionInformation(); + LegacyVersionInformation(const LegacyVersionInformation& other) = default; + LegacyVersionInformation& operator=(const LegacyVersionInformation& other) = + default; + LegacyVersionInformation& operator=(LegacyVersionInformation&& other) = + default; + LegacyVersionInformation(LegacyVersionInformation&& other) = default; + ~LegacyVersionInformation() = default; + bool operator==(const LegacyVersionInformation& rhs) const; + bool operator!=(const LegacyVersionInformation& rhs) const; + // When sent by the client, |version| is the initial version offered by the + // client (before any version negotiation packets) for this connection. When + // sent by the server, |version| is the version that is in use. + QuicVersionLabel version; + + // When sent by the server, |supported_versions| contains a list of all + // versions that the server would send in a version negotiation packet. When + // sent by the client, this is empty. + QuicVersionLabelVector supported_versions; + + // Allows easily logging. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, + const LegacyVersionInformation& legacy_version_information); + }; + + // Version information used for version downgrade prevention and compatible + // version negotiation. See draft-ietf-quic-version-negotiation-05. + struct QUIC_EXPORT_PRIVATE VersionInformation { + VersionInformation(); + VersionInformation(const VersionInformation& other) = default; + VersionInformation& operator=(const VersionInformation& other) = default; + VersionInformation& operator=(VersionInformation&& other) = default; + VersionInformation(VersionInformation&& other) = default; + ~VersionInformation() = default; + bool operator==(const VersionInformation& rhs) const; + bool operator!=(const VersionInformation& rhs) const; + + // Version that the sender has chosen to use on this connection. + QuicVersionLabel chosen_version; + + // When sent by the client, |other_versions| contains all the versions that + // this first flight is compatible with. When sent by the server, + // |other_versions| contains all of the versions supported by the server. + QuicVersionLabelVector other_versions; + + // Allows easily logging. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, const VersionInformation& version_information); + }; + + TransportParameters(); + TransportParameters(const TransportParameters& other); + ~TransportParameters(); + bool operator==(const TransportParameters& rhs) const; + bool operator!=(const TransportParameters& rhs) const; + + // Represents the sender of the transport parameters. When |perspective| is + // Perspective::IS_CLIENT, this struct is being used in the client_hello + // handshake message; when it is Perspective::IS_SERVER, it is being used in + // the encrypted_extensions handshake message. + Perspective perspective; + + // Google QUIC downgrade prevention mechanism sent over QUIC+TLS. + absl::optional<LegacyVersionInformation> legacy_version_information; + + // IETF downgrade prevention and compatible version negotiation, see + // draft-ietf-quic-version-negotiation. + absl::optional<VersionInformation> version_information; + + // The value of the Destination Connection ID field from the first + // Initial packet sent by the client. + absl::optional<QuicConnectionId> original_destination_connection_id; + + // Maximum idle timeout expressed in milliseconds. + IntegerParameter max_idle_timeout_ms; + + // Stateless reset token used in verifying stateless resets. + std::vector<uint8_t> stateless_reset_token; + + // Limits the size of packets that the endpoint is willing to receive. + // This indicates that packets larger than this limit will be dropped. + IntegerParameter max_udp_payload_size; + + // Contains the initial value for the maximum amount of data that can + // be sent on the connection. + IntegerParameter initial_max_data; + + // Initial flow control limit for locally-initiated bidirectional streams. + IntegerParameter initial_max_stream_data_bidi_local; + + // Initial flow control limit for peer-initiated bidirectional streams. + IntegerParameter initial_max_stream_data_bidi_remote; + + // Initial flow control limit for unidirectional streams. + IntegerParameter initial_max_stream_data_uni; + + // Initial maximum number of bidirectional streams the peer may initiate. + IntegerParameter initial_max_streams_bidi; + + // Initial maximum number of unidirectional streams the peer may initiate. + IntegerParameter initial_max_streams_uni; + + // Exponent used to decode the ACK Delay field in ACK frames. + IntegerParameter ack_delay_exponent; + + // Maximum amount of time in milliseconds by which the endpoint will + // delay sending acknowledgments. + IntegerParameter max_ack_delay; + + // Minimum amount of time in microseconds by which the endpoint will + // delay sending acknowledgments. Used to enable sender control of ack delay. + IntegerParameter min_ack_delay_us; + + // Indicates lack of support for connection migration. + bool disable_active_migration; + + // Used to effect a change in server address at the end of the handshake. + std::unique_ptr<PreferredAddress> preferred_address; + + // Maximum number of connection IDs from the peer that an endpoint is willing + // to store. + IntegerParameter active_connection_id_limit; + + // The value that the endpoint included in the Source Connection ID field of + // the first Initial packet it sent. + absl::optional<QuicConnectionId> initial_source_connection_id; + + // The value that the server included in the Source Connection ID field of a + // Retry packet it sent. + absl::optional<QuicConnectionId> retry_source_connection_id; + + // Indicates support for the DATAGRAM frame and the maximum frame size that + // the sender accepts. See draft-ietf-quic-datagram. + IntegerParameter max_datagram_frame_size; + + // Google-specific transport parameter that carries an estimate of the + // initial round-trip time in microseconds. + IntegerParameter initial_round_trip_time_us; + + // Google-specific connection options. + absl::optional<QuicTagVector> google_connection_options; + + // Validates whether transport parameters are valid according to + // the specification. If the transport parameters are not valid, this method + // will write a human-readable error message to |error_details|. + bool AreValid(std::string* error_details) const; + + // Custom parameters that may be specific to application protocol. + ParameterMap custom_parameters; + + // Allows easily logging transport parameters. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, + const TransportParameters& params); +}; + +// Serializes a TransportParameters struct into the format for sending it in a +// TLS extension. The serialized bytes are written to |*out|. Returns if the +// parameters are valid and serialization succeeded. +QUIC_EXPORT_PRIVATE bool SerializeTransportParameters( + ParsedQuicVersion version, + const TransportParameters& in, + std::vector<uint8_t>* out); + +// Parses bytes from the quic_transport_parameters TLS extension and writes the +// parsed parameters into |*out|. Input is read from |in| for |in_len| bytes. +// |perspective| indicates whether the input came from a client or a server. +// This method returns true if the input was successfully parsed. +// On failure, this method will write a human-readable error message to +// |error_details|. +QUIC_EXPORT_PRIVATE bool ParseTransportParameters(ParsedQuicVersion version, + Perspective perspective, + const uint8_t* in, + size_t in_len, + TransportParameters* out, + std::string* error_details); + +// Serializes |in| and |application_data| in a deterministic format so that +// multiple calls to SerializeTransportParametersForTicket with the same inputs +// will generate the same output, and if the inputs differ, then the output will +// differ. The output of this function is used by the server in +// SSL_set_quic_early_data_context to determine whether early data should be +// accepted: Early data will only be accepted if the inputs to this function +// match what they were on the connection that issued an early data capable +// ticket. +QUIC_EXPORT_PRIVATE bool SerializeTransportParametersForTicket( + const TransportParameters& in, + const std::vector<uint8_t>& application_data, + std::vector<uint8_t>* out); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_TRANSPORT_PARAMETERS_H_
diff --git a/quiche/quic/core/crypto/transport_parameters_test.cc b/quiche/quic/core/crypto/transport_parameters_test.cc new file mode 100644 index 0000000..fea6608 --- /dev/null +++ b/quiche/quic/core/crypto/transport_parameters_test.cc
@@ -0,0 +1,1131 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/transport_parameters.h" + +#include <cstring> +#include <utility> + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_tag.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/platform/api/quic_expect_bug.h" +#include "quiche/quic/platform/api/quic_ip_address.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +namespace quic { +namespace test { +namespace { + +const QuicVersionLabel kFakeVersionLabel = 0x01234567; +const QuicVersionLabel kFakeVersionLabel2 = 0x89ABCDEF; +const uint64_t kFakeIdleTimeoutMilliseconds = 12012; +const uint64_t kFakeInitialMaxData = 101; +const uint64_t kFakeInitialMaxStreamDataBidiLocal = 2001; +const uint64_t kFakeInitialMaxStreamDataBidiRemote = 2002; +const uint64_t kFakeInitialMaxStreamDataUni = 3000; +const uint64_t kFakeInitialMaxStreamsBidi = 21; +const uint64_t kFakeInitialMaxStreamsUni = 22; +const bool kFakeDisableMigration = true; +const uint64_t kFakeInitialRoundTripTime = 53; +const uint8_t kFakePreferredStatelessResetTokenData[16] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}; + +const auto kCustomParameter1 = + static_cast<TransportParameters::TransportParameterId>(0xffcd); +const char* kCustomParameter1Value = "foo"; +const auto kCustomParameter2 = + static_cast<TransportParameters::TransportParameterId>(0xff34); +const char* kCustomParameter2Value = "bar"; + +QuicConnectionId CreateFakeOriginalDestinationConnectionId() { + return TestConnectionId(0x1337); +} + +QuicConnectionId CreateFakeInitialSourceConnectionId() { + return TestConnectionId(0x2345); +} + +QuicConnectionId CreateFakeRetrySourceConnectionId() { + return TestConnectionId(0x9876); +} + +QuicConnectionId CreateFakePreferredConnectionId() { + return TestConnectionId(0xBEEF); +} + +std::vector<uint8_t> CreateFakePreferredStatelessResetToken() { + return std::vector<uint8_t>( + kFakePreferredStatelessResetTokenData, + kFakePreferredStatelessResetTokenData + + sizeof(kFakePreferredStatelessResetTokenData)); +} + +QuicSocketAddress CreateFakeV4SocketAddress() { + QuicIpAddress ipv4_address; + if (!ipv4_address.FromString("65.66.67.68")) { // 0x41, 0x42, 0x43, 0x44 + QUIC_LOG(FATAL) << "Failed to create IPv4 address"; + return QuicSocketAddress(); + } + return QuicSocketAddress(ipv4_address, 0x4884); +} + +QuicSocketAddress CreateFakeV6SocketAddress() { + QuicIpAddress ipv6_address; + if (!ipv6_address.FromString("6061:6263:6465:6667:6869:6A6B:6C6D:6E6F")) { + QUIC_LOG(FATAL) << "Failed to create IPv6 address"; + return QuicSocketAddress(); + } + return QuicSocketAddress(ipv6_address, 0x6336); +} + +std::unique_ptr<TransportParameters::PreferredAddress> +CreateFakePreferredAddress() { + TransportParameters::PreferredAddress preferred_address; + preferred_address.ipv4_socket_address = CreateFakeV4SocketAddress(); + preferred_address.ipv6_socket_address = CreateFakeV6SocketAddress(); + preferred_address.connection_id = CreateFakePreferredConnectionId(); + preferred_address.stateless_reset_token = + CreateFakePreferredStatelessResetToken(); + return std::make_unique<TransportParameters::PreferredAddress>( + preferred_address); +} + +TransportParameters::LegacyVersionInformation +CreateFakeLegacyVersionInformationClient() { + TransportParameters::LegacyVersionInformation legacy_version_information; + legacy_version_information.version = kFakeVersionLabel; + return legacy_version_information; +} + +TransportParameters::LegacyVersionInformation +CreateFakeLegacyVersionInformationServer() { + TransportParameters::LegacyVersionInformation legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + legacy_version_information.supported_versions.push_back(kFakeVersionLabel); + legacy_version_information.supported_versions.push_back(kFakeVersionLabel2); + return legacy_version_information; +} + +TransportParameters::VersionInformation CreateFakeVersionInformation() { + TransportParameters::VersionInformation version_information; + version_information.chosen_version = kFakeVersionLabel; + version_information.other_versions.push_back(kFakeVersionLabel); + version_information.other_versions.push_back(kFakeVersionLabel2); + return version_information; +} + +QuicTagVector CreateFakeGoogleConnectionOptions() { + return {kALPN, MakeQuicTag('E', 'F', 'G', 0x00), + MakeQuicTag('H', 'I', 'J', 0xff)}; +} + +void RemoveGreaseParameters(TransportParameters* params) { + std::vector<TransportParameters::TransportParameterId> grease_params; + for (const auto& kv : params->custom_parameters) { + if (kv.first % 31 == 27) { + grease_params.push_back(kv.first); + } + } + EXPECT_EQ(grease_params.size(), 1u); + for (TransportParameters::TransportParameterId param_id : grease_params) { + params->custom_parameters.erase(param_id); + } + // Remove all GREASE versions from version_information.other_versions. + if (params->version_information.has_value()) { + QuicVersionLabelVector& other_versions = + params->version_information.value().other_versions; + for (auto it = other_versions.begin(); it != other_versions.end();) { + if ((*it & 0x0f0f0f0f) == 0x0a0a0a0a) { + it = other_versions.erase(it); + } else { + ++it; + } + } + } +} + +} // namespace + +class TransportParametersTest : public QuicTestWithParam<ParsedQuicVersion> { + protected: + TransportParametersTest() : version_(GetParam()) {} + + ParsedQuicVersion version_; +}; + +INSTANTIATE_TEST_SUITE_P(TransportParametersTests, + TransportParametersTest, + ::testing::ValuesIn(AllSupportedVersionsWithTls()), + ::testing::PrintToStringParamName()); + +TEST_P(TransportParametersTest, Comparator) { + TransportParameters orig_params; + TransportParameters new_params; + // Test comparison on primitive members. + orig_params.perspective = Perspective::IS_CLIENT; + new_params.perspective = Perspective::IS_SERVER; + EXPECT_NE(orig_params, new_params); + EXPECT_FALSE(orig_params == new_params); + EXPECT_TRUE(orig_params != new_params); + new_params.perspective = Perspective::IS_CLIENT; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + new_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.version_information = CreateFakeVersionInformation(); + new_params.version_information = CreateFakeVersionInformation(); + orig_params.disable_active_migration = true; + new_params.disable_active_migration = true; + EXPECT_EQ(orig_params, new_params); + EXPECT_TRUE(orig_params == new_params); + EXPECT_FALSE(orig_params != new_params); + + // Test comparison on vectors. + orig_params.legacy_version_information.value().supported_versions.push_back( + kFakeVersionLabel); + new_params.legacy_version_information.value().supported_versions.push_back( + kFakeVersionLabel2); + EXPECT_NE(orig_params, new_params); + EXPECT_FALSE(orig_params == new_params); + EXPECT_TRUE(orig_params != new_params); + new_params.legacy_version_information.value().supported_versions.pop_back(); + new_params.legacy_version_information.value().supported_versions.push_back( + kFakeVersionLabel); + orig_params.stateless_reset_token = CreateStatelessResetTokenForTest(); + new_params.stateless_reset_token = CreateStatelessResetTokenForTest(); + EXPECT_EQ(orig_params, new_params); + EXPECT_TRUE(orig_params == new_params); + EXPECT_FALSE(orig_params != new_params); + + // Test comparison on IntegerParameters. + orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + new_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest + 1); + EXPECT_NE(orig_params, new_params); + EXPECT_FALSE(orig_params == new_params); + EXPECT_TRUE(orig_params != new_params); + new_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + EXPECT_EQ(orig_params, new_params); + EXPECT_TRUE(orig_params == new_params); + EXPECT_FALSE(orig_params != new_params); + + // Test comparison on PreferredAddress + orig_params.preferred_address = CreateFakePreferredAddress(); + EXPECT_NE(orig_params, new_params); + EXPECT_FALSE(orig_params == new_params); + EXPECT_TRUE(orig_params != new_params); + new_params.preferred_address = CreateFakePreferredAddress(); + EXPECT_EQ(orig_params, new_params); + EXPECT_TRUE(orig_params == new_params); + EXPECT_FALSE(orig_params != new_params); + + // Test comparison on CustomMap + orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; + orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; + + new_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; + new_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; + EXPECT_EQ(orig_params, new_params); + EXPECT_TRUE(orig_params == new_params); + EXPECT_FALSE(orig_params != new_params); + + // Test comparison on connection IDs. + orig_params.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + new_params.initial_source_connection_id = absl::nullopt; + EXPECT_NE(orig_params, new_params); + EXPECT_FALSE(orig_params == new_params); + EXPECT_TRUE(orig_params != new_params); + new_params.initial_source_connection_id = TestConnectionId(0xbadbad); + EXPECT_NE(orig_params, new_params); + EXPECT_FALSE(orig_params == new_params); + EXPECT_TRUE(orig_params != new_params); + new_params.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + EXPECT_EQ(orig_params, new_params); + EXPECT_TRUE(orig_params == new_params); + EXPECT_FALSE(orig_params != new_params); +} + +TEST_P(TransportParametersTest, CopyConstructor) { + TransportParameters orig_params; + orig_params.perspective = Perspective::IS_CLIENT; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.version_information = CreateFakeVersionInformation(); + orig_params.original_destination_connection_id = + CreateFakeOriginalDestinationConnectionId(); + orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + orig_params.stateless_reset_token = CreateStatelessResetTokenForTest(); + orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + orig_params.initial_max_data.set_value(kFakeInitialMaxData); + orig_params.initial_max_stream_data_bidi_local.set_value( + kFakeInitialMaxStreamDataBidiLocal); + orig_params.initial_max_stream_data_bidi_remote.set_value( + kFakeInitialMaxStreamDataBidiRemote); + orig_params.initial_max_stream_data_uni.set_value( + kFakeInitialMaxStreamDataUni); + orig_params.initial_max_streams_bidi.set_value(kFakeInitialMaxStreamsBidi); + orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni); + orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest); + orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); + orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); + orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.preferred_address = CreateFakePreferredAddress(); + orig_params.active_connection_id_limit.set_value( + kActiveConnectionIdLimitForTest); + orig_params.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + orig_params.retry_source_connection_id = CreateFakeRetrySourceConnectionId(); + orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); + orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; + orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; + + TransportParameters new_params(orig_params); + EXPECT_EQ(new_params, orig_params); +} + +TEST_P(TransportParametersTest, RoundTripClient) { + TransportParameters orig_params; + orig_params.perspective = Perspective::IS_CLIENT; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.version_information = CreateFakeVersionInformation(); + orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + orig_params.initial_max_data.set_value(kFakeInitialMaxData); + orig_params.initial_max_stream_data_bidi_local.set_value( + kFakeInitialMaxStreamDataBidiLocal); + orig_params.initial_max_stream_data_bidi_remote.set_value( + kFakeInitialMaxStreamDataBidiRemote); + orig_params.initial_max_stream_data_uni.set_value( + kFakeInitialMaxStreamDataUni); + orig_params.initial_max_streams_bidi.set_value(kFakeInitialMaxStreamsBidi); + orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni); + orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest); + orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); + orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); + orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.active_connection_id_limit.set_value( + kActiveConnectionIdLimitForTest); + orig_params.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); + orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; + orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParameters(version_, orig_params, &serialized)); + + TransportParameters new_params; + std::string error_details; + ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_CLIENT, + serialized.data(), serialized.size(), + &new_params, &error_details)) + << error_details; + EXPECT_TRUE(error_details.empty()); + RemoveGreaseParameters(&new_params); + EXPECT_EQ(new_params, orig_params); +} + +TEST_P(TransportParametersTest, RoundTripServer) { + TransportParameters orig_params; + orig_params.perspective = Perspective::IS_SERVER; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationServer(); + orig_params.version_information = CreateFakeVersionInformation(); + orig_params.original_destination_connection_id = + CreateFakeOriginalDestinationConnectionId(); + orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + orig_params.stateless_reset_token = CreateStatelessResetTokenForTest(); + orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + orig_params.initial_max_data.set_value(kFakeInitialMaxData); + orig_params.initial_max_stream_data_bidi_local.set_value( + kFakeInitialMaxStreamDataBidiLocal); + orig_params.initial_max_stream_data_bidi_remote.set_value( + kFakeInitialMaxStreamDataBidiRemote); + orig_params.initial_max_stream_data_uni.set_value( + kFakeInitialMaxStreamDataUni); + orig_params.initial_max_streams_bidi.set_value(kFakeInitialMaxStreamsBidi); + orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni); + orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest); + orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); + orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); + orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.preferred_address = CreateFakePreferredAddress(); + orig_params.active_connection_id_limit.set_value( + kActiveConnectionIdLimitForTest); + orig_params.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + orig_params.retry_source_connection_id = CreateFakeRetrySourceConnectionId(); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParameters(version_, orig_params, &serialized)); + + TransportParameters new_params; + std::string error_details; + ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_SERVER, + serialized.data(), serialized.size(), + &new_params, &error_details)) + << error_details; + EXPECT_TRUE(error_details.empty()); + RemoveGreaseParameters(&new_params); + EXPECT_EQ(new_params, orig_params); +} + +TEST_P(TransportParametersTest, AreValid) { + { + TransportParameters params; + std::string error_details; + params.perspective = Perspective::IS_CLIENT; + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + } + { + TransportParameters params; + std::string error_details; + params.perspective = Perspective::IS_CLIENT; + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.max_idle_timeout_ms.set_value(601000); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + } + { + TransportParameters params; + std::string error_details; + params.perspective = Perspective::IS_CLIENT; + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.max_udp_payload_size.set_value(1200); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.max_udp_payload_size.set_value(65535); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.max_udp_payload_size.set_value(9999999); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.max_udp_payload_size.set_value(0); + error_details = ""; + EXPECT_FALSE(params.AreValid(&error_details)); + EXPECT_EQ(error_details, + "Invalid transport parameters [Client max_udp_payload_size 0 " + "(Invalid)]"); + params.max_udp_payload_size.set_value(1199); + error_details = ""; + EXPECT_FALSE(params.AreValid(&error_details)); + EXPECT_EQ(error_details, + "Invalid transport parameters [Client max_udp_payload_size 1199 " + "(Invalid)]"); + } + { + TransportParameters params; + std::string error_details; + params.perspective = Perspective::IS_CLIENT; + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.ack_delay_exponent.set_value(0); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.ack_delay_exponent.set_value(20); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.ack_delay_exponent.set_value(21); + EXPECT_FALSE(params.AreValid(&error_details)); + EXPECT_EQ(error_details, + "Invalid transport parameters [Client ack_delay_exponent 21 " + "(Invalid)]"); + } + { + TransportParameters params; + std::string error_details; + params.perspective = Perspective::IS_CLIENT; + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.active_connection_id_limit.set_value(2); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.active_connection_id_limit.set_value(999999); + EXPECT_TRUE(params.AreValid(&error_details)); + EXPECT_TRUE(error_details.empty()); + params.active_connection_id_limit.set_value(1); + EXPECT_FALSE(params.AreValid(&error_details)); + EXPECT_EQ(error_details, + "Invalid transport parameters [Client active_connection_id_limit" + " 1 (Invalid)]"); + params.active_connection_id_limit.set_value(0); + EXPECT_FALSE(params.AreValid(&error_details)); + EXPECT_EQ(error_details, + "Invalid transport parameters [Client active_connection_id_limit" + " 0 (Invalid)]"); + } +} + +TEST_P(TransportParametersTest, NoClientParamsWithStatelessResetToken) { + TransportParameters orig_params; + orig_params.perspective = Perspective::IS_CLIENT; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + orig_params.stateless_reset_token = CreateStatelessResetTokenForTest(); + orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + + std::vector<uint8_t> out; + bool ok = true; + EXPECT_QUIC_BUG( + ok = SerializeTransportParameters(version_, orig_params, &out), + "Not serializing invalid transport parameters: Client cannot send " + "stateless reset token"); + EXPECT_FALSE(ok); +} + +TEST_P(TransportParametersTest, ParseClientParams) { + // clang-format off + const uint8_t kClientParams[] = { + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // max_udp_payload_size + 0x03, // parameter id + 0x02, // length + 0x63, 0x29, // value + // initial_max_data + 0x04, // parameter id + 0x02, // length + 0x40, 0x65, // value + // initial_max_stream_data_bidi_local + 0x05, // parameter id + 0x02, // length + 0x47, 0xD1, // value + // initial_max_stream_data_bidi_remote + 0x06, // parameter id + 0x02, // length + 0x47, 0xD2, // value + // initial_max_stream_data_uni + 0x07, // parameter id + 0x02, // length + 0x4B, 0xB8, // value + // initial_max_streams_bidi + 0x08, // parameter id + 0x01, // length + 0x15, // value + // initial_max_streams_uni + 0x09, // parameter id + 0x01, // length + 0x16, // value + // ack_delay_exponent + 0x0a, // parameter id + 0x01, // length + 0x0a, // value + // max_ack_delay + 0x0b, // parameter id + 0x01, // length + 0x33, // value + // min_ack_delay_us + 0x80, 0x00, 0xde, 0x1a, // parameter id + 0x02, // length + 0x43, 0xe8, // value + // disable_active_migration + 0x0c, // parameter id + 0x00, // length + // active_connection_id_limit + 0x0e, // parameter id + 0x01, // length + 0x34, // value + // initial_source_connection_id + 0x0f, // parameter id + 0x08, // length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + // initial_round_trip_time_us + 0x71, 0x27, // parameter id + 0x01, // length + 0x35, // value + // google_connection_options + 0x71, 0x28, // parameter id + 0x0c, // length + 'A', 'L', 'P', 'N', // value + 'E', 'F', 'G', 0x00, + 'H', 'I', 'J', 0xff, + // Google version extension + 0x80, 0x00, 0x47, 0x52, // parameter id + 0x04, // length + 0x01, 0x23, 0x45, 0x67, // initial version + // version_information + 0x80, 0xFF, 0x73, 0xDB, // parameter id + 0x0C, // length + 0x01, 0x23, 0x45, 0x67, // chosen version + 0x01, 0x23, 0x45, 0x67, // other version 1 + 0x89, 0xab, 0xcd, 0xef, // other version 2 + }; + // clang-format on + const uint8_t* client_params = + reinterpret_cast<const uint8_t*>(kClientParams); + size_t client_params_length = ABSL_ARRAYSIZE(kClientParams); + TransportParameters new_params; + std::string error_details; + ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_CLIENT, + client_params, client_params_length, + &new_params, &error_details)) + << error_details; + EXPECT_TRUE(error_details.empty()); + EXPECT_EQ(Perspective::IS_CLIENT, new_params.perspective); + ASSERT_TRUE(new_params.legacy_version_information.has_value()); + EXPECT_EQ(kFakeVersionLabel, + new_params.legacy_version_information.value().version); + EXPECT_TRUE( + new_params.legacy_version_information.value().supported_versions.empty()); + ASSERT_TRUE(new_params.version_information.has_value()); + EXPECT_EQ(new_params.version_information.value(), + CreateFakeVersionInformation()); + EXPECT_FALSE(new_params.original_destination_connection_id.has_value()); + EXPECT_EQ(kFakeIdleTimeoutMilliseconds, + new_params.max_idle_timeout_ms.value()); + EXPECT_TRUE(new_params.stateless_reset_token.empty()); + EXPECT_EQ(kMaxPacketSizeForTest, new_params.max_udp_payload_size.value()); + EXPECT_EQ(kFakeInitialMaxData, new_params.initial_max_data.value()); + EXPECT_EQ(kFakeInitialMaxStreamDataBidiLocal, + new_params.initial_max_stream_data_bidi_local.value()); + EXPECT_EQ(kFakeInitialMaxStreamDataBidiRemote, + new_params.initial_max_stream_data_bidi_remote.value()); + EXPECT_EQ(kFakeInitialMaxStreamDataUni, + new_params.initial_max_stream_data_uni.value()); + EXPECT_EQ(kFakeInitialMaxStreamsBidi, + new_params.initial_max_streams_bidi.value()); + EXPECT_EQ(kFakeInitialMaxStreamsUni, + new_params.initial_max_streams_uni.value()); + EXPECT_EQ(kAckDelayExponentForTest, new_params.ack_delay_exponent.value()); + EXPECT_EQ(kMaxAckDelayForTest, new_params.max_ack_delay.value()); + EXPECT_EQ(kMinAckDelayUsForTest, new_params.min_ack_delay_us.value()); + EXPECT_EQ(kFakeDisableMigration, new_params.disable_active_migration); + EXPECT_EQ(kActiveConnectionIdLimitForTest, + new_params.active_connection_id_limit.value()); + ASSERT_TRUE(new_params.initial_source_connection_id.has_value()); + EXPECT_EQ(CreateFakeInitialSourceConnectionId(), + new_params.initial_source_connection_id.value()); + EXPECT_FALSE(new_params.retry_source_connection_id.has_value()); + EXPECT_EQ(kFakeInitialRoundTripTime, + new_params.initial_round_trip_time_us.value()); + ASSERT_TRUE(new_params.google_connection_options.has_value()); + EXPECT_EQ(CreateFakeGoogleConnectionOptions(), + new_params.google_connection_options.value()); +} + +TEST_P(TransportParametersTest, + ParseClientParamsFailsWithFullStatelessResetToken) { + // clang-format off + const uint8_t kClientParamsWithFullToken[] = { + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // stateless_reset_token + 0x02, // parameter id + 0x10, // length + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + // max_udp_payload_size + 0x03, // parameter id + 0x02, // length + 0x63, 0x29, // value + // initial_max_data + 0x04, // parameter id + 0x02, // length + 0x40, 0x65, // value + }; + // clang-format on + const uint8_t* client_params = + reinterpret_cast<const uint8_t*>(kClientParamsWithFullToken); + size_t client_params_length = ABSL_ARRAYSIZE(kClientParamsWithFullToken); + TransportParameters out_params; + std::string error_details; + EXPECT_FALSE(ParseTransportParameters(version_, Perspective::IS_CLIENT, + client_params, client_params_length, + &out_params, &error_details)); + EXPECT_EQ(error_details, "Client cannot send stateless reset token"); +} + +TEST_P(TransportParametersTest, + ParseClientParamsFailsWithEmptyStatelessResetToken) { + // clang-format off + const uint8_t kClientParamsWithEmptyToken[] = { + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // stateless_reset_token + 0x02, // parameter id + 0x00, // length + // max_udp_payload_size + 0x03, // parameter id + 0x02, // length + 0x63, 0x29, // value + // initial_max_data + 0x04, // parameter id + 0x02, // length + 0x40, 0x65, // value + }; + // clang-format on + const uint8_t* client_params = + reinterpret_cast<const uint8_t*>(kClientParamsWithEmptyToken); + size_t client_params_length = ABSL_ARRAYSIZE(kClientParamsWithEmptyToken); + TransportParameters out_params; + std::string error_details; + EXPECT_FALSE(ParseTransportParameters(version_, Perspective::IS_CLIENT, + client_params, client_params_length, + &out_params, &error_details)); + EXPECT_EQ(error_details, + "Received stateless_reset_token of invalid length 0"); +} + +TEST_P(TransportParametersTest, ParseClientParametersRepeated) { + // clang-format off + const uint8_t kClientParamsRepeated[] = { + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // max_udp_payload_size + 0x03, // parameter id + 0x02, // length + 0x63, 0x29, // value + // max_idle_timeout (repeated) + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + }; + // clang-format on + const uint8_t* client_params = + reinterpret_cast<const uint8_t*>(kClientParamsRepeated); + size_t client_params_length = ABSL_ARRAYSIZE(kClientParamsRepeated); + TransportParameters out_params; + std::string error_details; + EXPECT_FALSE(ParseTransportParameters(version_, Perspective::IS_CLIENT, + client_params, client_params_length, + &out_params, &error_details)); + EXPECT_EQ(error_details, "Received a second max_idle_timeout"); +} + +TEST_P(TransportParametersTest, ParseServerParams) { + // clang-format off + const uint8_t kServerParams[] = { + // original_destination_connection_id + 0x00, // parameter id + 0x08, // length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x37, + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // stateless_reset_token + 0x02, // parameter id + 0x10, // length + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + // max_udp_payload_size + 0x03, // parameter id + 0x02, // length + 0x63, 0x29, // value + // initial_max_data + 0x04, // parameter id + 0x02, // length + 0x40, 0x65, // value + // initial_max_stream_data_bidi_local + 0x05, // parameter id + 0x02, // length + 0x47, 0xD1, // value + // initial_max_stream_data_bidi_remote + 0x06, // parameter id + 0x02, // length + 0x47, 0xD2, // value + // initial_max_stream_data_uni + 0x07, // parameter id + 0x02, // length + 0x4B, 0xB8, // value + // initial_max_streams_bidi + 0x08, // parameter id + 0x01, // length + 0x15, // value + // initial_max_streams_uni + 0x09, // parameter id + 0x01, // length + 0x16, // value + // ack_delay_exponent + 0x0a, // parameter id + 0x01, // length + 0x0a, // value + // max_ack_delay + 0x0b, // parameter id + 0x01, // length + 0x33, // value + // min_ack_delay_us + 0x80, 0x00, 0xde, 0x1a, // parameter id + 0x02, // length + 0x43, 0xe8, // value + // disable_active_migration + 0x0c, // parameter id + 0x00, // length + // preferred_address + 0x0d, // parameter id + 0x31, // length + 0x41, 0x42, 0x43, 0x44, // IPv4 address + 0x48, 0x84, // IPv4 port + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // IPv6 address + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x63, 0x36, // IPv6 port + 0x08, // connection ID length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xEF, // connection ID + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, // stateless reset token + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + // active_connection_id_limit + 0x0e, // parameter id + 0x01, // length + 0x34, // value + // initial_source_connection_id + 0x0f, // parameter id + 0x08, // length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + // retry_source_connection_id + 0x10, // parameter id + 0x08, // length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x76, + // google_connection_options + 0x71, 0x28, // parameter id + 0x0c, // length + 'A', 'L', 'P', 'N', // value + 'E', 'F', 'G', 0x00, + 'H', 'I', 'J', 0xff, + // Google version extension + 0x80, 0x00, 0x47, 0x52, // parameter id + 0x0d, // length + 0x01, 0x23, 0x45, 0x67, // negotiated_version + 0x08, // length of supported versions array + 0x01, 0x23, 0x45, 0x67, + 0x89, 0xab, 0xcd, 0xef, + // version_information + 0x80, 0xFF, 0x73, 0xDB, // parameter id + 0x0C, // length + 0x01, 0x23, 0x45, 0x67, // chosen version + 0x01, 0x23, 0x45, 0x67, // other version 1 + 0x89, 0xab, 0xcd, 0xef, // other version 2 + }; + // clang-format on + const uint8_t* server_params = + reinterpret_cast<const uint8_t*>(kServerParams); + size_t server_params_length = ABSL_ARRAYSIZE(kServerParams); + TransportParameters new_params; + std::string error_details; + ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_SERVER, + server_params, server_params_length, + &new_params, &error_details)) + << error_details; + EXPECT_TRUE(error_details.empty()); + EXPECT_EQ(Perspective::IS_SERVER, new_params.perspective); + ASSERT_TRUE(new_params.legacy_version_information.has_value()); + EXPECT_EQ(kFakeVersionLabel, + new_params.legacy_version_information.value().version); + ASSERT_EQ( + 2u, + new_params.legacy_version_information.value().supported_versions.size()); + EXPECT_EQ( + kFakeVersionLabel, + new_params.legacy_version_information.value().supported_versions[0]); + EXPECT_EQ( + kFakeVersionLabel2, + new_params.legacy_version_information.value().supported_versions[1]); + ASSERT_TRUE(new_params.version_information.has_value()); + EXPECT_EQ(new_params.version_information.value(), + CreateFakeVersionInformation()); + ASSERT_TRUE(new_params.original_destination_connection_id.has_value()); + EXPECT_EQ(CreateFakeOriginalDestinationConnectionId(), + new_params.original_destination_connection_id.value()); + EXPECT_EQ(kFakeIdleTimeoutMilliseconds, + new_params.max_idle_timeout_ms.value()); + EXPECT_EQ(CreateStatelessResetTokenForTest(), + new_params.stateless_reset_token); + EXPECT_EQ(kMaxPacketSizeForTest, new_params.max_udp_payload_size.value()); + EXPECT_EQ(kFakeInitialMaxData, new_params.initial_max_data.value()); + EXPECT_EQ(kFakeInitialMaxStreamDataBidiLocal, + new_params.initial_max_stream_data_bidi_local.value()); + EXPECT_EQ(kFakeInitialMaxStreamDataBidiRemote, + new_params.initial_max_stream_data_bidi_remote.value()); + EXPECT_EQ(kFakeInitialMaxStreamDataUni, + new_params.initial_max_stream_data_uni.value()); + EXPECT_EQ(kFakeInitialMaxStreamsBidi, + new_params.initial_max_streams_bidi.value()); + EXPECT_EQ(kFakeInitialMaxStreamsUni, + new_params.initial_max_streams_uni.value()); + EXPECT_EQ(kAckDelayExponentForTest, new_params.ack_delay_exponent.value()); + EXPECT_EQ(kMaxAckDelayForTest, new_params.max_ack_delay.value()); + EXPECT_EQ(kMinAckDelayUsForTest, new_params.min_ack_delay_us.value()); + EXPECT_EQ(kFakeDisableMigration, new_params.disable_active_migration); + ASSERT_NE(nullptr, new_params.preferred_address.get()); + EXPECT_EQ(CreateFakeV4SocketAddress(), + new_params.preferred_address->ipv4_socket_address); + EXPECT_EQ(CreateFakeV6SocketAddress(), + new_params.preferred_address->ipv6_socket_address); + EXPECT_EQ(CreateFakePreferredConnectionId(), + new_params.preferred_address->connection_id); + EXPECT_EQ(CreateFakePreferredStatelessResetToken(), + new_params.preferred_address->stateless_reset_token); + EXPECT_EQ(kActiveConnectionIdLimitForTest, + new_params.active_connection_id_limit.value()); + ASSERT_TRUE(new_params.initial_source_connection_id.has_value()); + EXPECT_EQ(CreateFakeInitialSourceConnectionId(), + new_params.initial_source_connection_id.value()); + ASSERT_TRUE(new_params.retry_source_connection_id.has_value()); + EXPECT_EQ(CreateFakeRetrySourceConnectionId(), + new_params.retry_source_connection_id.value()); + ASSERT_TRUE(new_params.google_connection_options.has_value()); + EXPECT_EQ(CreateFakeGoogleConnectionOptions(), + new_params.google_connection_options.value()); +} + +TEST_P(TransportParametersTest, ParseServerParametersRepeated) { + // clang-format off + const uint8_t kServerParamsRepeated[] = { + // original_destination_connection_id + 0x00, // parameter id + 0x08, // length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x37, + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // stateless_reset_token + 0x02, // parameter id + 0x10, // length + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + // max_idle_timeout (repeated) + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + }; + // clang-format on + const uint8_t* server_params = + reinterpret_cast<const uint8_t*>(kServerParamsRepeated); + size_t server_params_length = ABSL_ARRAYSIZE(kServerParamsRepeated); + TransportParameters out_params; + std::string error_details; + EXPECT_FALSE(ParseTransportParameters(version_, Perspective::IS_SERVER, + server_params, server_params_length, + &out_params, &error_details)); + EXPECT_EQ(error_details, "Received a second max_idle_timeout"); +} + +TEST_P(TransportParametersTest, + ParseServerParametersEmptyOriginalConnectionId) { + // clang-format off + const uint8_t kServerParamsEmptyOriginalConnectionId[] = { + // original_destination_connection_id + 0x00, // parameter id + 0x00, // length + // max_idle_timeout + 0x01, // parameter id + 0x02, // length + 0x6e, 0xec, // value + // stateless_reset_token + 0x02, // parameter id + 0x10, // length + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + }; + // clang-format on + const uint8_t* server_params = + reinterpret_cast<const uint8_t*>(kServerParamsEmptyOriginalConnectionId); + size_t server_params_length = + ABSL_ARRAYSIZE(kServerParamsEmptyOriginalConnectionId); + TransportParameters out_params; + std::string error_details; + ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_SERVER, + server_params, server_params_length, + &out_params, &error_details)) + << error_details; + ASSERT_TRUE(out_params.original_destination_connection_id.has_value()); + EXPECT_EQ(out_params.original_destination_connection_id.value(), + EmptyQuicConnectionId()); +} + +TEST_P(TransportParametersTest, VeryLongCustomParameter) { + // Ensure we can handle a 70KB custom parameter on both send and receive. + std::string custom_value(70000, '?'); + TransportParameters orig_params; + orig_params.perspective = Perspective::IS_CLIENT; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.custom_parameters[kCustomParameter1] = custom_value; + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParameters(version_, orig_params, &serialized)); + + TransportParameters new_params; + std::string error_details; + ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_CLIENT, + serialized.data(), serialized.size(), + &new_params, &error_details)) + << error_details; + EXPECT_TRUE(error_details.empty()); + RemoveGreaseParameters(&new_params); + EXPECT_EQ(new_params, orig_params); +} + +TEST_P(TransportParametersTest, SerializationOrderIsRandom) { + TransportParameters orig_params; + orig_params.perspective = Perspective::IS_CLIENT; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); + orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + orig_params.initial_max_data.set_value(kFakeInitialMaxData); + orig_params.initial_max_stream_data_bidi_local.set_value( + kFakeInitialMaxStreamDataBidiLocal); + orig_params.initial_max_stream_data_bidi_remote.set_value( + kFakeInitialMaxStreamDataBidiRemote); + orig_params.initial_max_stream_data_uni.set_value( + kFakeInitialMaxStreamDataUni); + orig_params.initial_max_streams_bidi.set_value(kFakeInitialMaxStreamsBidi); + orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni); + orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest); + orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); + orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); + orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.active_connection_id_limit.set_value( + kActiveConnectionIdLimitForTest); + orig_params.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); + orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; + orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; + + std::vector<uint8_t> first_serialized; + ASSERT_TRUE( + SerializeTransportParameters(version_, orig_params, &first_serialized)); + // Test that a subsequent serialization is different from the first. + // Run in a loop to avoid a failure in the unlikely event that randomization + // produces the same result multiple times. + for (int i = 0; i < 1000; i++) { + std::vector<uint8_t> serialized; + ASSERT_TRUE( + SerializeTransportParameters(version_, orig_params, &serialized)); + if (serialized != first_serialized) { + return; + } + } +} + +class TransportParametersTicketSerializationTest : public QuicTest { + protected: + void SetUp() override { + original_params_.perspective = Perspective::IS_SERVER; + original_params_.legacy_version_information = + CreateFakeLegacyVersionInformationServer(); + original_params_.original_destination_connection_id = + CreateFakeOriginalDestinationConnectionId(); + original_params_.max_idle_timeout_ms.set_value( + kFakeIdleTimeoutMilliseconds); + original_params_.stateless_reset_token = CreateStatelessResetTokenForTest(); + original_params_.max_udp_payload_size.set_value(kMaxPacketSizeForTest); + original_params_.initial_max_data.set_value(kFakeInitialMaxData); + original_params_.initial_max_stream_data_bidi_local.set_value( + kFakeInitialMaxStreamDataBidiLocal); + original_params_.initial_max_stream_data_bidi_remote.set_value( + kFakeInitialMaxStreamDataBidiRemote); + original_params_.initial_max_stream_data_uni.set_value( + kFakeInitialMaxStreamDataUni); + original_params_.initial_max_streams_bidi.set_value( + kFakeInitialMaxStreamsBidi); + original_params_.initial_max_streams_uni.set_value( + kFakeInitialMaxStreamsUni); + original_params_.ack_delay_exponent.set_value(kAckDelayExponentForTest); + original_params_.max_ack_delay.set_value(kMaxAckDelayForTest); + original_params_.min_ack_delay_us.set_value(kMinAckDelayUsForTest); + original_params_.disable_active_migration = kFakeDisableMigration; + original_params_.preferred_address = CreateFakePreferredAddress(); + original_params_.active_connection_id_limit.set_value( + kActiveConnectionIdLimitForTest); + original_params_.initial_source_connection_id = + CreateFakeInitialSourceConnectionId(); + original_params_.retry_source_connection_id = + CreateFakeRetrySourceConnectionId(); + original_params_.google_connection_options = + CreateFakeGoogleConnectionOptions(); + + ASSERT_TRUE(SerializeTransportParametersForTicket( + original_params_, application_state_, &original_serialized_params_)); + } + + TransportParameters original_params_; + std::vector<uint8_t> application_state_ = {0, 1}; + std::vector<uint8_t> original_serialized_params_; +}; + +TEST_F(TransportParametersTicketSerializationTest, + StatelessResetTokenDoesntChangeOutput) { + // Test that changing the stateless reset token doesn't change the ticket + // serialization. + TransportParameters new_params = original_params_; + new_params.stateless_reset_token = CreateFakePreferredStatelessResetToken(); + EXPECT_NE(new_params, original_params_); + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParametersForTicket( + new_params, application_state_, &serialized)); + EXPECT_EQ(original_serialized_params_, serialized); +} + +TEST_F(TransportParametersTicketSerializationTest, + ConnectionIDDoesntChangeOutput) { + // Changing original destination CID doesn't change serialization. + TransportParameters new_params = original_params_; + new_params.original_destination_connection_id = TestConnectionId(0xCAFE); + EXPECT_NE(new_params, original_params_); + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParametersForTicket( + new_params, application_state_, &serialized)); + EXPECT_EQ(original_serialized_params_, serialized); +} + +TEST_F(TransportParametersTicketSerializationTest, StreamLimitChangesOutput) { + // Changing a stream limit does change the serialization. + TransportParameters new_params = original_params_; + new_params.initial_max_stream_data_bidi_local.set_value( + kFakeInitialMaxStreamDataBidiLocal + 1); + EXPECT_NE(new_params, original_params_); + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParametersForTicket( + new_params, application_state_, &serialized)); + EXPECT_NE(original_serialized_params_, serialized); +} + +TEST_F(TransportParametersTicketSerializationTest, + ApplicationStateChangesOutput) { + // Changing the application state changes the serialization. + std::vector<uint8_t> new_application_state = {0}; + EXPECT_NE(new_application_state, application_state_); + + std::vector<uint8_t> serialized; + ASSERT_TRUE(SerializeTransportParametersForTicket( + original_params_, new_application_state, &serialized)); + EXPECT_NE(original_serialized_params_, serialized); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.cc b/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.cc new file mode 100644 index 0000000..1353137 --- /dev/null +++ b/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.cc
@@ -0,0 +1,229 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h" + +#include <cstdint> +#include <memory> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/sha.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/common/quiche_text_utils.h" + +namespace quic { +namespace { + +constexpr size_t kFingerprintLength = SHA256_DIGEST_LENGTH * 3 - 1; + +// Assumes that the character is normalized to lowercase beforehand. +bool IsNormalizedHexDigit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); +} + +void NormalizeFingerprint(CertificateFingerprint& fingerprint) { + fingerprint.fingerprint = + quiche::QuicheTextUtils::ToLower(fingerprint.fingerprint); +} + +} // namespace + +constexpr char CertificateFingerprint::kSha256[]; +constexpr char WebTransportHash::kSha256[]; + +ProofVerifyDetails* WebTransportFingerprintProofVerifier::Details::Clone() + const { + return new Details(*this); +} + +WebTransportFingerprintProofVerifier::WebTransportFingerprintProofVerifier( + const QuicClock* clock, int max_validity_days) + : clock_(clock), + max_validity_days_(max_validity_days), + // Add an extra second to max validity to accomodate various edge cases. + max_validity_( + QuicTime::Delta::FromSeconds(max_validity_days * 86400 + 1)) {} + +bool WebTransportFingerprintProofVerifier::AddFingerprint( + CertificateFingerprint fingerprint) { + NormalizeFingerprint(fingerprint); + if (fingerprint.algorithm != CertificateFingerprint::kSha256) { + QUIC_DLOG(WARNING) << "Algorithms other than SHA-256 are not supported"; + return false; + } + if (fingerprint.fingerprint.size() != kFingerprintLength) { + QUIC_DLOG(WARNING) << "Invalid fingerprint length"; + return false; + } + for (size_t i = 0; i < fingerprint.fingerprint.size(); i++) { + char current = fingerprint.fingerprint[i]; + if (i % 3 == 2) { + if (current != ':') { + QUIC_DLOG(WARNING) + << "Missing colon separator between the bytes of the hash"; + return false; + } + } else { + if (!IsNormalizedHexDigit(current)) { + QUIC_DLOG(WARNING) << "Fingerprint must be in hexadecimal"; + return false; + } + } + } + + std::string normalized = + absl::StrReplaceAll(fingerprint.fingerprint, {{":", ""}}); + hashes_.push_back(WebTransportHash{fingerprint.algorithm, + absl::HexStringToBytes(normalized)}); + return true; +} + +bool WebTransportFingerprintProofVerifier::AddFingerprint( + WebTransportHash hash) { + if (hash.algorithm != CertificateFingerprint::kSha256) { + QUIC_DLOG(WARNING) << "Algorithms other than SHA-256 are not supported"; + return false; + } + if (hash.value.size() != SHA256_DIGEST_LENGTH) { + QUIC_DLOG(WARNING) << "Invalid fingerprint length"; + return false; + } + hashes_.push_back(std::move(hash)); + return true; +} + +QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyProof( + const std::string& /*hostname*/, const uint16_t /*port*/, + const std::string& /*server_config*/, + QuicTransportVersion /*transport_version*/, absl::string_view /*chlo_hash*/, + const std::vector<std::string>& /*certs*/, const std::string& /*cert_sct*/, + const std::string& /*signature*/, const ProofVerifyContext* /*context*/, + std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details, + std::unique_ptr<ProofVerifierCallback> /*callback*/) { + *error_details = + "QUIC crypto certificate verification is not supported in " + "WebTransportFingerprintProofVerifier"; + QUIC_BUG(quic_bug_10879_1) << *error_details; + *details = std::make_unique<Details>(Status::kInternalError); + return QUIC_FAILURE; +} + +QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyCertChain( + const std::string& /*hostname*/, const uint16_t /*port*/, + const std::vector<std::string>& certs, const std::string& /*ocsp_response*/, + const std::string& /*cert_sct*/, const ProofVerifyContext* /*context*/, + std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details, + uint8_t* /*out_alert*/, + std::unique_ptr<ProofVerifierCallback> /*callback*/) { + if (certs.empty()) { + *details = std::make_unique<Details>(Status::kInternalError); + *error_details = "No certificates provided"; + return QUIC_FAILURE; + } + + if (!HasKnownFingerprint(certs[0])) { + *details = std::make_unique<Details>(Status::kUnknownFingerprint); + *error_details = "Certificate does not match any fingerprint"; + return QUIC_FAILURE; + } + + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(certs[0]); + if (view == nullptr) { + *details = std::make_unique<Details>(Status::kCertificateParseFailure); + *error_details = "Failed to parse the certificate"; + return QUIC_FAILURE; + } + + if (!HasValidExpiry(*view)) { + *details = std::make_unique<Details>(Status::kExpiryTooLong); + *error_details = + absl::StrCat("Certificate expiry exceeds the configured limit of ", + max_validity_days_, " days"); + return QUIC_FAILURE; + } + + if (!IsWithinValidityPeriod(*view)) { + *details = std::make_unique<Details>(Status::kExpired); + *error_details = + "Certificate has expired or has validity listed in the future"; + return QUIC_FAILURE; + } + + if (!IsKeyTypeAllowedByPolicy(*view)) { + *details = std::make_unique<Details>(Status::kDisallowedKeyAlgorithm); + *error_details = + absl::StrCat("Certificate uses a disallowed public key type (", + PublicKeyTypeToString(view->public_key_type()), ")"); + return QUIC_FAILURE; + } + + *details = std::make_unique<Details>(Status::kValidCertificate); + return QUIC_SUCCESS; +} + +std::unique_ptr<ProofVerifyContext> +WebTransportFingerprintProofVerifier::CreateDefaultContext() { + return nullptr; +} + +bool WebTransportFingerprintProofVerifier::HasKnownFingerprint( + absl::string_view der_certificate) { + // https://w3c.github.io/webtransport/#verify-a-certificate-hash + const std::string hash = RawSha256(der_certificate); + for (const WebTransportHash& reference : hashes_) { + if (reference.algorithm != WebTransportHash::kSha256) { + QUIC_BUG(quic_bug_10879_2) << "Unexpected non-SHA-256 hash"; + continue; + } + if (hash == reference.value) { + return true; + } + } + return false; +} + +bool WebTransportFingerprintProofVerifier::HasValidExpiry( + const CertificateView& certificate) { + if (!certificate.validity_start().IsBefore(certificate.validity_end())) { + return false; + } + + const QuicTime::Delta duration_seconds = + certificate.validity_end() - certificate.validity_start(); + return duration_seconds <= max_validity_; +} + +bool WebTransportFingerprintProofVerifier::IsWithinValidityPeriod( + const CertificateView& certificate) { + QuicWallTime now = clock_->WallNow(); + return now.IsAfter(certificate.validity_start()) && + now.IsBefore(certificate.validity_end()); +} + +bool WebTransportFingerprintProofVerifier::IsKeyTypeAllowedByPolicy( + const CertificateView& certificate) { + switch (certificate.public_key_type()) { + // https://github.com/w3c/webtransport/pull/375 defines P-256 as an MTI + // algorithm, and prohibits RSA. We also allow P-384 and Ed25519. + case PublicKeyType::kP256: + case PublicKeyType::kP384: + case PublicKeyType::kEd25519: + return true; + case PublicKeyType::kRsa: + // TODO(b/213614428): this should be false by default. + return true; + default: + return false; + } +} + +} // namespace quic
diff --git a/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h b/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h new file mode 100644 index 0000000..87ecf2a --- /dev/null +++ b/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h
@@ -0,0 +1,135 @@ +// Copyright 2020 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. + +#ifndef QUICHE_QUIC_QUIC_TRANSPORT_FINGERPRINT_PROOF_VERIFIER_H_ +#define QUICHE_QUIC_QUIC_TRANSPORT_FINGERPRINT_PROOF_VERIFIER_H_ + +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/quic_clock.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +// Represents a fingerprint of an X.509 certificate in a format based on +// https://w3c.github.io/webrtc-pc/#dom-rtcdtlsfingerprint. +// TODO(vasilvv): remove this once all consumers of this API use +// WebTransportHash. +struct QUIC_EXPORT_PRIVATE CertificateFingerprint { + static constexpr char kSha256[] = "sha-256"; + + // An algorithm described by one of the names in + // https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml + std::string algorithm; + // Hex-encoded, colon-separated fingerprint of the certificate. For example, + // "12:3d:5b:71:8c:54:df:85:7e:bd:e3:7c:66:da:f9:db:6a:94:8f:85:cb:6e:44:7f:09:3e:05:f2:dd:d4:f7:86" + std::string fingerprint; +}; + +// Represents a fingerprint of an X.509 certificate in a format based on +// https://w3c.github.io/webtransport/#dictdef-webtransporthash. +struct QUIC_EXPORT_PRIVATE WebTransportHash { + static constexpr char kSha256[] = "sha-256"; + + // An algorithm described by one of the names in + // https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml + std::string algorithm; + // Raw bytes of the hash. + std::string value; +}; + +// WebTransportFingerprintProofVerifier verifies the server leaf certificate +// against a supplied list of certificate fingerprints following the procedure +// described in the WebTransport specification. The certificate is deemed +// trusted if it matches a fingerprint in the list, has expiry dates that are +// not too long and has not expired. Only the leaf is checked, the rest of the +// chain is ignored. Reference specification: +// https://wicg.github.io/web-transport/#dom-quictransportconfiguration-server_certificate_fingerprints +class QUIC_EXPORT_PRIVATE WebTransportFingerprintProofVerifier + : public ProofVerifier { + public: + // Note: the entries in this list may be logged into a UMA histogram, and thus + // should not be renumbered. + enum class Status { + kValidCertificate = 0, + kUnknownFingerprint = 1, + kCertificateParseFailure = 2, + kExpiryTooLong = 3, + kExpired = 4, + kInternalError = 5, + kDisallowedKeyAlgorithm = 6, + + kMaxValue = kDisallowedKeyAlgorithm, + }; + + class QUIC_EXPORT_PRIVATE Details : public ProofVerifyDetails { + public: + explicit Details(Status status) : status_(status) {} + Status status() const { return status_; } + + ProofVerifyDetails* Clone() const override; + + private: + const Status status_; + }; + + // |clock| is used to check if the certificate has expired. It is not owned + // and must outlive the object. |max_validity_days| is the maximum time for + // which the certificate is allowed to be valid. + WebTransportFingerprintProofVerifier(const QuicClock* clock, + int max_validity_days); + + // Adds a certificate fingerprint to be trusted. The fingerprints are + // case-insensitive and are validated internally; the function returns true if + // the validation passes. + bool AddFingerprint(CertificateFingerprint fingerprint); + bool AddFingerprint(WebTransportHash hash); + + // ProofVerifier implementation. + QuicAsyncStatus VerifyProof( + const std::string& hostname, + const uint16_t port, + const std::string& server_config, + QuicTransportVersion transport_version, + absl::string_view chlo_hash, + const std::vector<std::string>& certs, + const std::string& cert_sct, + const std::string& signature, + const ProofVerifyContext* context, + std::string* error_details, + std::unique_ptr<ProofVerifyDetails>* details, + std::unique_ptr<ProofVerifierCallback> callback) override; + QuicAsyncStatus VerifyCertChain( + const std::string& hostname, + const uint16_t port, + const std::vector<std::string>& certs, + const std::string& ocsp_response, + const std::string& cert_sct, + const ProofVerifyContext* context, + std::string* error_details, + std::unique_ptr<ProofVerifyDetails>* details, + uint8_t* out_alert, + std::unique_ptr<ProofVerifierCallback> callback) override; + std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override; + + protected: + virtual bool IsKeyTypeAllowedByPolicy(const CertificateView& certificate); + + private: + bool HasKnownFingerprint(absl::string_view der_certificate); + bool HasValidExpiry(const CertificateView& certificate); + bool IsWithinValidityPeriod(const CertificateView& certificate); + + const QuicClock* clock_; // Unowned. + const int max_validity_days_; + const QuicTime::Delta max_validity_; + std::vector<WebTransportHash> hashes_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUIC_TRANSPORT_FINGERPRINT_PROOF_VERIFIER_H_
diff --git a/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc b/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc new file mode 100644 index 0000000..11c769d --- /dev/null +++ b/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc
@@ -0,0 +1,183 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h" + +#include <memory> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/mock_clock.h" +#include "quiche/quic/test_tools/test_certificates.h" + +namespace quic { +namespace test { +namespace { + +using ::testing::HasSubstr; + +// 2020-02-01 12:35:56 UTC +constexpr QuicTime::Delta kValidTime = QuicTime::Delta::FromSeconds(1580560556); + +struct VerifyResult { + QuicAsyncStatus status; + WebTransportFingerprintProofVerifier::Status detailed_status; + std::string error; +}; + +class WebTransportFingerprintProofVerifierTest : public QuicTest { + public: + WebTransportFingerprintProofVerifierTest() { + clock_.AdvanceTime(kValidTime); + verifier_ = std::make_unique<WebTransportFingerprintProofVerifier>( + &clock_, /*max_validity_days=*/365); + AddTestCertificate(); + } + + protected: + VerifyResult Verify(absl::string_view certificate) { + VerifyResult result; + std::unique_ptr<ProofVerifyDetails> details; + uint8_t tls_alert; + result.status = verifier_->VerifyCertChain( + /*hostname=*/"", /*port=*/0, + std::vector<std::string>{std::string(certificate)}, + /*ocsp_response=*/"", + /*cert_sct=*/"", + /*context=*/nullptr, &result.error, &details, &tls_alert, + /*callback=*/nullptr); + result.detailed_status = + static_cast<WebTransportFingerprintProofVerifier::Details*>( + details.get()) + ->status(); + return result; + } + + void AddTestCertificate() { + EXPECT_TRUE(verifier_->AddFingerprint(WebTransportHash{ + WebTransportHash::kSha256, RawSha256(kTestCertificate)})); + } + + MockClock clock_; + std::unique_ptr<WebTransportFingerprintProofVerifier> verifier_; +}; + +TEST_F(WebTransportFingerprintProofVerifierTest, Sha256Fingerprint) { + // Computed using `openssl x509 -fingerprint -sha256`. + EXPECT_EQ(absl::BytesToHexString(RawSha256(kTestCertificate)), + "f2e5465e2bf7ecd6f63066a5a37511734aa0eb7c4701" + "0e86d6758ed4f4fa1b0f"); +} + +TEST_F(WebTransportFingerprintProofVerifierTest, SimpleFingerprint) { + VerifyResult result = Verify(kTestCertificate); + EXPECT_EQ(result.status, QUIC_SUCCESS); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kValidCertificate); + + result = Verify(kWildcardCertificate); + EXPECT_EQ(result.status, QUIC_FAILURE); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kUnknownFingerprint); + + result = Verify("Some random text"); + EXPECT_EQ(result.status, QUIC_FAILURE); +} + +TEST_F(WebTransportFingerprintProofVerifierTest, Validity) { + // Validity periods of kTestCertificate, according to `openssl x509 -text`: + // Not Before: Jan 30 18:13:59 2020 GMT + // Not After : Feb 2 18:13:59 2020 GMT + + // 2020-01-29 19:00:00 UTC + constexpr QuicTime::Delta kStartTime = + QuicTime::Delta::FromSeconds(1580324400); + clock_.Reset(); + clock_.AdvanceTime(kStartTime); + + VerifyResult result = Verify(kTestCertificate); + EXPECT_EQ(result.status, QUIC_FAILURE); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kExpired); + + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(86400)); + result = Verify(kTestCertificate); + EXPECT_EQ(result.status, QUIC_SUCCESS); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kValidCertificate); + + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(4 * 86400)); + result = Verify(kTestCertificate); + EXPECT_EQ(result.status, QUIC_FAILURE); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kExpired); +} + +TEST_F(WebTransportFingerprintProofVerifierTest, MaxValidity) { + verifier_ = std::make_unique<WebTransportFingerprintProofVerifier>( + &clock_, /*max_validity_days=*/2); + AddTestCertificate(); + VerifyResult result = Verify(kTestCertificate); + EXPECT_EQ(result.status, QUIC_FAILURE); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kExpiryTooLong); + EXPECT_THAT(result.error, HasSubstr("limit of 2 days")); + + // kTestCertificate is valid for exactly four days. + verifier_ = std::make_unique<WebTransportFingerprintProofVerifier>( + &clock_, /*max_validity_days=*/4); + AddTestCertificate(); + result = Verify(kTestCertificate); + EXPECT_EQ(result.status, QUIC_SUCCESS); + EXPECT_EQ(result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kValidCertificate); +} + +TEST_F(WebTransportFingerprintProofVerifierTest, InvalidCertificate) { + constexpr absl::string_view kInvalidCertificate = "Hello, world!"; + ASSERT_TRUE(verifier_->AddFingerprint(WebTransportHash{ + WebTransportHash::kSha256, RawSha256(kInvalidCertificate)})); + + VerifyResult result = Verify(kInvalidCertificate); + EXPECT_EQ(result.status, QUIC_FAILURE); + EXPECT_EQ( + result.detailed_status, + WebTransportFingerprintProofVerifier::Status::kCertificateParseFailure); +} + +TEST_F(WebTransportFingerprintProofVerifierTest, AddCertificate) { + // Accept all-uppercase fingerprints. + verifier_ = std::make_unique<WebTransportFingerprintProofVerifier>( + &clock_, /*max_validity_days=*/365); + EXPECT_TRUE(verifier_->AddFingerprint(CertificateFingerprint{ + CertificateFingerprint::kSha256, + "F2:E5:46:5E:2B:F7:EC:D6:F6:30:66:A5:A3:75:11:73:4A:A0:EB:" + "7C:47:01:0E:86:D6:75:8E:D4:F4:FA:1B:0F"})); + EXPECT_EQ(Verify(kTestCertificate).detailed_status, + WebTransportFingerprintProofVerifier::Status::kValidCertificate); + + // Reject unknown hash algorithms. + EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{ + "sha-1", "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"})); + // Reject invalid length. + EXPECT_FALSE(verifier_->AddFingerprint( + CertificateFingerprint{CertificateFingerprint::kSha256, "00:00:00:00"})); + // Reject missing colons. + EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{ + CertificateFingerprint::kSha256, + "00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00." + "00.00.00.00.00.00.00.00.00.00.00.00.00"})); + // Reject non-hex symbols. + EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{ + CertificateFingerprint::kSha256, + "zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:" + "zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz"})); +} + +} // namespace +} // namespace test +} // namespace quic