diff --git a/webrtc/base/fakesslidentity.h b/webrtc/base/fakesslidentity.h index 7926580e7b..69d590b5eb 100644 --- a/webrtc/base/fakesslidentity.h +++ b/webrtc/base/fakesslidentity.h @@ -45,6 +45,7 @@ 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; } void set_digest_algorithm(const std::string& algorithm) { digest_algorithm_ = algorithm; } diff --git a/webrtc/base/opensslidentity.cc b/webrtc/base/opensslidentity.cc index 7894b4887c..7185571102 100644 --- a/webrtc/base/opensslidentity.cc +++ b/webrtc/base/opensslidentity.cc @@ -96,6 +96,7 @@ static X509* MakeCertificate(EVP_PKEY* pkey, const SSLIdentityParams& params) { X509* x509 = NULL; BIGNUM* serial_number = NULL; X509_NAME* name = NULL; + time_t epoch_off = 0; // Time offset since epoch. if ((x509=X509_new()) == NULL) goto error; @@ -130,8 +131,8 @@ static X509* MakeCertificate(EVP_PKEY* pkey, const SSLIdentityParams& params) { !X509_set_issuer_name(x509, name)) goto error; - if (!X509_gmtime_adj(X509_get_notBefore(x509), params.not_before) || - !X509_gmtime_adj(X509_get_notAfter(x509), params.not_after)) + if (!X509_time_adj(X509_get_notBefore(x509), params.not_before, &epoch_off) || + !X509_time_adj(X509_get_notAfter(x509), params.not_after, &epoch_off)) goto error; if (!X509_sign(x509, pkey, EVP_sha256())) @@ -373,6 +374,22 @@ void OpenSSLCertificate::AddReference() const { #endif } +// Documented in sslidentity.h. +int64_t OpenSSLCertificate::CertificateExpirationTime() const { + ASN1_TIME* expire_time = X509_get_notAfter(x509_); + bool long_format; + + if (expire_time->type == V_ASN1_UTCTIME) { + long_format = false; + } else if (expire_time->type == V_ASN1_GENERALIZEDTIME) { + long_format = true; + } else { + return -1; + } + + return ASN1TimeToSec(expire_time->data, expire_time->length, long_format); +} + OpenSSLIdentity::OpenSSLIdentity(OpenSSLKeyPair* key_pair, OpenSSLCertificate* certificate) : key_pair_(key_pair), certificate_(certificate) { @@ -401,8 +418,9 @@ OpenSSLIdentity* OpenSSLIdentity::Generate(const std::string& common_name, SSLIdentityParams params; params.key_params = key_params; params.common_name = common_name; - params.not_before = CERTIFICATE_WINDOW; - params.not_after = CERTIFICATE_LIFETIME; + time_t now = time(NULL); + params.not_before = now + CERTIFICATE_WINDOW; + params.not_after = now + CERTIFICATE_LIFETIME; return GenerateInternal(params); } diff --git a/webrtc/base/opensslidentity.h b/webrtc/base/opensslidentity.h index f957ef2288..c8aa69a76e 100644 --- a/webrtc/base/opensslidentity.h +++ b/webrtc/base/opensslidentity.h @@ -87,6 +87,8 @@ class OpenSSLCertificate : public SSLCertificate { bool GetSignatureDigestAlgorithm(std::string* algorithm) const override; bool GetChain(SSLCertChain** chain) const override; + int64_t CertificateExpirationTime() const override; + private: void AddReference() const; diff --git a/webrtc/base/sslidentity.cc b/webrtc/base/sslidentity.cc index 180e60c58b..5f6b6869dd 100644 --- a/webrtc/base/sslidentity.cc +++ b/webrtc/base/sslidentity.cc @@ -15,6 +15,7 @@ #include "webrtc/base/sslidentity.h" +#include #include #include "webrtc/base/base64.h" @@ -177,4 +178,74 @@ SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, #endif // SSL_USE_OPENSSL +// Read |n| bytes from ASN1 number string at *|pp| and return the numeric value. +// Update *|pp| and *|np| to reflect number of read bytes. +static inline int ASN1ReadInt(const unsigned char** pp, size_t* np, size_t n) { + const unsigned char* p = *pp; + int x = 0; + for (size_t i = 0; i < n; i++) + x = 10 * x + p[i] - '0'; + *pp = p + n; + *np = *np - n; + return x; +} + +int64_t ASN1TimeToSec(const unsigned char* s, size_t length, bool long_format) { + size_t bytes_left = length; + + // Make sure the string ends with Z. Doing it here protects the strspn call + // from running off the end of the string in Z's absense. + if (length == 0 || s[length - 1] != 'Z') + return -1; + + // Make sure we only have ASCII digits so that we don't need to clutter the + // code below and ASN1ReadInt with error checking. + size_t n = strspn(reinterpret_cast(s), "0123456789"); + if (n + 1 != length) + return -1; + + int year; + + // Read out ASN1 year, in either 2-char "UTCTIME" or 4-char "GENERALIZEDTIME" + // format. Both format use UTC in this context. + if (long_format) { + // ASN1 format: yyyymmddhh[mm[ss[.fff]]]Z where the Z is literal, but + // RFC 5280 requires us to only support exactly yyyymmddhhmmssZ. + + if (bytes_left < 11) + return -1; + + year = ASN1ReadInt(&s, &bytes_left, 4); + year -= 1900; + } else { + // ASN1 format: yymmddhhmm[ss]Z where the Z is literal, but RFC 5280 + // requires us to only support exactly yymmddhhmmssZ. + + if (bytes_left < 9) + return -1; + + year = ASN1ReadInt(&s, &bytes_left, 2); + if (year < 50) // Per RFC 5280 4.1.2.5.1 + year += 100; + } + + std::tm tm; + tm.tm_year = year; + + // Read out remaining ASN1 time data and store it in |tm| in documented + // std::tm format. + tm.tm_mon = ASN1ReadInt(&s, &bytes_left, 2) - 1; + tm.tm_mday = ASN1ReadInt(&s, &bytes_left, 2); + tm.tm_hour = ASN1ReadInt(&s, &bytes_left, 2); + tm.tm_min = ASN1ReadInt(&s, &bytes_left, 2); + tm.tm_sec = ASN1ReadInt(&s, &bytes_left, 2); + + if (bytes_left != 1) { + // Now just Z should remain. Its existence was asserted above. + return -1; + } + + return TmToSeconds(tm); +} + } // namespace rtc diff --git a/webrtc/base/sslidentity.h b/webrtc/base/sslidentity.h index cf9942637e..b8063cee15 100644 --- a/webrtc/base/sslidentity.h +++ b/webrtc/base/sslidentity.h @@ -19,6 +19,7 @@ #include "webrtc/base/buffer.h" #include "webrtc/base/messagedigest.h" +#include "webrtc/base/timeutils.h" namespace rtc { @@ -68,6 +69,9 @@ class SSLCertificate { unsigned char* digest, size_t size, size_t* length) const = 0; + + // Returns the time in seconds relative to epoch. + virtual int64_t CertificateExpirationTime() const = 0; }; // SSLCertChain is a simple wrapper for a vector of SSLCertificates. It serves @@ -168,8 +172,8 @@ KeyType IntKeyTypeFamilyToKeyType(int key_type_family); // random string will be used. struct SSLIdentityParams { std::string common_name; - int not_before; // offset from current time in seconds. - int not_after; // offset from current time in seconds. + time_t not_before; // Absolute time since epoch in seconds. + time_t not_after; // Absolute time since epoch in seconds. KeyParams key_params; }; @@ -217,6 +221,11 @@ class SSLIdentity { size_t length); }; +// Convert from ASN1 time as restricted by RFC 5280 to seconds from 1970-01-01 +// 00.00 ("epoch"). If the ASN1 time cannot be read, return -1. The data at +// |s| is not 0-terminated; its char count is defined by |length|. +int64_t ASN1TimeToSec(const unsigned char* s, size_t length, bool long_format); + extern const char kPemTypeCertificate[]; extern const char kPemTypeRsaPrivateKey[]; extern const char kPemTypeEcPrivateKey[]; diff --git a/webrtc/base/sslidentity_unittest.cc b/webrtc/base/sslidentity_unittest.cc index e8df41506b..3582edb4a4 100644 --- a/webrtc/base/sslidentity_unittest.cc +++ b/webrtc/base/sslidentity_unittest.cc @@ -11,6 +11,7 @@ #include #include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" #include "webrtc/base/ssladapter.h" #include "webrtc/base/sslidentity.h" @@ -295,3 +296,119 @@ TEST_F(SSLIdentityTest, PemDerConversion) { TEST_F(SSLIdentityTest, GetSignatureDigestAlgorithm) { TestGetSignatureDigestAlgorithm(); } + +class SSLIdentityExpirationTest : public testing::Test { + public: + SSLIdentityExpirationTest() { + // Set use of the test RNG to get deterministic expiration timestamp. + rtc::SetRandomTestMode(true); + } + ~SSLIdentityExpirationTest() { + // Put it back for the next test. + rtc::SetRandomTestMode(false); + } + + void TestASN1TimeToSec() { + struct asn_example { + const char* string; + bool long_format; + int64_t want; + } static const data[] = { + // Valid examples. + {"19700101000000Z", true, 0}, + {"700101000000Z", false, 0}, + {"19700101000001Z", true, 1}, + {"700101000001Z", false, 1}, + {"19700101000100Z", true, 60}, + {"19700101000101Z", true, 61}, + {"19700101010000Z", true, 3600}, + {"19700101010001Z", true, 3601}, + {"19700101010100Z", true, 3660}, + {"19700101010101Z", true, 3661}, + {"710911012345Z", false, 53400225}, + {"20000101000000Z", true, 946684800}, + {"20000101000000Z", true, 946684800}, + {"20151130140156Z", true, 1448892116}, + {"151130140156Z", false, 1448892116}, + {"20491231235959Z", true, 2524607999}, + {"491231235959Z", false, 2524607999}, + {"20500101000000Z", true, 2524607999+1}, + {"20700101000000Z", true, 3155760000}, + {"21000101000000Z", true, 4102444800}, + {"24000101000000Z", true, 13569465600}, + + // Invalid examples. + {"19700101000000", true, -1}, // missing Z long format + {"19700101000000X", true, -1}, // X instead of Z long format + {"197001010000000", true, -1}, // 0 instead of Z long format + {"1970010100000000Z", true, -1}, // excess digits long format + {"700101000000", false, -1}, // missing Z short format + {"700101000000X", false, -1}, // X instead of Z short format + {"7001010000000", false, -1}, // 0 instead of Z short format + {"70010100000000Z", false, -1}, // excess digits short format + {":9700101000000Z", true, -1}, // invalid character + {"1:700101000001Z", true, -1}, // invalid character + {"19:00101000100Z", true, -1}, // invalid character + {"197:0101000101Z", true, -1}, // invalid character + {"1970:101010000Z", true, -1}, // invalid character + {"19700:01010001Z", true, -1}, // invalid character + {"197001:1010100Z", true, -1}, // invalid character + {"1970010:010101Z", true, -1}, // invalid character + {"70010100:000Z", false, -1}, // invalid character + {"700101000:01Z", false, -1}, // invalid character + {"2000010100:000Z", true, -1}, // invalid character + {"21000101000:00Z", true, -1}, // invalid character + {"240001010000:0Z", true, -1}, // invalid character + {"500101000000Z", false, -1}, // but too old for epoch + {"691231235959Z", false, -1}, // too old for epoch + {"19611118043000Z", false, -1}, // way too old for epoch + }; + + unsigned char buf[20]; + + // Run all examples and check for the expected result. + for (const auto& entry : data) { + size_t length = strlen(entry.string); + memcpy(buf, entry.string, length); // Copy the ASN1 string... + buf[length] = rtc::CreateRandomId(); // ...and terminate it with junk. + int64_t res = rtc::ASN1TimeToSec(buf, length, entry.long_format); + LOG(LS_VERBOSE) << entry.string; + ASSERT_EQ(entry.want, res); + } + // Run all examples again, but with an invalid length. + for (const auto& entry : data) { + size_t length = strlen(entry.string); + memcpy(buf, entry.string, length); // Copy the ASN1 string... + buf[length] = rtc::CreateRandomId(); // ...and terminate it with junk. + int64_t res = rtc::ASN1TimeToSec(buf, length - 1, entry.long_format); + LOG(LS_VERBOSE) << entry.string; + ASSERT_EQ(-1, res); + } + } + + void TestExpireTime(int times) { + for (int i = 0; i < times; i++) { + rtc::SSLIdentityParams params; + params.common_name = ""; + params.not_before = 0; + // We limit the time to < 2^31 here, i.e., we stay before 2038, since else + // we hit time offset limitations in OpenSSL on some 32-bit systems. + params.not_after = rtc::CreateRandomId() % 0x80000000; + // We test just ECDSA here since what we're out to exercise here is the + // code for expiration setting and reading. + params.key_params = rtc::KeyParams::ECDSA(rtc::EC_NIST_P256); + SSLIdentity* identity = rtc::SSLIdentity::GenerateForTest(params); + EXPECT_EQ(params.not_after, + identity->certificate().CertificateExpirationTime()); + delete identity; + } + } +}; + +TEST_F(SSLIdentityExpirationTest, TestASN1TimeToSec) { + TestASN1TimeToSec(); +} + +TEST_F(SSLIdentityExpirationTest, TestExpireTime) { + TestExpireTime(500); +} diff --git a/webrtc/base/sslstreamadapter_unittest.cc b/webrtc/base/sslstreamadapter_unittest.cc index 17bf4b1020..fedfa392db 100644 --- a/webrtc/base/sslstreamadapter_unittest.cc +++ b/webrtc/base/sslstreamadapter_unittest.cc @@ -294,18 +294,20 @@ class SSLStreamAdapterTestBase : public testing::Test, client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + time_t now = time(nullptr); + rtc::SSLIdentityParams client_params; client_params.key_params = rtc::KeyParams(rtc::KT_DEFAULT); client_params.common_name = "client"; - client_params.not_before = not_before; - client_params.not_after = not_after; + client_params.not_before = now + not_before; + client_params.not_after = now + not_after; client_identity_ = rtc::SSLIdentity::GenerateForTest(client_params); rtc::SSLIdentityParams server_params; server_params.key_params = rtc::KeyParams(rtc::KT_DEFAULT); server_params.common_name = "server"; - server_params.not_before = not_before; - server_params.not_after = not_after; + server_params.not_before = now + not_before; + server_params.not_after = now + not_after; server_identity_ = rtc::SSLIdentity::GenerateForTest(server_params); client_ssl_->SetIdentity(client_identity_); diff --git a/webrtc/base/timeutils.cc b/webrtc/base/timeutils.cc index fac5b66c7e..05e9ad8243 100644 --- a/webrtc/base/timeutils.cc +++ b/webrtc/base/timeutils.cc @@ -204,4 +204,48 @@ int64_t TimestampWrapAroundHandler::Unwrap(uint32_t ts) { return unwrapped_ts; } +int64_t TmToSeconds(const std::tm& tm) { + static short int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + static short int cumul_mdays[12] = {0, 31, 59, 90, 120, 151, + 181, 212, 243, 273, 304, 334}; + int year = tm.tm_year + 1900; + int month = tm.tm_mon; + int day = tm.tm_mday - 1; // Make 0-based like the rest. + int hour = tm.tm_hour; + int min = tm.tm_min; + int sec = tm.tm_sec; + + bool expiry_in_leap_year = (year % 4 == 0 && + (year % 100 != 0 || year % 400 == 0)); + + if (year < 1970) + return -1; + if (month < 0 || month > 11) + return -1; + if (day < 0 || day >= mdays[month] + (expiry_in_leap_year && month == 2 - 1)) + return -1; + if (hour < 0 || hour > 23) + return -1; + if (min < 0 || min > 59) + return -1; + if (sec < 0 || sec > 59) + return -1; + + day += cumul_mdays[month]; + + // Add number of leap days between 1970 and the expiration year, inclusive. + day += ((year / 4 - 1970 / 4) - (year / 100 - 1970 / 100) + + (year / 400 - 1970 / 400)); + + // We will have added one day too much above if expiration is during a leap + // year, and expiration is in January or February. + if (expiry_in_leap_year && month <= 2 - 1) // |month| is zero based. + day -= 1; + + // Combine all variables into seconds from 1970-01-01 00:00 (except |month| + // which was accumulated into |day| above). + return (((static_cast + (year - 1970) * 365 + day) * 24 + hour) * 60 + min) * 60 + sec; +} + } // namespace rtc diff --git a/webrtc/base/timeutils.h b/webrtc/base/timeutils.h index bdeccc3739..3ade430947 100644 --- a/webrtc/base/timeutils.h +++ b/webrtc/base/timeutils.h @@ -11,6 +11,7 @@ #ifndef WEBRTC_BASE_TIMEUTILS_H_ #define WEBRTC_BASE_TIMEUTILS_H_ +#include #include #include "webrtc/base/basictypes.h" @@ -93,6 +94,11 @@ class TimestampWrapAroundHandler { int64_t num_wrap_; }; +// Convert from std::tm, which is relative to 1900-01-01 00:00 to number of +// seconds from 1970-01-01 00:00 ("epoch"). Don't return time_t since that +// is still 32 bits on many systems. +int64_t TmToSeconds(const std::tm& tm); + } // namespace rtc #endif // WEBRTC_BASE_TIMEUTILS_H_ diff --git a/webrtc/base/timeutils_unittest.cc b/webrtc/base/timeutils_unittest.cc index d1b9ad4f96..688658b32f 100644 --- a/webrtc/base/timeutils_unittest.cc +++ b/webrtc/base/timeutils_unittest.cc @@ -10,6 +10,7 @@ #include "webrtc/base/common.h" #include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" #include "webrtc/base/thread.h" #include "webrtc/base/timeutils.h" @@ -166,4 +167,99 @@ TEST_F(TimestampWrapAroundHandlerTest, Unwrap) { EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); } +class TmToSeconds : public testing::Test { + public: + TmToSeconds() { + // Set use of the test RNG to get deterministic expiration timestamp. + rtc::SetRandomTestMode(true); + } + ~TmToSeconds() { + // Put it back for the next test. + rtc::SetRandomTestMode(false); + } + + void TestTmToSeconds(int times) { + static char mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + for (int i = 0; i < times; i++) { + + // First generate something correct and check that TmToSeconds is happy. + int year = rtc::CreateRandomId() % 400 + 1970; + + bool leap_year = false; + if (year % 4 == 0) + leap_year = true; + if (year % 100 == 0) + leap_year = false; + if (year % 400 == 0) + leap_year = true; + + std::tm tm; + tm.tm_year = year - 1900; // std::tm is year 1900 based. + tm.tm_mon = rtc::CreateRandomId() % 12; + tm.tm_mday = rtc::CreateRandomId() % mdays[tm.tm_mon] + 1; + tm.tm_hour = rtc::CreateRandomId() % 24; + tm.tm_min = rtc::CreateRandomId() % 60; + tm.tm_sec = rtc::CreateRandomId() % 60; + int64_t t = rtc::TmToSeconds(tm); + EXPECT_TRUE(t >= 0); + + // Now damage a random field and check that TmToSeconds is unhappy. + switch (rtc::CreateRandomId() % 11) { + case 0: + tm.tm_year = 1969 - 1900; + break; + case 1: + tm.tm_mon = -1; + break; + case 2: + tm.tm_mon = 12; + break; + case 3: + tm.tm_mday = 0; + break; + case 4: + tm.tm_mday = mdays[tm.tm_mon] + (leap_year && tm.tm_mon == 1) + 1; + break; + case 5: + tm.tm_hour = -1; + break; + case 6: + tm.tm_hour = 24; + break; + case 7: + tm.tm_min = -1; + break; + case 8: + tm.tm_min = 60; + break; + case 9: + tm.tm_sec = -1; + break; + case 10: + tm.tm_sec = 60; + break; + } + EXPECT_EQ(rtc::TmToSeconds(tm), -1); + } + // Check consistency with the system gmtime_r. With time_t, we can only + // portably test dates until 2038, which is achieved by the % 0x80000000. + for (int i = 0; i < times; i++) { + time_t t = rtc::CreateRandomId() % 0x80000000; +#if defined(WEBRTC_WIN) + std::tm* tm = std::gmtime(&t); + EXPECT_TRUE(tm); + EXPECT_TRUE(rtc::TmToSeconds(*tm) == t); +#else + std::tm tm; + EXPECT_TRUE(gmtime_r(&t, &tm)); + EXPECT_TRUE(rtc::TmToSeconds(tm) == t); +#endif + } + } +}; + +TEST_F(TmToSeconds, TestTmToSeconds) { + TestTmToSeconds(100000); +} + } // namespace rtc