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, &not_before, &not_before_tag) ||
+      !CBS_get_any_asn1(&validity, &not_after, &not_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(&param_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(), &parameter_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