From 20ef654174e245b3a06c9e9045bb97be9acd90cf Mon Sep 17 00:00:00 2001 From: hbos Date: Tue, 8 Dec 2015 01:42:37 -0800 Subject: [PATCH] RTCCertificate::Expires() and ::HasExpired() implemented using SSLCertificate::CertificateExpirationTime(). NOPRESUBMIT=true BUG=chromium:544894 Review URL: https://codereview.webrtc.org/1494103003 Cr-Commit-Position: refs/heads/master@{#10930} --- webrtc/base/base_tests.gyp | 1 + webrtc/base/fakesslidentity.h | 15 ++- webrtc/base/rtccertificate.cc | 14 +-- webrtc/base/rtccertificate.h | 7 +- webrtc/base/rtccertificate_unittests.cc | 116 ++++++++++++++++++++++++ webrtc/base/sslidentity.h | 3 +- 6 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 webrtc/base/rtccertificate_unittests.cc diff --git a/webrtc/base/base_tests.gyp b/webrtc/base/base_tests.gyp index 23b1f9a15e..1da93defd4 100644 --- a/webrtc/base/base_tests.gyp +++ b/webrtc/base/base_tests.gyp @@ -88,6 +88,7 @@ 'ratetracker_unittest.cc', 'referencecountedsingletonfactory_unittest.cc', 'rollingaccumulator_unittest.cc', + 'rtccertificate_unittests.cc', 'scopedptrcollection_unittest.cc', 'sha1digest_unittest.cc', 'sharedexclusivelock_unittest.cc', diff --git a/webrtc/base/fakesslidentity.h b/webrtc/base/fakesslidentity.h index 69d590b5eb..ec603a541d 100644 --- a/webrtc/base/fakesslidentity.h +++ b/webrtc/base/fakesslidentity.h @@ -25,9 +25,11 @@ class FakeSSLCertificate : public rtc::SSLCertificate { // SHA-1 is the default digest algorithm because it is available in all build // configurations used for unit testing. explicit FakeSSLCertificate(const std::string& data) - : data_(data), digest_algorithm_(DIGEST_SHA_1) {} + : data_(data), digest_algorithm_(DIGEST_SHA_1), expiration_time_(-1) {} explicit FakeSSLCertificate(const std::vector& certs) - : data_(certs.front()), digest_algorithm_(DIGEST_SHA_1) { + : data_(certs.front()), + digest_algorithm_(DIGEST_SHA_1), + expiration_time_(-1) { std::vector::const_iterator it; // Skip certs[0]. for (it = certs.begin() + 1; it != certs.end(); ++it) { @@ -45,7 +47,12 @@ class FakeSSLCertificate : public rtc::SSLCertificate { VERIFY(SSLIdentity::PemToDer(kPemTypeCertificate, data_, &der_string)); der_buffer->SetData(der_string.c_str(), der_string.size()); } - int64_t CertificateExpirationTime() const override { return -1; } + int64_t CertificateExpirationTime() const override { + return expiration_time_; + } + void SetCertificateExpirationTime(int64_t expiration_time) { + expiration_time_ = expiration_time; + } void set_digest_algorithm(const std::string& algorithm) { digest_algorithm_ = algorithm; } @@ -79,6 +86,8 @@ class FakeSSLCertificate : public rtc::SSLCertificate { std::string data_; std::vector certs_; std::string digest_algorithm_; + // Expiration time in seconds relative to epoch, 1970-01-01T00:00:00Z (UTC). + int64_t expiration_time_; }; class FakeSSLIdentity : public rtc::SSLIdentity { diff --git a/webrtc/base/rtccertificate.cc b/webrtc/base/rtccertificate.cc index a176d9080b..7b764bd72e 100644 --- a/webrtc/base/rtccertificate.cc +++ b/webrtc/base/rtccertificate.cc @@ -11,7 +11,6 @@ #include "webrtc/base/rtccertificate.h" #include "webrtc/base/checks.h" -#include "webrtc/base/timeutils.h" namespace rtc { @@ -28,13 +27,16 @@ RTCCertificate::RTCCertificate(SSLIdentity* identity) RTCCertificate::~RTCCertificate() { } -uint64_t RTCCertificate::expires_timestamp_ns() const { - // TODO(hbos): Update once SSLIdentity/SSLCertificate supports expires field. - return 0; +uint64_t RTCCertificate::Expires() const { + int64_t expires = ssl_certificate().CertificateExpirationTime(); + if (expires != -1) + return static_cast(expires) * kNumMillisecsPerSec; + // If the expiration time could not be retrieved return an expired timestamp. + return 0; // = 1970-01-01 } -bool RTCCertificate::HasExpired() const { - return expires_timestamp_ns() <= TimeNanos(); +bool RTCCertificate::HasExpired(uint64_t now) const { + return Expires() <= now; } const SSLCertificate& RTCCertificate::ssl_certificate() const { diff --git a/webrtc/base/rtccertificate.h b/webrtc/base/rtccertificate.h index d238938ae1..600739bc86 100644 --- a/webrtc/base/rtccertificate.h +++ b/webrtc/base/rtccertificate.h @@ -27,8 +27,11 @@ class RTCCertificate : public RefCountInterface { // Takes ownership of |identity|. static scoped_refptr Create(scoped_ptr identity); - uint64_t expires_timestamp_ns() const; - bool HasExpired() const; + // Returns the expiration time in ms relative to epoch, 1970-01-01T00:00:00Z. + uint64_t Expires() const; + // Checks if the certificate has expired, where |now| is expressed in ms + // relative to epoch, 1970-01-01T00:00:00Z. + bool HasExpired(uint64_t now) const; const SSLCertificate& ssl_certificate() const; // TODO(hbos): If possible, remove once RTCCertificate and its diff --git a/webrtc/base/rtccertificate_unittests.cc b/webrtc/base/rtccertificate_unittests.cc new file mode 100644 index 0000000000..3e9439fedb --- /dev/null +++ b/webrtc/base/rtccertificate_unittests.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/checks.h" +#include "webrtc/base/fakesslidentity.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/rtccertificate.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +namespace { + +static const char* kTestCertCommonName = "RTCCertificateTest's certificate"; + +} // namespace + +class RTCCertificateTest : public testing::Test { + public: + RTCCertificateTest() {} + ~RTCCertificateTest() {} + + protected: + // Timestamp note: + // All timestamps in this unittest are expressed in number of seconds since + // epoch, 1970-01-01T00:00:00Z (UTC). The RTCCertificate interface uses ms, + // but only seconds-precision is supported by SSLCertificate. To make the + // tests clearer we convert everything to seconds since the precision matters + // when generating certificates or comparing timestamps. + // As a result, ExpiresSeconds and HasExpiredSeconds are used instead of + // RTCCertificate::Expires and ::HasExpired for ms -> s conversion. + + uint64_t NowSeconds() const { + return TimeNanos() / kNumNanosecsPerSec; + } + + uint64_t ExpiresSeconds(const scoped_refptr& cert) const { + uint64_t exp_ms = cert->Expires(); + uint64_t exp_s = exp_ms / kNumMillisecsPerSec; + // Make sure this did not result in loss of precision. + RTC_CHECK_EQ(exp_s * kNumMillisecsPerSec, exp_ms); + return exp_s; + } + + bool HasExpiredSeconds(const scoped_refptr& cert, + uint64_t now_s) const { + return cert->HasExpired(now_s * kNumMillisecsPerSec); + } + + // An RTC_CHECK ensures that |expires_s| this is in valid range of time_t as + // is required by SSLIdentityParams. On some 32-bit systems time_t is limited + // to < 2^31. On such systems this will fail for expiration times of year 2038 + // or later. + scoped_refptr GenerateCertificateWithExpires( + uint64_t expires_s) const { + RTC_CHECK(IsValueInRangeForNumericType(expires_s)); + + SSLIdentityParams params; + params.common_name = kTestCertCommonName; + params.not_before = 0; + params.not_after = static_cast(expires_s); + // Certificate type does not matter for our purposes, using ECDSA because it + // is fast to generate. + params.key_params = KeyParams::ECDSA(); + + scoped_ptr identity(SSLIdentity::GenerateForTest(params)); + return RTCCertificate::Create(identity.Pass()); + } +}; + +TEST_F(RTCCertificateTest, NewCertificateNotExpired) { + // Generate a real certificate without specifying the expiration time. + // Certificate type doesn't matter, using ECDSA because it's fast to generate. + scoped_ptr identity( + SSLIdentity::Generate(kTestCertCommonName, KeyParams::ECDSA())); + scoped_refptr certificate = + RTCCertificate::Create(identity.Pass()); + + uint64_t now = NowSeconds(); + EXPECT_FALSE(HasExpiredSeconds(certificate, now)); + // Even without specifying the expiration time we would expect it to be valid + // for at least half an hour. + EXPECT_FALSE(HasExpiredSeconds(certificate, now + 30*60)); +} + +TEST_F(RTCCertificateTest, UsesExpiresAskedFor) { + uint64_t now = NowSeconds(); + scoped_refptr certificate = + GenerateCertificateWithExpires(now); + EXPECT_EQ(now, ExpiresSeconds(certificate)); +} + +TEST_F(RTCCertificateTest, ExpiresInOneSecond) { + // Generate a certificate that expires in 1s. + uint64_t now = NowSeconds(); + scoped_refptr certificate = + GenerateCertificateWithExpires(now + 1); + // Now it should not have expired. + EXPECT_FALSE(HasExpiredSeconds(certificate, now)); + // In 2s it should have expired. + EXPECT_TRUE(HasExpiredSeconds(certificate, now + 2)); +} + +} // namespace rtc diff --git a/webrtc/base/sslidentity.h b/webrtc/base/sslidentity.h index b8063cee15..a143ee4108 100644 --- a/webrtc/base/sslidentity.h +++ b/webrtc/base/sslidentity.h @@ -70,7 +70,8 @@ class SSLCertificate { size_t size, size_t* length) const = 0; - // Returns the time in seconds relative to epoch. + // Returns the time in seconds relative to epoch, 1970-01-01T00:00:00Z (UTC), + // or -1 if an expiration time could not be retrieved. virtual int64_t CertificateExpirationTime() const = 0; };