Add expiry parsing into CertificateView.

PiperOrigin-RevId: 314437222
Change-Id: Ia7ff932ea0c21ef064f68f6242179d7b579d2a9f
diff --git a/common/platform/api/quiche_time_utils.h b/common/platform/api/quiche_time_utils.h
new file mode 100644
index 0000000..7319568
--- /dev/null
+++ b/common/platform/api/quiche_time_utils.h
@@ -0,0 +1,31 @@
+// 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_COMMON_PLATFORM_API_QUICHE_TIME_UTILS_H_
+#define QUICHE_COMMON_PLATFORM_API_QUICHE_TIME_UTILS_H_
+
+#include <cstdint>
+
+#include "net/quiche/common/platform/impl/quiche_time_utils_impl.h"
+
+namespace quiche {
+
+// Converts a civil time specified in UTC into a number of seconds since the
+// Unix epoch.  This function is strict about validity of accepted dates.  For
+// instance, it will reject February 29 on non-leap years, or 25 hours in a day.
+// As a notable exception, 60 seconds is accepted to deal with potential leap
+// seconds.  If the date predates Unix epoch, nullopt will be returned.
+inline QuicheOptional<int64_t> QuicheUtcDateTimeToUnixSeconds(int year,
+                                                              int month,
+                                                              int day,
+                                                              int hour,
+                                                              int minute,
+                                                              int second) {
+  return QuicheUtcDateTimeToUnixSecondsImpl(year, month, day, hour, minute,
+                                            second);
+}
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_PLATFORM_API_QUICHE_TIME_UTILS_H_
diff --git a/common/platform/api/quiche_time_utils_test.cc b/common/platform/api/quiche_time_utils_test.cc
new file mode 100644
index 0000000..0f32b10
--- /dev/null
+++ b/common/platform/api/quiche_time_utils_test.cc
@@ -0,0 +1,51 @@
+// 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/common/platform/api/quiche_time_utils.h"
+
+#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace {
+
+TEST(QuicheTimeUtilsTest, Basic) {
+  EXPECT_EQ(1, QuicheUtcDateTimeToUnixSeconds(1970, 1, 1, 0, 0, 1));
+  EXPECT_EQ(365 * 86400, QuicheUtcDateTimeToUnixSeconds(1971, 1, 1, 0, 0, 0));
+  // Some arbitrary timestamps closer to the present, compared to the output of
+  // "Date(...).getTime()" from the JavaScript console.
+  EXPECT_EQ(1152966896,
+            QuicheUtcDateTimeToUnixSeconds(2006, 7, 15, 12, 34, 56));
+  EXPECT_EQ(1591130001, QuicheUtcDateTimeToUnixSeconds(2020, 6, 2, 20, 33, 21));
+
+  EXPECT_EQ(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1970, 2, 29, 0, 0, 1));
+  EXPECT_NE(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1972, 2, 29, 0, 0, 1));
+}
+
+TEST(QuicheTimeUtilsTest, Bounds) {
+  EXPECT_EQ(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1970, 1, 32, 0, 0, 1));
+  EXPECT_EQ(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1970, 4, 31, 0, 0, 1));
+  EXPECT_EQ(QuicheNullOpt, QuicheUtcDateTimeToUnixSeconds(1970, 1, 0, 0, 0, 1));
+  EXPECT_EQ(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1970, 13, 1, 0, 0, 1));
+  EXPECT_EQ(QuicheNullOpt, QuicheUtcDateTimeToUnixSeconds(1970, 0, 1, 0, 0, 1));
+  EXPECT_EQ(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1970, 1, 1, 24, 0, 0));
+  EXPECT_EQ(QuicheNullOpt,
+            QuicheUtcDateTimeToUnixSeconds(1970, 1, 1, 0, 60, 0));
+}
+
+TEST(QuicheTimeUtilsTest, LeapSecond) {
+  EXPECT_EQ(QuicheUtcDateTimeToUnixSeconds(2015, 6, 30, 23, 59, 60),
+            QuicheUtcDateTimeToUnixSeconds(2015, 7, 1, 0, 0, 0));
+  EXPECT_EQ(QuicheUtcDateTimeToUnixSeconds(2015, 6, 30, 25, 59, 60),
+            QuicheNullOpt);
+}
+
+}  // namespace
+}  // namespace quiche
diff --git a/common/quiche_data_reader.cc b/common/quiche_data_reader.cc
index 3445013..2242fea 100644
--- a/common/quiche_data_reader.cc
+++ b/common/quiche_data_reader.cc
@@ -10,6 +10,7 @@
 #include "net/third_party/quiche/src/common/platform/api/quiche_logging.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 quiche {
 
@@ -119,6 +120,15 @@
   return ReadBytes(tag, sizeof(*tag));
 }
 
+bool QuicheDataReader::ReadDecimal64(size_t num_digits, uint64_t* result) {
+  quiche::QuicheStringPiece digits;
+  if (!ReadStringPiece(&digits, num_digits)) {
+    return false;
+  }
+
+  return QuicheTextUtils::StringToUint64(digits, result);
+}
+
 quiche::QuicheStringPiece QuicheDataReader::ReadRemainingPayload() {
   quiche::QuicheStringPiece payload = PeekRemainingPayload();
   pos_ = len_;
diff --git a/common/quiche_data_reader.h b/common/quiche_data_reader.h
index cf62a16..f74f90d 100644
--- a/common/quiche_data_reader.h
+++ b/common/quiche_data_reader.h
@@ -87,6 +87,11 @@
   // endian.
   bool ReadTag(uint32_t* tag);
 
+  // Reads a sequence of a fixed number of decimal digits, parses them as an
+  // unsigned integer and returns them as a uint64_t.  Forwards internal
+  // iterator on success, may forward it even in case of failure.
+  bool ReadDecimal64(size_t num_digits, uint64_t* result);
+
   // Returns the remaining payload as a quiche::QuicheStringPiece.
   //
   // NOTE: Does not copy but rather references strings in the underlying buffer.
diff --git a/quic/core/crypto/certificate_view.cc b/quic/core/crypto/certificate_view.cc
index 96486c3..a5c4052 100644
--- a/quic/core/crypto/certificate_view.cc
+++ b/quic/core/crypto/certificate_view.cc
@@ -4,6 +4,7 @@
 
 #include "net/third_party/quiche/src/quic/core/crypto/certificate_view.h"
 
+#include <algorithm>
 #include <cstdint>
 #include <memory>
 #include <string>
@@ -17,6 +18,8 @@
 #include "third_party/boringssl/src/include/openssl/rsa.h"
 #include "third_party/boringssl/src/include/openssl/ssl.h"
 #include "net/third_party/quiche/src/quic/core/crypto/boring_utils.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/quic/platform/api/quic_ip_address.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
@@ -24,10 +27,13 @@
 #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"
+#include "net/third_party/quiche/src/common/platform/api/quiche_time_utils.h"
+#include "net/third_party/quiche/src/common/quiche_data_reader.h"
+
+namespace {
 
 // The literals below were encoded using `ascii2der | xxd -i`.  The comments
 // above the literals are the contents in the der2ascii syntax.
-namespace {
 
 // X.509 version 3 (version numbering starts with zero).
 // INTEGER { 2 }
@@ -94,6 +100,40 @@
 
 namespace quic {
 
+quiche::QuicheOptional<quic::QuicWallTime> ParseDerTime(
+    unsigned tag,
+    quiche::QuicheStringPiece payload) {
+  if (tag != CBS_ASN1_GENERALIZEDTIME && tag != CBS_ASN1_UTCTIME) {
+    QUIC_BUG << "Invalid tag supplied for a DER timestamp";
+    return QuicheNullOpt;
+  }
+
+  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 QuicheNullOpt;
+  }
+
+  if (tag == CBS_ASN1_UTCTIME) {
+    DCHECK_LE(year, 100u);
+    year += (year >= 50) ? 1900 : 2000;
+  }
+
+  const quiche::QuicheOptional<int64_t> unix_time =
+      quiche::QuicheUtcDateTimeToUnixSeconds(year, month, day, hour, minute,
+                                             second);
+  if (!unix_time.has_value() || *unix_time < 0) {
+    return QuicheNullOpt;
+  }
+  return QuicWallTime::FromUNIXSeconds(*unix_time);
+}
+
 PemReadResult ReadNextPemMessage(std::istream* input) {
   constexpr quiche::QuicheStringPiece kPemBegin = "-----BEGIN ";
   constexpr quiche::QuicheStringPiece kPemEnd = "-----END ";
@@ -215,6 +255,25 @@
     return nullptr;
   }
 
+  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;
+  }
+  quiche::QuicheOptional<QuicWallTime> not_before_parsed =
+      ParseDerTime(not_before_tag, CbsToStringPiece(not_before));
+  quiche::QuicheOptional<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";
diff --git a/quic/core/crypto/certificate_view.h b/quic/core/crypto/certificate_view.h
index 286226d..870beb7 100644
--- a/quic/core/crypto/certificate_view.h
+++ b/quic/core/crypto/certificate_view.h
@@ -13,8 +13,11 @@
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
 #include "net/third_party/quiche/src/quic/core/crypto/boring_utils.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_export.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 
 namespace quic {
@@ -46,6 +49,8 @@
   // 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<quiche::QuicheStringPiece>& subject_alt_name_domains()
@@ -64,6 +69,9 @@
  private:
   CertificateView() = default;
 
+  QuicWallTime validity_start_ = QuicWallTime::Zero();
+  QuicWallTime validity_end_ = QuicWallTime::Zero();
+
   // Public key parsed from SPKI.
   bssl::UniquePtr<EVP_PKEY> public_key_;
 
@@ -103,6 +111,12 @@
   bssl::UniquePtr<EVP_PKEY> private_key_;
 };
 
+// Parses a DER time based on the specified ASN.1 tag.  Exposed primarily for
+// testing.
+quiche::QuicheOptional<quic::QuicWallTime> ParseDerTime(
+    unsigned tag,
+    quiche::QuicheStringPiece payload);
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_VIEW_H_
diff --git a/quic/core/crypto/certificate_view_test.cc b/quic/core/crypto/certificate_view_test.cc
index 833351c..a715fd0 100644
--- a/quic/core/crypto/certificate_view_test.cc
+++ b/quic/core/crypto/certificate_view_test.cc
@@ -8,19 +8,23 @@
 #include <sstream>
 
 #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 "net/third_party/quiche/src/quic/core/quic_time.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.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"
+#include "net/third_party/quiche/src/common/platform/api/quiche_time_utils.h"
 
 namespace quic {
 namespace test {
 namespace {
 
-using testing::ElementsAre;
-using testing::HasSubstr;
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+using ::testing::Optional;
 
 TEST(CertificateViewTest, PemParser) {
   std::stringstream stream(kTestCertificatePem);
@@ -45,6 +49,13 @@
   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);
 }
 
 TEST(CertificateViewTest, ParseCertWithUnknownSanType) {
@@ -120,6 +131,47 @@
   EXPECT_TRUE(legacy_key->MatchesPublicKey(*view));
 }
 
+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, ""), QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.001Z"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024Q"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024-0500"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "700101000024ZZ"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.00Z"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.Z"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "197O0101000024Z"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "19700101000024.0O1Z"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "-9700101000024Z"),
+            QuicheNullOpt);
+  EXPECT_EQ(ParseDerTime(CBS_ASN1_GENERALIZEDTIME, "1970-101000024Z"),
+            QuicheNullOpt);
+
+  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"),
+            QuicheNullOpt);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_time.h b/quic/core/quic_time.h
index adecbcd..93ea308 100644
--- a/quic/core/quic_time.h
+++ b/quic/core/quic_time.h
@@ -184,6 +184,10 @@
   QUIC_TIME_WARN_UNUSED_RESULT QuicWallTime
   Subtract(QuicTime::Delta delta) const;
 
+  bool operator==(const QuicWallTime& other) const {
+    return microseconds_ == other.microseconds_;
+  }
+
  private:
   explicit constexpr QuicWallTime(uint64_t microseconds)
       : microseconds_(microseconds) {}