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, ¬_before, ¬_before_tag) || + !CBS_get_any_asn1(&validity, ¬_after, ¬_after_tag) || + CBS_len(&validity) != 0) { + QUIC_DLOG(WARNING) << "Failed to extract the validity dates"; + return nullptr; + } + 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) {}