Merge "Keystore 2.0 certificate utils: Set time by string."

This commit is contained in:
Treehugger Robot 2021-06-07 09:36:27 +00:00 committed by Gerrit Code Review
commit fbeb0a69bc
3 changed files with 80 additions and 44 deletions

View file

@ -24,10 +24,13 @@
#include <functional>
#include <limits>
#include <string>
#include <variant>
#include <vector>
#ifndef __LP64__
#include <time64.h>
#endif
namespace keystore {
namespace {
@ -168,45 +171,42 @@ makeKeyUsageExtension(bool is_signing_key, bool is_encryption_key, bool is_cert_
return key_usage;
}
template <typename Out, typename In> static Out saturate(In in) {
if constexpr (std::is_signed_v<Out> == std::is_signed_v<In>) {
if constexpr (sizeof(Out) >= sizeof(In)) {
// Same sign, and In fits into Out. Cast is lossless.
return static_cast<Out>(in);
} else {
// Out is smaller than In we may need to truncate.
// We pick the smaller of `out::max()` and the greater of `out::min()` and `in`.
return static_cast<Out>(
std::min(static_cast<In>(std::numeric_limits<Out>::max()),
std::max(static_cast<In>(std::numeric_limits<Out>::min()), in)));
// TODO Once boring ssl can take int64_t instead of time_t we can go back to using
// ASN1_TIME_set: https://bugs.chromium.org/p/boringssl/issues/detail?id=416
std::optional<std::array<char, 16>> toTimeString(int64_t timeMillis) {
struct tm time;
// If timeMillis is negative the rounding direction should still be to the nearest previous
// second.
if (timeMillis < 0 && __builtin_add_overflow(timeMillis, -999, &timeMillis)) {
return std::nullopt;
}
} else {
// So we have different signs. This puts the lower bound at 0 because either input or output
// is unsigned. The upper bound is max of the smaller type or, if they are equal the max of
// the signed type.
if constexpr (std::is_signed_v<Out>) {
if constexpr (sizeof(Out) > sizeof(In)) {
return static_cast<Out>(in);
} else {
// Because `out` is the signed one, the lower bound of `in` is 0 and fits into
// `out`. We just have to compare the maximum and we do it in type In because it has
// a greater range than Out, so Out::max() is guaranteed to fit.
return static_cast<Out>(
std::min(static_cast<In>(std::numeric_limits<Out>::max()), in));
#if defined(__LP64__)
time_t timeSeconds = timeMillis / 1000;
if (gmtime_r(&timeSeconds, &time) == nullptr) {
return std::nullopt;
}
#else
time64_t timeSeconds = timeMillis / 1000;
if (gmtime64_r(&timeSeconds, &time) == nullptr) {
return std::nullopt;
}
#endif
std::array<char, 16> buffer;
if (__builtin_add_overflow(time.tm_year, 1900, &time.tm_year)) {
return std::nullopt;
}
if (time.tm_year >= 1950 && time.tm_year < 2050) {
// UTCTime according to RFC5280 4.1.2.5.1.
snprintf(buffer.data(), buffer.size(), "%02d%02d%02d%02d%02d%02dZ", time.tm_year % 100,
time.tm_mon + 1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec);
} else if (time.tm_year >= 0 && time.tm_year < 10000) {
// GeneralizedTime according to RFC5280 4.1.2.5.2.
snprintf(buffer.data(), buffer.size(), "%04d%02d%02d%02d%02d%02dZ", time.tm_year,
time.tm_mon + 1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec);
} else {
// Out is unsigned. So we can return 0 if in is negative.
if (in < 0) return 0;
if constexpr (sizeof(Out) >= sizeof(In)) {
// If Out is wider or equal we can assign lossless.
return static_cast<Out>(in);
} else {
// Otherwise we have to take the minimum of Out::max() and `in`.
return static_cast<Out>(
std::min(static_cast<In>(std::numeric_limits<Out>::max()), in));
}
}
return std::nullopt;
}
return buffer;
}
// Creates a rump certificate structure with serial, subject and issuer names, as well as
@ -260,19 +260,24 @@ makeCertRump(std::optional<std::reference_wrapper<const std::vector<uint8_t>>> s
return std::get<CertUtilsError>(subjectName);
}
time_t notBeforeTime = saturate<time_t>(activeDateTimeMilliSeconds / 1000);
auto notBeforeTime = toTimeString(activeDateTimeMilliSeconds);
if (!notBeforeTime) {
return CertUtilsError::TimeError;
}
// Set activation date.
ASN1_TIME_Ptr notBefore(ASN1_TIME_new());
if (!notBefore || !ASN1_TIME_set(notBefore.get(), notBeforeTime) ||
if (!notBefore || !ASN1_TIME_set_string(notBefore.get(), notBeforeTime->data()) ||
!X509_set_notBefore(certificate.get(), notBefore.get() /* Don't release; copied */))
return CertUtilsError::BoringSsl;
// Set expiration date.
time_t notAfterTime;
notAfterTime = saturate<time_t>(usageExpireDateTimeMilliSeconds / 1000);
auto notAfterTime = toTimeString(usageExpireDateTimeMilliSeconds);
if (!notAfterTime) {
return CertUtilsError::TimeError;
}
ASN1_TIME_Ptr notAfter(ASN1_TIME_new());
if (!notAfter || !ASN1_TIME_set(notAfter.get(), notAfterTime) ||
if (!notAfter || !ASN1_TIME_set_string(notAfter.get(), notAfterTime->data()) ||
!X509_set_notAfter(certificate.get(), notAfter.get() /* Don't release; copied */)) {
return CertUtilsError::BoringSsl;
}

View file

@ -54,6 +54,7 @@ class CertUtilsError {
InvalidArgument,
UnexpectedNullPointer,
SignatureFailed,
TimeError,
};
private:
@ -137,6 +138,16 @@ enum class Padding {
PSS,
};
/**
* Takes an int64_t representing UNIX epoch time in milliseconds and turns it into a UTCTime
* or GeneralizedTime string depending on whether the year is in the interval [1950 .. 2050).
* Note: The string returned in the array buffer is NUL terminated and of length 13 (UTCTime)
* or 15 (GeneralizedTime).
* @param timeMillis
* @return UTCTime or GeneralizedTime string.
*/
std::optional<std::array<char, 16>> toTimeString(int64_t timeMillis);
/**
* Sets the signature specifier of the certificate and the signature according to the parameters
* c. Then it signs the certificate with the `sign` callback.

View file

@ -315,3 +315,23 @@ TEST_P(CertificateUtilsWithRsa, CertSigningWithCallbackRsa) {
EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get()));
ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get()));
}
TEST(TimeStringTests, toTimeStringTest) {
// Two test vectors that need to result in UTCTime
ASSERT_EQ(std::string(toTimeString(1622758591000)->data()), std::string("210603221631Z"));
ASSERT_EQ(std::string(toTimeString(0)->data()), std::string("700101000000Z"));
// Two test vectors that need to result in GeneralizedTime.
ASSERT_EQ(std::string(toTimeString(16227585910000)->data()), std::string("24840325064510Z"));
ASSERT_EQ(std::string(toTimeString(-1622758591000)->data()), std::string("19180731014329Z"));
// Highest possible UTCTime
ASSERT_EQ(std::string(toTimeString(2524607999999)->data()), "491231235959Z");
// And one millisecond later must be GeneralizedTime.
ASSERT_EQ(std::string(toTimeString(2524608000000)->data()), "20500101000000Z");
// Earliest possible UTCTime
ASSERT_EQ(std::string(toTimeString(-631152000000)->data()), "500101000000Z");
// And one millisecond earlier must be GeneralizedTime.
// This also checks that the rounding direction does not flip when the input is negative.
ASSERT_EQ(std::string(toTimeString(-631152000001)->data()), "19491231235959Z");
}