Landing Recent QUIC changes until 9/14/2015 19:47 UTC

relnote: Change QuicSpdyServerStream to correctly handle multiple Content-Length values.

Merge internal change: 103019414
https://codereview.chromium.org/1356263002/

relnote: deprecate FLAGS_quic_limit_mtu_by_writer.

Merge internal change: 102991700
https://codereview.chromium.org/1358913002/

relnote: Add XLCT tag to QUIC client hello gated by QUIC_VERSION_26.

In the client hello, the client sends the XLCT tag to indicate what it expects
the server's leaf certificate to be. If present, the server verifies the tag
and rejects the hello if the value in the tag doesn't match.

Merge internal change: 102874973
https://codereview.chromium.org/1358713002/

Review URL: https://codereview.chromium.org/1360843002

Cr-Commit-Position: refs/heads/master@{#350208}
diff --git a/net/quic/crypto/crypto_handshake.cc b/net/quic/crypto/crypto_handshake.cc
index 5a928b6..c790814c 100644
--- a/net/quic/crypto/crypto_handshake.cc
+++ b/net/quic/crypto/crypto_handshake.cc
@@ -14,11 +14,14 @@
 QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters()
     : key_exchange(0),
       aead(0),
-      x509_ecdsa_supported(false) {
-}
+      x509_ecdsa_supported(false),
+      x509_supported(false) {}
 
 QuicCryptoNegotiatedParameters::~QuicCryptoNegotiatedParameters() {}
 
+QuicCryptoProof::QuicCryptoProof() : certs(nullptr) {}
+QuicCryptoProof::~QuicCryptoProof() {}
+
 CrypterPair::CrypterPair() {}
 
 CrypterPair::~CrypterPair() {}
diff --git a/net/quic/crypto/crypto_handshake.h b/net/quic/crypto/crypto_handshake.h
index e888325..74671cae 100644
--- a/net/quic/crypto/crypto_handshake.h
+++ b/net/quic/crypto/crypto_handshake.h
@@ -77,7 +77,10 @@
   // The source-address token has expired.
   SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE = 19,
 
-  MAX_FAILURE_REASON = 21,
+  // 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 and we don't want to set the most
@@ -129,12 +132,22 @@
 
   // Used when generating proof signature when sending server config updates.
   bool x509_ecdsa_supported;
+  bool x509_supported;
 
   // Used to generate cert chain when sending server config updates.
   std::string client_common_set_hashes;
   std::string client_cached_cert_hashes;
 };
 
+struct NET_EXPORT_PRIVATE QuicCryptoProof {
+  QuicCryptoProof();
+  ~QuicCryptoProof();
+
+  std::string signature;
+  // QuicCryptoProof does not take ownership of |certs|.
+  const std::vector<std::string>* certs;
+};
+
 // QuicCryptoConfig contains common configuration between clients and servers.
 class NET_EXPORT_PRIVATE QuicCryptoConfig {
  public:
diff --git a/net/quic/crypto/crypto_protocol.h b/net/quic/crypto/crypto_protocol.h
index a518b003..6d56198c 100644
--- a/net/quic/crypto/crypto_protocol.h
+++ b/net/quic/crypto/crypto_protocol.h
@@ -146,6 +146,7 @@
 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.
 
 // Rejection tags
 const QuicTag kRREJ = TAG('R', 'R', 'E', 'J');   // Reasons for server sending
diff --git a/net/quic/crypto/crypto_server_test.cc b/net/quic/crypto/crypto_server_test.cc
index 9ab24a0a..4ff446b 100644
--- a/net/quic/crypto/crypto_server_test.cc
+++ b/net/quic/crypto/crypto_server_test.cc
@@ -8,7 +8,9 @@
 #include "base/basictypes.h"
 #include "base/strings/string_number_conversions.h"
 #include "crypto/secure_hash.h"
+#include "net/quic/crypto/crypto_handshake.h"
 #include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/proof_source.h"
 #include "net/quic/crypto/quic_crypto_server_config.h"
 #include "net/quic/crypto/quic_random.h"
 #include "net/quic/quic_flags.h"
@@ -216,7 +218,10 @@
 
   void ShouldSucceed(const CryptoHandshakeMessage& message) {
     bool called = false;
-    config_.ValidateClientHello(message, client_address_.address(), &clock_,
+    IPAddressNumber server_ip;
+    config_.ValidateClientHello(message, client_address_.address(), server_ip,
+                                supported_versions_.front(), &clock_,
+                                &crypto_proof_,
                                 new ValidateCallback(this, true, "", &called));
     EXPECT_TRUE(called);
   }
@@ -231,8 +236,10 @@
   void ShouldFailMentioning(const char* error_substr,
                             const CryptoHandshakeMessage& message,
                             bool* called) {
+    IPAddressNumber server_ip;
     config_.ValidateClientHello(
-        message, client_address_.address(), &clock_,
+        message, client_address_.address(), server_ip,
+        supported_versions_.front(), &clock_, &crypto_proof_,
         new ValidateCallback(this, false, error_substr, called));
   }
 
@@ -248,7 +255,7 @@
         result, 1 /* ConnectionId */, server_ip, client_address_,
         supported_versions_.front(), supported_versions_,
         use_stateless_rejects_, server_designated_connection_id, &clock_, rand_,
-        &params_, &out_, &error_details);
+        &params_, &crypto_proof_, &out_, &error_details);
 
     if (should_succeed) {
       ASSERT_EQ(error, QUIC_NO_ERROR) << "Message failed with error "
@@ -322,6 +329,29 @@
            GetParam().use_stateless_rejects;
   }
 
+  string XlctHexString() {
+    const vector<string>* certs;
+    IPAddressNumber server_ip;
+    string sig;
+#if defined(USE_OPENSSL)
+    scoped_ptr<ProofSource> proof_source(
+        CryptoTestUtils::ProofSourceForTesting());
+#else
+    scoped_ptr<ProofSource> proof_source(
+        CryptoTestUtils::FakeProofSourceForTesting());
+#endif
+    if (!proof_source->GetProof(server_ip, "", "", false, &certs, &sig) ||
+        certs->empty()) {
+      return "#0100000000000000";
+    }
+
+    std::ostringstream xlct_stream;
+    uint64 xlct =
+        QuicUtils::FNV1a_64_Hash(certs->at(0).data(), certs->at(0).length());
+
+    return "#" + base::HexEncode(reinterpret_cast<char*>(&xlct), sizeof(xlct));
+  }
+
  protected:
   QuicRandom* const rand_;
   MockRandom rand_for_id_generation_;
@@ -332,6 +362,7 @@
   QuicCryptoServerConfig config_;
   QuicCryptoServerConfig::ConfigOptions config_options_;
   QuicCryptoNegotiatedParameters params_;
+  QuicCryptoProof crypto_proof_;
   CryptoHandshakeMessage out_;
   uint8 orbit_[kOrbitSize];
   bool use_stateless_rejects_;
@@ -394,6 +425,7 @@
       "PUBS", pub_hex_.c_str(),
       "NONC", nonce_hex_.c_str(),
       "PDMD", "X509",
+      "XLCT", XlctHexString().c_str(),
       "VER\0", client_version_.data(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
       nullptr);
@@ -530,6 +562,7 @@
       "#004b5453", (string(1, 'X') + srct_hex_).c_str(),
       "PUBS", pub_hex_.c_str(),
       "NONC", nonce_hex_.c_str(),
+      "XLCT", XlctHexString().c_str(),
       "VER\0", client_version_.data(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
       nullptr);
@@ -552,6 +585,7 @@
       "#004b5453", (string(1, 'X') + srct_hex_).c_str(),
       "PUBS", pub_hex_.c_str(),
       "NONC", (string(1, 'X') + nonce_hex_).c_str(),
+      "XLCT", XlctHexString().c_str(),
       "VER\0", client_version_.data(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
       nullptr);
@@ -575,6 +609,7 @@
       "PUBS", pub_hex_.c_str(),
       "NONC", (string(1, 'X') + nonce_hex_).c_str(),
       "SNO\0", (string(1, 'X') + nonce_hex_).c_str(),
+      "XLCT", XlctHexString().c_str(),
       "VER\0", client_version_.data(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
       nullptr);
@@ -599,6 +634,7 @@
       "#004b5453", srct_hex_.c_str(),
       "PUBS", pub_hex_.c_str(),
       "NONC", nonce_hex_.c_str(),
+      "XLCT", XlctHexString().c_str(),
       "VER\0", client_version_.data(),
       "$padding", static_cast<int>(kClientHelloMinimumSize),
       nullptr);
@@ -625,6 +661,40 @@
   CheckServerHello(out_);
 }
 
+TEST_P(CryptoServerTest, RejectInvalidXlct) {
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO", "AEAD", "AESG", "KEXS", "C255", "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(), "PUBS", pub_hex_.c_str(), "NONC",
+      nonce_hex_.c_str(), "VER\0", client_version_.data(), "XLCT",
+      "#0102030405060708", "$padding",
+      static_cast<int>(kClientHelloMinimumSize), nullptr);
+  // 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, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, ValidXlct) {
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO", "AEAD", "AESG", "KEXS", "C255", "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(), "PUBS", pub_hex_.c_str(), "NONC",
+      nonce_hex_.c_str(), "VER\0", client_version_.data(), "XLCT",
+      XlctHexString().c_str(), "$padding",
+      static_cast<int>(kClientHelloMinimumSize), nullptr);
+  // 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(CryptoServerConfigGenerationTest, Determinism) {
   // Test that using a deterministic PRNG causes the server-config to be
   // deterministic.
@@ -716,6 +786,46 @@
   CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
 }
 
+class CryptoServerTestOldVersion : public CryptoServerTest {
+ public:
+  void SetUp() override {
+    client_version_ = QuicUtils::TagToString(
+        QuicVersionToQuicTag(supported_versions_.back()));
+    CryptoServerTest::SetUp();
+  }
+};
+
+TEST_P(CryptoServerTestOldVersion, ServerIgnoresXlct) {
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO", "AEAD", "AESG", "KEXS", "C255", "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(), "PUBS", pub_hex_.c_str(), "NONC",
+      nonce_hex_.c_str(), "VER\0", client_version_.data(), "XLCT",
+      "#0100000000000000", "$padding",
+      static_cast<int>(kClientHelloMinimumSize), nullptr);
+  // 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 = CryptoTestUtils::Message(
+      "CHLO", "AEAD", "AESG", "KEXS", "C255", "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(), "PUBS", pub_hex_.c_str(), "NONC",
+      nonce_hex_.c_str(), "VER\0", client_version_.data(), "$padding",
+      static_cast<int>(kClientHelloMinimumSize), nullptr);
+  // 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());
+}
+
 class AsyncStrikeServerVerificationTest : public CryptoServerTest {
  protected:
   AsyncStrikeServerVerificationTest() {}
@@ -757,8 +867,10 @@
   out_.set_tag(0);
 
   bool called = false;
-  config_.ValidateClientHello(msg, client_address_.address(), &clock_,
-                              new ValidateCallback(this, true, "", &called));
+  IPAddressNumber server_ip;
+  config_.ValidateClientHello(
+      msg, client_address_.address(), server_ip, supported_versions_.front(),
+      &clock_, &crypto_proof_, new ValidateCallback(this, true, "", &called));
   // The verification request was queued.
   ASSERT_FALSE(called);
   EXPECT_EQ(0u, out_.tag());
@@ -772,8 +884,9 @@
   EXPECT_EQ(kSHLO, out_.tag());
 
   // Rejected if replayed.
-  config_.ValidateClientHello(msg, client_address_.address(), &clock_,
-                              new ValidateCallback(this, true, "", &called));
+  config_.ValidateClientHello(
+      msg, client_address_.address(), server_ip, supported_versions_.front(),
+      &clock_, &crypto_proof_, new ValidateCallback(this, true, "", &called));
   // The verification request was queued.
   ASSERT_FALSE(called);
   EXPECT_EQ(1, strike_register_client_->PendingVerifications());
diff --git a/net/quic/crypto/crypto_utils.cc b/net/quic/crypto/crypto_utils.cc
index 0916474..6021dc6 100644
--- a/net/quic/crypto/crypto_utils.cc
+++ b/net/quic/crypto/crypto_utils.cc
@@ -12,6 +12,7 @@
 #include "net/quic/crypto/quic_encrypter.h"
 #include "net/quic/crypto/quic_random.h"
 #include "net/quic/quic_time.h"
+#include "net/quic/quic_utils.h"
 #include "url/url_canon.h"
 
 using base::StringPiece;
@@ -159,4 +160,9 @@
   return true;
 }
 
+// static
+uint64 CryptoUtils::ComputeLeafCertHash(const std::string& cert) {
+  return QuicUtils::FNV1a_64_Hash(cert.data(), cert.size());
+}
+
 }  // namespace net
diff --git a/net/quic/crypto/crypto_utils.h b/net/quic/crypto/crypto_utils.h
index 725a863..436f178 100644
--- a/net/quic/crypto/crypto_utils.h
+++ b/net/quic/crypto/crypto_utils.h
@@ -70,6 +70,10 @@
                                    size_t result_len,
                                    std::string* result);
 
+  // Computes the FNV-1a hash of the provided DER-encoded cert for use in the
+  // XLCT tag.
+  static uint64 ComputeLeafCertHash(const std::string& cert);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(CryptoUtils);
 };
diff --git a/net/quic/crypto/quic_crypto_client_config.cc b/net/quic/crypto/quic_crypto_client_config.cc
index 13a8bb8..ce48352a 100644
--- a/net/quic/crypto/quic_crypto_client_config.cc
+++ b/net/quic/crypto/quic_crypto_client_config.cc
@@ -539,6 +539,15 @@
   }
   out->SetStringPiece(kPUBS, out_params->client_key_exchange->public_value());
 
+  const vector<string>& certs = cached->certs();
+  if (preferred_version > QUIC_VERSION_25 && proof_verifier()) {
+    if (certs.empty()) {
+      *error_details = "No certs to calculate XLCT";
+      return QUIC_CRYPTO_INTERNAL_ERROR;
+    }
+    out->SetValue(kXLCT, CryptoUtils::ComputeLeafCertHash(certs[0]));
+  }
+
   if (channel_id_key) {
     // In order to calculate the encryption key for the CETV block we need to
     // serialise the client hello as it currently is (i.e. without the CETV
@@ -608,6 +617,13 @@
   out_params->hkdf_input_suffix.append(client_hello_serialized.data(),
                                        client_hello_serialized.length());
   out_params->hkdf_input_suffix.append(cached->server_config());
+  if (preferred_version > QUIC_VERSION_25 && proof_verifier()) {
+    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]);
+  }
 
   string hkdf_input;
   const size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1;
diff --git a/net/quic/crypto/quic_crypto_server_config.cc b/net/quic/crypto/quic_crypto_server_config.cc
index 3d99e5e..b8c6c736 100644
--- a/net/quic/crypto/quic_crypto_server_config.cc
+++ b/net/quic/crypto/quic_crypto_server_config.cc
@@ -482,8 +482,11 @@
 
 void QuicCryptoServerConfig::ValidateClientHello(
     const CryptoHandshakeMessage& client_hello,
-    IPAddressNumber client_ip,
+    const IPAddressNumber& client_ip,
+    const IPAddressNumber& server_ip,
+    QuicVersion version,
     const QuicClock* clock,
+    QuicCryptoProof* crypto_proof,
     ValidateClientHelloResultCallback* done_cb) const {
   const QuicWallTime now(clock->WallNow());
 
@@ -517,7 +520,8 @@
   }
 
   if (result->error_code == QUIC_NO_ERROR) {
-    EvaluateClientHello(primary_orbit, requested_config, result, done_cb);
+    EvaluateClientHello(server_ip, version, primary_orbit, requested_config,
+                        crypto_proof, result, done_cb);
   } else {
     done_cb->Run(result);
   }
@@ -535,6 +539,7 @@
     const QuicClock* clock,
     QuicRandom* rand,
     QuicCryptoNegotiatedParameters* params,
+    QuicCryptoProof* crypto_proof,
     CryptoHandshakeMessage* out,
     string* error_details) const {
   DCHECK(error_details);
@@ -601,14 +606,25 @@
 
   out->Clear();
 
+  bool x509_supported = false;
+  bool x509_ecdsa_supported = false;
+  ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported);
+  if (proof_source_.get() && !crypto_proof->certs &&
+      !proof_source_->GetProof(server_ip, info.sni.as_string(),
+                               primary_config->serialized, x509_ecdsa_supported,
+                               &crypto_proof->certs,
+                               &crypto_proof->signature)) {
+    return QUIC_HANDSHAKE_FAILED;
+  }
+
   if (!info.valid_source_address_token ||
       !info.client_nonce_well_formed ||
       !info.unique ||
       !requested_config.get()) {
-    BuildRejection(server_ip, *primary_config.get(), client_hello, info,
+    BuildRejection(*primary_config, client_hello, info,
                    validate_chlo_result.cached_network_params,
                    use_stateless_rejects, server_designated_connection_id, rand,
-                   params, out);
+                   params, *crypto_proof, out);
     return QUIC_NO_ERROR;
   }
 
@@ -667,6 +683,18 @@
   hkdf_suffix.append(client_hello_serialized.data(),
                      client_hello_serialized.length());
   hkdf_suffix.append(requested_config->serialized);
+  // The addition of x509_supported in this if statement is so that an insecure
+  // quic client talking to a secure quic server will not result in the secure
+  // quic server adding the cert to the kdf.
+  // TODO(nharper): Should a server that is configured to be secure (i.e. one
+  // that has a proof_source_) be accepting responses from an insecure client?
+  if (version > QUIC_VERSION_25 && proof_source_.get() && x509_supported) {
+    if (crypto_proof->certs->empty()) {
+      *error_details = "Failed to get certs";
+      return QUIC_CRYPTO_INTERNAL_ERROR;
+    }
+    hkdf_suffix.append(crypto_proof->certs->at(0));
+  }
 
   StringPiece cetv_ciphertext;
   if (requested_config->channel_id_enabled &&
@@ -911,8 +939,11 @@
 }
 
 void QuicCryptoServerConfig::EvaluateClientHello(
+    const IPAddressNumber& server_ip,
+    QuicVersion version,
     const uint8* primary_orbit,
     scoped_refptr<Config> requested_config,
+    QuicCryptoProof* crypto_proof,
     ValidateClientHelloResultCallback::Result* client_hello_state,
     ValidateClientHelloResultCallback* done_cb) const {
   ValidateClientHelloHelper helper(client_hello_state, done_cb);
@@ -990,6 +1021,25 @@
     found_error = true;
   }
 
+  if (version > QUIC_VERSION_25) {
+    bool x509_supported = false;
+    bool x509_ecdsa_supported = false;
+    ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported);
+    if (proof_source_.get() &&
+        !proof_source_->GetProof(server_ip, info->sni.as_string(),
+                                 requested_config->serialized,
+                                 x509_ecdsa_supported, &crypto_proof->certs,
+                                 &crypto_proof->signature)) {
+      found_error = true;
+      info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE);
+    }
+
+    if (!ValidateExpectedLeafCertificate(client_hello, *crypto_proof)) {
+      found_error = true;
+      info->reject_reasons.push_back(INVALID_EXPECTED_LEAF_CERTIFICATE);
+    }
+  }
+
   if (!replay_protection_) {
     if (!found_error) {
       info->unique = true;
@@ -1097,7 +1147,6 @@
 }
 
 void QuicCryptoServerConfig::BuildRejection(
-    const IPAddressNumber& server_ip,
     const Config& config,
     const CryptoHandshakeMessage& client_hello,
     const ClientHelloInfo& info,
@@ -1106,6 +1155,7 @@
     QuicConnectionId server_designated_connection_id,
     QuicRandom* rand,
     QuicCryptoNegotiatedParameters* params,
+    const QuicCryptoProof& crypto_proof,
     CryptoHandshakeMessage* out) const {
   if (FLAGS_enable_quic_stateless_reject_support && use_stateless_rejects) {
     DVLOG(1) << "QUIC Crypto server config returning stateless reject "
@@ -1130,38 +1180,14 @@
   out->SetVector(kRREJ, info.reject_reasons);
 
   // The client may have requested a certificate chain.
-  const QuicTag* their_proof_demands;
-  size_t num_their_proof_demands;
-
-  if (proof_source_.get() == nullptr ||
-      client_hello.GetTaglist(kPDMD, &their_proof_demands,
-                              &num_their_proof_demands) !=
-          QUIC_NO_ERROR) {
-    return;
-  }
-
   bool x509_supported = false;
-  for (size_t i = 0; i < num_their_proof_demands; i++) {
-    switch (their_proof_demands[i]) {
-      case kX509:
-        x509_supported = true;
-        params->x509_ecdsa_supported = true;
-        break;
-      case kX59R:
-        x509_supported = true;
-        break;
-    }
-  }
-
+  ParseProofDemand(client_hello, &x509_supported,
+                   &params->x509_ecdsa_supported);
   if (!x509_supported) {
     return;
   }
 
-  const vector<string>* certs;
-  string signature;
-  if (!proof_source_->GetProof(server_ip, info.sni.as_string(),
-                               config.serialized, params->x509_ecdsa_supported,
-                               &certs, &signature)) {
+  if (!proof_source_.get()) {
     return;
   }
 
@@ -1176,7 +1202,7 @@
   }
 
   const string compressed = CertCompressor::CompressChain(
-      *certs, params->client_common_set_hashes,
+      *crypto_proof.certs, params->client_common_set_hashes,
       params->client_cached_cert_hashes, config.common_cert_sets);
 
   // kREJOverheadBytes is a very rough estimate of how much of a REJ
@@ -1199,9 +1225,9 @@
   static_assert(kClientHelloMinimumSize * kMultiplier >= kREJOverheadBytes,
                 "overhead calculation may overflow");
   if (info.valid_source_address_token ||
-      signature.size() + compressed.size() < max_unverified_size) {
+      crypto_proof.signature.size() + compressed.size() < max_unverified_size) {
     out->SetStringPiece(kCertificateTag, compressed);
-    out->SetStringPiece(kPROF, signature);
+    out->SetStringPiece(kPROF, crypto_proof.signature);
   }
 }
 
@@ -1644,6 +1670,53 @@
   }
 }
 
+bool QuicCryptoServerConfig::ValidateExpectedLeafCertificate(
+    const CryptoHandshakeMessage& client_hello,
+    const QuicCryptoProof& crypto_proof) const {
+  // If the server doesn't use https, then the client won't send XLCT and
+  // proof_source_ will be null, so in this case return true.
+  if (!proof_source_.get()) {
+    return true;
+  }
+  if (crypto_proof.certs->empty()) {
+    return false;
+  }
+
+  uint64 hash_from_client;
+  if (client_hello.GetUint64(kXLCT, &hash_from_client) != QUIC_NO_ERROR) {
+    return false;
+  }
+  return CryptoUtils::ComputeLeafCertHash(crypto_proof.certs->at(0)) ==
+         hash_from_client;
+}
+
+void QuicCryptoServerConfig::ParseProofDemand(
+    const CryptoHandshakeMessage& client_hello,
+    bool* x509_supported,
+    bool* x509_ecdsa_supported) const {
+  const QuicTag* their_proof_demands;
+  size_t num_their_proof_demands;
+
+  if (proof_source_.get() == nullptr ||
+      client_hello.GetTaglist(kPDMD, &their_proof_demands,
+                              &num_their_proof_demands) != QUIC_NO_ERROR) {
+    return;
+  }
+
+  *x509_supported = false;
+  for (size_t i = 0; i < num_their_proof_demands; i++) {
+    switch (their_proof_demands[i]) {
+      case kX509:
+        *x509_supported = true;
+        *x509_ecdsa_supported = true;
+        break;
+      case kX59R:
+        *x509_supported = true;
+        break;
+    }
+  }
+}
+
 QuicCryptoServerConfig::Config::Config()
     : channel_id_enabled(false),
       is_primary(false),
diff --git a/net/quic/crypto/quic_crypto_server_config.h b/net/quic/crypto/quic_crypto_server_config.h
index f7716e2..2ab58a2 100644
--- a/net/quic/crypto/quic_crypto_server_config.h
+++ b/net/quic/crypto/quic_crypto_server_config.h
@@ -201,15 +201,23 @@
   // 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_ip: the IP address of the server. The IP address may be used for
+  //     certificate selection.
+  // version: protocol version used for this connection.
   // clock: used to validate client nonces and ephemeral keys.
+  // crypto_proof: output structure containing the crypto proof used in reply to
+  // a proof demand.
   // 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,
-                           IPAddressNumber client_ip,
+                           const IPAddressNumber& client_ip,
+                           const IPAddressNumber& server_ip,
+                           QuicVersion version,
                            const QuicClock* clock,
+                           QuicCryptoProof* crypto_proof,
                            ValidateClientHelloResultCallback* done_cb) const;
 
   // ProcessClientHello processes |client_hello| and decides whether to accept
@@ -223,20 +231,20 @@
   //     information about it.
   // connection_id: the ConnectionId for the connection, which is used in key
   //     derivation.
-  // server_ip: the IP address and port of the server. The IP address may be
-  //     used for certificate selection.
+  // 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.
-  // initial_flow_control_window: size of initial flow control window this
-  //     server uses for new streams.
   // clock: used to validate client nonces and ephemeral keys.
   // rand: an entropy source
   // params: the state of the handshake. This may be updated with a server
   //     nonce when we send a rejection. After a successful handshake, this will
   //     contain the state of the connection.
+  // crypto_proof: output structure containing the crypto proof used in reply to
+  //     a proof demand.
   // out: the resulting handshake message (either REJ or SHLO)
   // error_details: used to store a string describing any error.
   QuicErrorCode ProcessClientHello(
@@ -251,6 +259,7 @@
       const QuicClock* clock,
       QuicRandom* rand,
       QuicCryptoNegotiatedParameters* params,
+      QuicCryptoProof* crypto_proof,
       CryptoHandshakeMessage* out,
       std::string* error_details) const;
 
@@ -426,14 +435,16 @@
   // whether it can be shown to be fresh (i.e. not a replay). The results are
   // written to |info|.
   void EvaluateClientHello(
+      const IPAddressNumber& server_ip,
+      QuicVersion version,
       const uint8* primary_orbit,
       scoped_refptr<Config> requested_config,
+      QuicCryptoProof* crypto_proof,
       ValidateClientHelloResultCallback::Result* client_hello_state,
       ValidateClientHelloResultCallback* done_cb) const;
 
   // BuildRejection sets |out| to be a REJ message in reply to |client_hello|.
-  void BuildRejection(const IPAddressNumber& server_ip,
-                      const Config& config,
+  void BuildRejection(const Config& config,
                       const CryptoHandshakeMessage& client_hello,
                       const ClientHelloInfo& info,
                       const CachedNetworkParameters& cached_network_params,
@@ -441,6 +452,7 @@
                       QuicConnectionId server_designated_connection_id,
                       QuicRandom* rand,
                       QuicCryptoNegotiatedParameters* params,
+                      const QuicCryptoProof& crypto_proof,
                       CryptoHandshakeMessage* out) const;
 
   // ParseConfigProtobuf parses the given config protobuf and returns a
@@ -506,6 +518,22 @@
       base::StringPiece echoed_server_nonce,
       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. The certs field of |crypto_proof| 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 QuicCryptoProof& crypto_proof) const;
+
+  // ParseProofDemand reads the PDMD field from the client hello and sets the
+  // |x509_ecdsa_supported| and |x509_supported| output parameters.
+  void ParseProofDemand(const CryptoHandshakeMessage& client_hello,
+                        bool* x509_supported,
+                        bool* x509_ecdsa_supported) const;
+
   // replay_protection_ controls whether the server enforces that handshakes
   // aren't replays.
   bool replay_protection_;
diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc
index ac6629eb..261230b0 100644
--- a/net/quic/quic_connection.cc
+++ b/net/quic/quic_connection.cc
@@ -2264,10 +2264,6 @@
     return suggested_max_packet_size;
   }
 
-  if (!FLAGS_quic_limit_mtu_by_writer) {
-    return suggested_max_packet_size;
-  }
-
   if (peer_address_.address().empty()) {
     LOG(DFATAL) << "Attempted to use a connection without a valid peer address";
     return suggested_max_packet_size;
diff --git a/net/quic/quic_crypto_server_stream.cc b/net/quic/quic_crypto_server_stream.cc
index dd22a8e..f50ab9e 100644
--- a/net/quic/quic_crypto_server_stream.cc
+++ b/net/quic/quic_crypto_server_stream.cc
@@ -79,7 +79,9 @@
   validate_client_hello_cb_ = new ValidateCallback(this);
   return crypto_config_->ValidateClientHello(
       message, session()->connection()->peer_address().address(),
-      session()->connection()->clock(), validate_client_hello_cb_);
+      session()->connection()->self_address().address(), version(),
+      session()->connection()->clock(), &crypto_proof_,
+      validate_client_hello_cb_);
 }
 
 void QuicCryptoServerStream::FinishProcessingHandshakeMessage(
@@ -253,7 +255,7 @@
       connection->peer_address(), version(), connection->supported_versions(),
       use_stateless_rejects_in_crypto_config, server_designated_connection_id,
       connection->clock(), connection->random_generator(),
-      &crypto_negotiated_params_, reply, error_details);
+      &crypto_negotiated_params_, &crypto_proof_, reply, error_details);
 }
 
 void QuicCryptoServerStream::OverrideQuicConfigDefaults(QuicConfig* config) {
diff --git a/net/quic/quic_crypto_server_stream.h b/net/quic/quic_crypto_server_stream.h
index 0a1a61a..0a0b37c 100644
--- a/net/quic/quic_crypto_server_stream.h
+++ b/net/quic/quic_crypto_server_stream.h
@@ -162,6 +162,10 @@
   // crypto_config_ contains crypto parameters for the handshake.
   const QuicCryptoServerConfig* crypto_config_;
 
+  // Server's certificate chain and signature of the server config, as provided
+  // by ProofSource::GetProof.
+  QuicCryptoProof crypto_proof_;
+
   // Pointer to the active callback that will receive the result of
   // the client hello validation request and forward it to
   // FinishProcessingHandshakeMessage for processing.  nullptr if no
diff --git a/net/quic/quic_crypto_server_stream_test.cc b/net/quic/quic_crypto_server_stream_test.cc
index 5eb4e45..5a999ed 100644
--- a/net/quic/quic_crypto_server_stream_test.cc
+++ b/net/quic/quic_crypto_server_stream_test.cc
@@ -61,14 +61,14 @@
 namespace {
 
 const char kServerHostname[] = "test.example.com";
-const uint16 kServerPort = 80;
+const uint16 kServerPort = 443;
 
 class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> {
  public:
   QuicCryptoServerStreamTest()
       : server_crypto_config_(QuicCryptoServerConfig::TESTING,
                               QuicRandom::GetInstance()),
-        server_id_(kServerHostname, kServerPort, false, PRIVACY_MODE_DISABLED) {
+        server_id_(kServerHostname, kServerPort, true, PRIVACY_MODE_DISABLED) {
 #if defined(USE_OPENSSL)
     server_crypto_config_.SetProofSource(
         CryptoTestUtils::ProofSourceForTesting());
@@ -128,6 +128,16 @@
                                &client_session);
     CHECK(client_session);
     client_session_.reset(client_session);
+    if (!client_options_.dont_verify_certs) {
+#if defined(USE_OPENSSL)
+      client_crypto_config_.SetProofVerifier(
+          CryptoTestUtils::ProofVerifierForTesting());
+#else
+      // TODO(rch): Implement a NSS proof source.
+      client_crypto_config_.SetProofVerifier(
+          CryptoTestUtils::FakeProofVerifierForTesting());
+#endif
+    }
   }
 
   bool AsyncStrikeRegisterVerification() {
@@ -143,7 +153,7 @@
     CHECK(server_connection_);
     CHECK(server_session_ != nullptr);
     return CryptoTestUtils::HandshakeWithFakeClient(
-        server_connection_, server_stream(), client_options_);
+        server_connection_, server_stream(), server_id_, client_options_);
   }
 
   // Performs a single round of handshake message-exchange between the
@@ -376,6 +386,8 @@
 
 TEST_P(QuicCryptoServerStreamTest, WithoutCertificates) {
   server_crypto_config_.SetProofSource(nullptr);
+  server_id_ =
+      QuicServerId(kServerHostname, kServerPort, false, PRIVACY_MODE_DISABLED);
   client_options_.dont_verify_certs = true;
 
   // Only 2 client hellos need to be sent in the no-certs case: one to get the
diff --git a/net/quic/quic_flags.cc b/net/quic/quic_flags.cc
index 08a5f47..dcbc230 100644
--- a/net/quic/quic_flags.cc
+++ b/net/quic/quic_flags.cc
@@ -66,9 +66,6 @@
 // number order.
 bool FLAGS_quic_close_connection_out_of_order_sending = true;
 
-// Allow QUIC packet writer to limit the MTU of the connection.
-bool FLAGS_quic_limit_mtu_by_writer = true;
-
 // QUIC-specific flag. If true, Cubic's epoch is reset when the sender is
 // application-limited.
 bool FLAGS_reset_cubic_epoch_when_app_limited = true;
diff --git a/net/quic/quic_flags.h b/net/quic/quic_flags.h
index ed8d0e1..ec4cd5a 100644
--- a/net/quic/quic_flags.h
+++ b/net/quic/quic_flags.h
@@ -24,7 +24,6 @@
 NET_EXPORT_PRIVATE extern bool FLAGS_quic_disable_truncated_ack_handling;
 NET_EXPORT_PRIVATE extern bool FLAGS_send_goaway_after_client_migration;
 NET_EXPORT_PRIVATE extern bool FLAGS_quic_close_connection_out_of_order_sending;
-NET_EXPORT_PRIVATE extern bool FLAGS_quic_limit_mtu_by_writer;
 NET_EXPORT_PRIVATE extern bool FLAGS_reset_cubic_epoch_when_app_limited;
 NET_EXPORT_PRIVATE extern bool FLAGS_quic_packet_queue_use_interval_set;
 
diff --git a/net/quic/quic_protocol.cc b/net/quic/quic_protocol.cc
index deaeaf36..651ee84 100644
--- a/net/quic/quic_protocol.cc
+++ b/net/quic/quic_protocol.cc
@@ -134,6 +134,8 @@
       return MakeQuicTag('Q', '0', '2', '4');
     case QUIC_VERSION_25:
       return MakeQuicTag('Q', '0', '2', '5');
+    case QUIC_VERSION_26:
+      return MakeQuicTag('Q', '0', '2', '6');
     default:
       // This shold be an ERROR because we should never attempt to convert an
       // invalid QuicVersion to be written to the wire.
@@ -162,6 +164,7 @@
   switch (version) {
     RETURN_STRING_LITERAL(QUIC_VERSION_24);
     RETURN_STRING_LITERAL(QUIC_VERSION_25);
+    RETURN_STRING_LITERAL(QUIC_VERSION_26);
     default:
       return "QUIC_VERSION_UNSUPPORTED";
   }
diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h
index da542333..bfe7cc5 100644
--- a/net/quic/quic_protocol.h
+++ b/net/quic/quic_protocol.h
@@ -338,6 +338,7 @@
   QUIC_VERSION_24 = 24,  // SPDY/4 header compression.
   QUIC_VERSION_25 = 25,  // SPDY/4 header keys, and removal of error_details
                          // from QuicRstStreamFrame
+  QUIC_VERSION_26 = 26,  // In CHLO, send XLCT tag containing hash of leaf cert
 };
 
 // This vector contains QUIC versions which we currently support.
@@ -347,8 +348,8 @@
 //
 // IMPORTANT: if you are adding to this list, follow the instructions at
 // http://sites/quic/adding-and-removing-versions
-static const QuicVersion kSupportedQuicVersions[] = {QUIC_VERSION_25,
-                                                     QUIC_VERSION_24};
+static const QuicVersion kSupportedQuicVersions[] = {
+    QUIC_VERSION_26, QUIC_VERSION_25, QUIC_VERSION_24};
 
 typedef std::vector<QuicVersion> QuicVersionVector;
 
diff --git a/net/quic/test_tools/crypto_test_utils.cc b/net/quic/test_tools/crypto_test_utils.cc
index 1f3da2b7..409a042 100644
--- a/net/quic/test_tools/crypto_test_utils.cc
+++ b/net/quic/test_tools/crypto_test_utils.cc
@@ -32,9 +32,6 @@
 
 namespace {
 
-const char kServerHostname[] = "test.example.com";
-const uint16 kServerPort = 80;
-
 // CryptoFramerVisitor is a framer visitor that records handshake messages.
 class CryptoFramerVisitor : public CryptoFramerVisitorInterface {
  public:
@@ -206,6 +203,7 @@
 int CryptoTestUtils::HandshakeWithFakeClient(
     PacketSavingConnection* server_conn,
     QuicCryptoServerStream* server,
+    const QuicServerId& server_id,
     const FakeClientOptions& options) {
   PacketSavingConnection* client_conn =
       new PacketSavingConnection(Perspective::IS_CLIENT);
@@ -213,10 +211,8 @@
   client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(1));
 
   QuicCryptoClientConfig crypto_config;
-  bool is_https = false;
   AsyncTestChannelIDSource* async_channel_id_source = nullptr;
   if (options.channel_id_enabled) {
-    is_https = true;
 
     ChannelIDSource* source = ChannelIDSourceForTesting();
     if (options.channel_id_source_async) {
@@ -225,9 +221,7 @@
     }
     crypto_config.SetChannelIDSource(source);
   }
-  QuicServerId server_id(kServerHostname, kServerPort, is_https,
-                         PRIVACY_MODE_DISABLED);
-  if (!options.dont_verify_certs) {
+  if (!options.dont_verify_certs && server_id.is_https()) {
 #if defined(USE_OPENSSL)
     crypto_config.SetProofVerifier(ProofVerifierForTesting());
 #else
@@ -250,7 +244,7 @@
   if (options.channel_id_enabled) {
     scoped_ptr<ChannelIDKey> channel_id_key;
     QuicAsyncStatus status = crypto_config.channel_id_source()->GetChannelIDKey(
-        kServerHostname, &channel_id_key, nullptr);
+        server_id.host(), &channel_id_key, nullptr);
     EXPECT_EQ(QUIC_SUCCESS, status);
     EXPECT_EQ(channel_id_key->SerializeKey(),
               server->crypto_negotiated_params().channel_id);
diff --git a/net/quic/test_tools/crypto_test_utils.h b/net/quic/test_tools/crypto_test_utils.h
index 240bd36..78aec5cc 100644
--- a/net/quic/test_tools/crypto_test_utils.h
+++ b/net/quic/test_tools/crypto_test_utils.h
@@ -31,6 +31,7 @@
 class QuicCryptoServerStream;
 class QuicCryptoStream;
 class QuicRandom;
+class QuicServerId;
 
 namespace test {
 
@@ -77,6 +78,7 @@
   // returns: the number of client hellos that the client sent.
   static int HandshakeWithFakeClient(PacketSavingConnection* server_conn,
                                      QuicCryptoServerStream* server,
+                                     const QuicServerId& server_id,
                                      const FakeClientOptions& options);
 
   // SetupCryptoServerConfigForTest configures |config| and |crypto_config|
diff --git a/net/tools/quic/end_to_end_test.cc b/net/tools/quic/end_to_end_test.cc
index 651f499..fa671a2c 100644
--- a/net/tools/quic/end_to_end_test.cc
+++ b/net/tools/quic/end_to_end_test.cc
@@ -142,14 +142,18 @@
   // to do 0-RTT across incompatible versions. Chromium only supports
   // a single version at a time anyway. :)
   QuicVersionVector all_supported_versions = QuicSupportedVersions();
-  QuicVersionVector client_version_buckets[2];
+  QuicVersionVector client_version_buckets[3];
   for (const QuicVersion version : all_supported_versions) {
     if (version <= QUIC_VERSION_24) {
       // SPDY/4 compression but SPDY/3 headers
       client_version_buckets[0].push_back(version);
-    } else {
+    } else if (version <= QUIC_VERSION_25) {
       // SPDY/4
       client_version_buckets[1].push_back(version);
+    } else {
+      // QUIC_VERSION_26 changes the kdf in a way that is incompatible with
+      // version negotiation across the version 26 boundary.
+      client_version_buckets[2].push_back(version);
     }
   }
 
diff --git a/net/tools/quic/quic_spdy_server_stream.cc b/net/tools/quic/quic_spdy_server_stream.cc
index aeb5686..06d065c 100644
--- a/net/tools/quic/quic_spdy_server_stream.cc
+++ b/net/tools/quic/quic_spdy_server_stream.cc
@@ -8,6 +8,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
 #include "net/quic/quic_data_stream.h"
 #include "net/quic/quic_spdy_session.h"
 #include "net/quic/spdy_utils.h"
@@ -99,17 +100,27 @@
     body_.append(data + len, data_len - len);
   }
   if (ContainsKey(request_headers_, "content-length")) {
-    // Historically, if an input to SimpleAtoi contained null byte, anything
-    // past it would be silently ignored. This behavior is being removed, but
-    // this method relies on it (see cl/101239633). Hence, we explicitly call
-    // c_str() on request headers to simulate the old behavior.
-    // TODO(rch): Correctly handle null-separated value in content-length.
-    // b/23554022
-    StringPiece trimmed_header(request_headers_["content-length"].c_str());
-    if (!StringToInt(trimmed_header, &content_length_)) {
-      return false;  // Invalid content-length.
+    string delimiter;
+    delimiter.push_back('\0');
+    std::vector<string> values =
+        base::SplitString(request_headers_["content-length"], delimiter,
+                          base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+    for (const string& value : values) {
+      int new_value;
+      if (!StringToInt(value, &new_value) || new_value < 0) {
+        return false;
+      }
+      if (content_length_ < 0) {
+        content_length_ = new_value;
+        continue;
+      }
+      if (new_value != content_length_) {
+        return false;
+      }
     }
   }
+
   return true;
 }
 
diff --git a/net/tools/quic/quic_spdy_server_stream_test.cc b/net/tools/quic/quic_spdy_server_stream_test.cc
index e335642b..c49da69 100644
--- a/net/tools/quic/quic_spdy_server_stream_test.cc
+++ b/net/tools/quic/quic_spdy_server_stream_test.cc
@@ -62,6 +62,10 @@
     return stream->body_;
   }
 
+  static const int content_length(QuicSpdyServerStream* stream) {
+    return stream->content_length_;
+  }
+
   static const SpdyHeaderBlock& headers(QuicSpdyServerStream* stream) {
     return stream->request_headers_;
   }
@@ -221,6 +225,62 @@
   EXPECT_TRUE(stream_->write_side_closed());
 }
 
+TEST_P(QuicSpdyServerStreamTest, InvalidMultipleContentLength) {
+  SpdyHeaderBlock request_headers;
+  request_headers["content-length"] = "11";
+  request_headers["content-length"].push_back('\0');
+  request_headers["content-length"].append("12");
+
+  headers_string_ =
+      SpdyUtils::SerializeUncompressedHeaders(request_headers, GetParam());
+
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .Times(AnyNumber())
+      .WillRepeatedly(Invoke(ConsumeAllData));
+
+  stream_->OnStreamHeaders(headers_string_);
+  stream_->OnStreamHeadersComplete(false, headers_string_.size());
+
+  EXPECT_TRUE(stream_->read_side_closed());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSpdyServerStreamTest, InvalidLeadingNullContentLength) {
+  SpdyHeaderBlock request_headers;
+  request_headers["content-length"] = '\0';
+  request_headers["content-length"].append("12");
+
+  headers_string_ =
+      SpdyUtils::SerializeUncompressedHeaders(request_headers, GetParam());
+
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .Times(AnyNumber())
+      .WillRepeatedly(Invoke(ConsumeAllData));
+
+  stream_->OnStreamHeaders(headers_string_);
+  stream_->OnStreamHeadersComplete(false, headers_string_.size());
+
+  EXPECT_TRUE(stream_->read_side_closed());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSpdyServerStreamTest, ValidMultipleContentLength) {
+  SpdyHeaderBlock request_headers;
+  request_headers["content-length"] = "11";
+  request_headers["content-length"].push_back('\0');
+  request_headers["content-length"].append("11");
+
+  headers_string_ =
+      SpdyUtils::SerializeUncompressedHeaders(request_headers, GetParam());
+
+  stream_->OnStreamHeaders(headers_string_);
+  stream_->OnStreamHeadersComplete(false, headers_string_.size());
+
+  EXPECT_EQ(11, QuicSpdyServerStreamPeer::content_length(stream_.get()));
+  EXPECT_FALSE(stream_->read_side_closed());
+  EXPECT_FALSE(stream_->write_side_closed());
+}
+
 TEST_P(QuicSpdyServerStreamTest, InvalidHeadersWithFin) {
   char arr[] = {
     0x3a, 0x68, 0x6f, 0x73,  // :hos