Add FingerprintProofVerifier, a proof verifier that checks against a predefined set of certificate fingerprints.

This will be used to implement <https://wicg.github.io/web-transport/#dom-quictransportconfiguration-server_certificate_fingerprints>.

PiperOrigin-RevId: 314717008
Change-Id: I0a745f442618936c501e6324382c95806af369bb
diff --git a/quic/core/quic_time.h b/quic/core/quic_time.h
index 93ea308..079b59a 100644
--- a/quic/core/quic_time.h
+++ b/quic/core/quic_time.h
@@ -188,6 +188,10 @@
     return microseconds_ == other.microseconds_;
   }
 
+  QuicTime::Delta operator-(const QuicWallTime& rhs) const {
+    return QuicTime::Delta::FromMicroseconds(microseconds_ - rhs.microseconds_);
+  }
+
  private:
   explicit constexpr QuicWallTime(uint64_t microseconds)
       : microseconds_(microseconds) {}
diff --git a/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc b/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc
new file mode 100644
index 0000000..15cd706
--- /dev/null
+++ b/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc
@@ -0,0 +1,220 @@
+// 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 "net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.h"
+
+#include <cstdint>
+#include <memory>
+
+#include "third_party/boringssl/src/include/openssl/sha.h"
+#include "net/third_party/quiche/src/quic/core/crypto/certificate_view.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
+
+namespace quic {
+namespace {
+
+constexpr size_t kFingerprintLength = SHA256_DIGEST_LENGTH * 3 - 1;
+
+constexpr std::array<char, 16> kHexDigits = {'0', '1', '2', '3', '4', '5',
+                                             '6', '7', '8', '9', 'a', 'b',
+                                             'c', 'd', 'e', 'f'};
+
+// 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[];
+
+std::string ComputeSha256Fingerprint(quiche::QuicheStringPiece input) {
+  std::vector<uint8_t> raw_hash;
+  raw_hash.resize(SHA256_DIGEST_LENGTH);
+  SHA256(reinterpret_cast<const uint8_t*>(input.data()), input.size(),
+         raw_hash.data());
+
+  std::string output;
+  output.resize(kFingerprintLength);
+  for (size_t i = 0; i < output.size(); i++) {
+    uint8_t hash_byte = raw_hash[i / 3];
+    switch (i % 3) {
+      case 0:
+        output[i] = kHexDigits[hash_byte >> 4];
+        break;
+      case 1:
+        output[i] = kHexDigits[hash_byte & 0xf];
+        break;
+      case 2:
+        output[i] = ':';
+        break;
+    }
+  }
+  return output;
+}
+
+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;
+      }
+    }
+  }
+
+  fingerprints_.push_back(fingerprint);
+  return true;
+}
+
+QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyProof(
+    const std::string& /*hostname*/,
+    const uint16_t /*port*/,
+    const std::string& /*server_config*/,
+    QuicTransportVersion /*transport_version*/,
+    quiche::QuicheStringPiece /*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 << *error_details;
+  *details = std::make_unique<Details>(Status::kInternalError);
+  return QUIC_FAILURE;
+}
+
+QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyCertChain(
+    const std::string& /*hostname*/,
+    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,
+    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 = quiche::QuicheStrCat(
+        "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;
+  }
+
+  *details = std::make_unique<Details>(Status::kValidCertificate);
+  return QUIC_SUCCESS;
+}
+
+std::unique_ptr<ProofVerifyContext>
+WebTransportFingerprintProofVerifier::CreateDefaultContext() {
+  return nullptr;
+}
+
+bool WebTransportFingerprintProofVerifier::HasKnownFingerprint(
+    quiche::QuicheStringPiece der_certificate) {
+  // https://wicg.github.io/web-transport/#verify-a-certificate-fingerprint
+  const std::string fingerprint = ComputeSha256Fingerprint(der_certificate);
+  for (const CertificateFingerprint& reference : fingerprints_) {
+    if (reference.algorithm != CertificateFingerprint::kSha256) {
+      QUIC_BUG << "Unexpected non-SHA-256 hash";
+      continue;
+    }
+    if (fingerprint == reference.fingerprint) {
+      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());
+}
+
+}  // namespace quic
diff --git a/quic/quic_transport/web_transport_fingerprint_proof_verifier.h b/quic/quic_transport/web_transport_fingerprint_proof_verifier.h
new file mode 100644
index 0000000..b17ee87
--- /dev/null
+++ b/quic/quic_transport/web_transport_fingerprint_proof_verifier.h
@@ -0,0 +1,118 @@
+// 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 "net/third_party/quiche/src/quic/core/crypto/certificate_view.h"
+#include "net/third_party/quiche/src/quic/core/crypto/proof_verifier.h"
+#include "net/third_party/quiche/src/quic/core/quic_clock.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
+
+namespace quic {
+
+// Represents a fingerprint of an X.509 certificate in a format based on
+// https://w3c.github.io/webrtc-pc/#dom-rtcdtlsfingerprint.
+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;
+};
+
+// Computes a SHA-256 fingerprint of the specified input formatted in the same
+// format as CertificateFingerprint::fingerprint would contain.
+QUIC_EXPORT_PRIVATE std::string ComputeSha256Fingerprint(
+    quiche::QuicheStringPiece input);
+
+// 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,
+
+    kMaxValue = kInternalError,
+  };
+
+  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);
+
+  // ProofVerifier implementation.
+  QuicAsyncStatus VerifyProof(
+      const std::string& hostname,
+      const uint16_t port,
+      const std::string& server_config,
+      QuicTransportVersion transport_version,
+      quiche::QuicheStringPiece 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 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,
+      std::unique_ptr<ProofVerifierCallback> callback) override;
+  std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override;
+
+ private:
+  bool HasKnownFingerprint(quiche::QuicheStringPiece 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<CertificateFingerprint> fingerprints_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUIC_TRANSPORT_FINGERPRINT_PROOF_VERIFIER_H_
diff --git a/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc b/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc
new file mode 100644
index 0000000..b79e873
--- /dev/null
+++ b/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc
@@ -0,0 +1,184 @@
+// 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 "net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.h"
+
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
+#include "net/third_party/quiche/src/quic/test_tools/test_certificates.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.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(quiche::QuicheStringPiece certificate) {
+    VerifyResult result;
+    std::unique_ptr<ProofVerifyDetails> details;
+    result.status = verifier_->VerifyCertChain(
+        /*hostname=*/"", std::vector<std::string>{std::string(certificate)},
+        /*ocsp_response=*/"",
+        /*cert_sct=*/"",
+        /*context=*/nullptr, &result.error, &details,
+        /*callback=*/nullptr);
+    result.detailed_status =
+        static_cast<WebTransportFingerprintProofVerifier::Details*>(
+            details.get())
+            ->status();
+    return result;
+  }
+
+  void AddTestCertificate() {
+    EXPECT_TRUE(verifier_->AddFingerprint(CertificateFingerprint{
+        .algorithm = CertificateFingerprint::kSha256,
+        .fingerprint = ComputeSha256Fingerprint(kTestCertificate)}));
+  }
+
+  MockClock clock_;
+  std::unique_ptr<WebTransportFingerprintProofVerifier> verifier_;
+};
+
+TEST_F(WebTransportFingerprintProofVerifierTest, Sha256Fingerprint) {
+  // Computed using `openssl x509 -fingerprint -sha256`.
+  EXPECT_EQ(ComputeSha256Fingerprint(kTestCertificate),
+            "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");
+}
+
+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 quiche::QuicheStringPiece kInvalidCertificate = "Hello, world!";
+  ASSERT_TRUE(verifier_->AddFingerprint(CertificateFingerprint{
+      .algorithm = CertificateFingerprint::kSha256,
+      .fingerprint = ComputeSha256Fingerprint(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{
+      .algorithm = CertificateFingerprint::kSha256,
+      .fingerprint = "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{
+      .algorithm = "sha-1",
+      .fingerprint =
+          "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{.algorithm = CertificateFingerprint::kSha256,
+                             .fingerprint = "00:00:00:00"}));
+  // Reject missing colons.
+  EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{
+      .algorithm = CertificateFingerprint::kSha256,
+      .fingerprint = "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{
+      .algorithm = CertificateFingerprint::kSha256,
+      .fingerprint = "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
diff --git a/quic/test_tools/mock_clock.cc b/quic/test_tools/mock_clock.cc
index 1761dd9..21c080a 100644
--- a/quic/test_tools/mock_clock.cc
+++ b/quic/test_tools/mock_clock.cc
@@ -14,6 +14,10 @@
   now_ = now_ + delta;
 }
 
+void MockClock::Reset() {
+  now_ = QuicTime::Zero();
+}
+
 QuicTime MockClock::Now() const {
   return now_;
 }
diff --git a/quic/test_tools/mock_clock.h b/quic/test_tools/mock_clock.h
index 4bd51e9..2ce2e96 100644
--- a/quic/test_tools/mock_clock.h
+++ b/quic/test_tools/mock_clock.h
@@ -24,6 +24,8 @@
 
   // Advances the current time by |delta|, which may be negative.
   void AdvanceTime(QuicTime::Delta delta);
+  // Resets time back to zero.
+  void Reset();
 
  private:
   QuicTime now_;