Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed

- Only rollback uncommittable indices during become_leader (#7620)
- x509 parsing now correctly handles times validity beyond 2262. To support this, some public function signatures (`ccf::ds::time_point_from_string()`, `ccf::crypto::Verifier::remaining_seconds()`) now use `time_point`s from `ccf::nonstd::SystemClock` rather than `std::chrono::system_clock` (#7648)

## [7.0.0-dev9]

Expand Down
2 changes: 1 addition & 1 deletion include/ccf/crypto/openssl/openssl_wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ namespace ccf::crypto::OpenSSL
Unique_X509_TIME(ASN1_TIME* t) :
Unique_SSL_OBJECT(t, ASN1_TIME_free, /*check_null=*/false)
{}
Unique_X509_TIME(const std::chrono::system_clock::time_point& t) :
Unique_X509_TIME(const ccf::nonstd::SystemClock::time_point& t) :
Unique_X509_TIME(ccf::ds::to_x509_time_string(t))
{}
};
Expand Down
5 changes: 3 additions & 2 deletions include/ccf/crypto/verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "ccf/crypto/jwk.h"
#include "ccf/crypto/pem.h"
#include "ccf/crypto/rsa_public_key.h"
#include "ccf/ds/nonstd.h"

#include <chrono>

Expand Down Expand Up @@ -154,11 +155,11 @@ namespace ccf::crypto
/** The number of seconds of the validity period of the
* certificate remaining */
[[nodiscard]] virtual size_t remaining_seconds(
const std::chrono::system_clock::time_point& now) const = 0;
const ccf::nonstd::SystemClock::time_point& now) const = 0;

/** The percentage of the validity period of the certificate remaining */
[[nodiscard]] virtual double remaining_percentage(
const std::chrono::system_clock::time_point& now) const = 0;
const ccf::nonstd::SystemClock::time_point& now) const = 0;

/** The subject name of the certificate */
[[nodiscard]] virtual std::string subject() const = 0;
Expand Down
31 changes: 31 additions & 0 deletions include/ccf/ds/nonstd.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <array>
#include <cctype>
#include <chrono>
#include <ctime>
#include <regex>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -210,9 +212,38 @@ namespace ccf::nonstd
*fd = -1;
}
}

using CloseFdGuard = std::unique_ptr<int, decltype(&close_fd)>;
static inline CloseFdGuard make_close_fd_guard(int* fd)
{
return {fd, close_fd};
}

// A custom clock type for handling certificate validity periods, which are
// defined in terms of seconds since the epoch. This avoids issues with
// system_clock::time_point being unable to represent times after 2262-04-11
// 23:47:17 UTC (due to tracking nanosecond precision).
struct SystemClock
{
using duration = std::chrono::seconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<SystemClock>;
static constexpr bool is_steady = false;

static time_point now() noexcept
{
return time_point(duration(std::time(nullptr)));
}

static std::time_t to_time_t(const time_point& t) noexcept
{
return std::time_t(t.time_since_epoch().count());
}

static time_point from_time_t(std::time_t t) noexcept
{
return time_point(duration(t));
}
};
}
42 changes: 32 additions & 10 deletions include/ccf/ds/x509_time_fmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the Apache 2.0 License.
#pragma once

#define FMT_HEADER_ONLY
#include "ccf/ds/nonstd.h"

#include <chrono>
#define FMT_HEADER_ONLY
#include <fmt/chrono.h>
#include <fmt/format.h>
#include <iomanip>
Expand All @@ -20,13 +22,20 @@ namespace ccf::ds
return fmt::format("{:%Y%m%d%H%M%SZ}", time);
}

static inline std::string to_x509_time_string(
const ccf::nonstd::SystemClock::time_point& time)
{
return to_x509_time_string(
fmt::gmtime(ccf::nonstd::SystemClock::to_time_t(time)));
}

static inline std::string to_x509_time_string(
const std::chrono::system_clock::time_point& time)
{
return to_x509_time_string(fmt::gmtime(time));
}

static inline std::chrono::system_clock::time_point time_point_from_string(
static inline ccf::nonstd::SystemClock::time_point time_point_from_string(
const std::string& time)
{
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
Expand All @@ -44,7 +53,7 @@ namespace ccf::ds
auto* sres = strptime(ts, afmt, &t);
if (sres != nullptr && *sres == '\0')
{
auto r = std::chrono::system_clock::from_time_t(timegm(&t));
auto r = ccf::nonstd::SystemClock::from_time_t(timegm(&t));
r -= std::chrono::seconds(t.tm_gmtoff);
return r;
}
Expand All @@ -56,8 +65,8 @@ namespace ccf::ds
{"%04u-%02u-%02u %02u:%02u:%f %d:%02u", 8},
{"%04u-%02u-%02uT%02u:%02u:%f %d:%02u", 8},
{"%04u-%02u-%02u %02u:%02u:%f %03d %02u", 8},
{"%02u%02u%02u%02u%02u%02f%03d%02u", 8},
{"%04u%02u%02u%02u%02u%02f%03d%02u", 8},
{"%02u%02u%02u%02u%02u%f%03d%02u", 8},
{"%04u%02u%02u%02u%02u%f%03d%02u", 8},
{"%04u-%02u-%02uT%02u:%02u:%f", 6},
{"%04u-%02u-%02u %02u:%02u:%f", 6}};

Expand All @@ -76,7 +85,6 @@ namespace ccf::ds
if (rs >= 1 && rs == n)
{
using namespace std::chrono;

if (strncmp(fmt, "%02u", 4) == 0)
{
// ASN.1 two-digit year range
Expand All @@ -94,16 +102,30 @@ namespace ccf::ds
continue;
}

system_clock::time_point r = (sys_days)date;
// Build a struct tm and use timegm() to convert to time_t
// directly, avoiding system_clock::time_point which can
// overflow for dates outside ~1677-2262.
struct tm t = {};
t.tm_year = static_cast<int>(y) - 1900;
t.tm_mon = static_cast<int>(m) - 1;
t.tm_mday = static_cast<int>(d);
if (rs >= 6)
{
r += hours(h) + minutes(mn) + microseconds((long)(s * 1e6));
t.tm_hour = static_cast<int>(h);
t.tm_min = static_cast<int>(mn);
t.tm_sec = static_cast<int>(s);
}

auto tt = timegm(&t);

if (rs >= 8)
{
r -= hours(oh) + minutes(om);
auto offset_secs = oh * 3600 +
(oh < 0 ? -static_cast<int>(om) : static_cast<int>(om)) * 60;
tt -= offset_secs;
}
return r;

return ccf::nonstd::SystemClock::from_time_t(tt);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/openssl/verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ namespace ccf::crypto
}

size_t Verifier_OpenSSL::remaining_seconds(
const std::chrono::system_clock::time_point& now) const
const ccf::nonstd::SystemClock::time_point& now) const
{
auto [from, to] = validity_period();
auto tp_to = ccf::ds::time_point_from_string(to);
Expand All @@ -217,7 +217,7 @@ namespace ccf::crypto
}

double Verifier_OpenSSL::remaining_percentage(
const std::chrono::system_clock::time_point& now) const
const ccf::nonstd::SystemClock::time_point& now) const
{
auto [from, to] = validity_period();
auto tp_from = ccf::ds::time_point_from_string(from);
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/openssl/verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ namespace ccf::crypto
std::pair<std::string, std::string> validity_period() const override;

size_t remaining_seconds(
const std::chrono::system_clock::time_point& now) const override;
const ccf::nonstd::SystemClock::time_point& now) const override;

double remaining_percentage(
const std::chrono::system_clock::time_point& now) const override;
const ccf::nonstd::SystemClock::time_point& now) const override;

std::string subject() const override;
};
Expand Down
102 changes: 96 additions & 6 deletions src/crypto/test/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ ccf::crypto::Pem generate_self_signed_cert(
constexpr size_t certificate_validity_period_days = 365;
using namespace std::literals;
auto valid_from =
ccf::ds::to_x509_time_string(std::chrono::system_clock::now() - 24h);
ccf::ds::to_x509_time_string(ccf::nonstd::SystemClock::now() - 24h);

return ccf::crypto::create_self_signed_cert(
kp, name, {}, valid_from, certificate_validity_period_days);
Expand Down Expand Up @@ -750,6 +750,11 @@ TEST_CASE("Non-ASN.1 timepoint formats")
conv = ccf::ds::to_x509_time_string(tp);
REQUIRE(conv == "20220405215327Z");

time_str = "2026-02-09 05:00:00 -03:30";
tp = ccf::ds::time_point_from_string(time_str);
conv = ccf::ds::to_x509_time_string(tp);
REQUIRE(conv == "20260209083000Z");

time_str = "2022-04-07T10:37:49.567612";
tp = ccf::ds::time_point_from_string(time_str);
conv = ccf::ds::to_x509_time_string(tp);
Expand Down Expand Up @@ -781,6 +786,91 @@ TEST_CASE("Non-ASN.1 timepoint formats")
REQUIRE(conv == "20220425195619Z");
}

TEST_CASE("Timepoint bounds")
{
// Can handle values beyond bounds of system_clock::time_point
{
INFO("Beyond system_clock::time_point min");
auto time_str = "1677-09-21 00:12:44";
auto tp = ccf::ds::time_point_from_string(time_str);
auto conv = ccf::ds::to_x509_time_string(tp);
REQUIRE(conv == "16770921001244Z");

time_str = "1677-09-21 00:12:43";
tp = ccf::ds::time_point_from_string(time_str);
conv = ccf::ds::to_x509_time_string(tp);
REQUIRE(conv == "16770921001243Z");
}

{
INFO("Beyond system_clock::time_point max");
auto time_str = "2262-04-11 23:47:16";
auto tp = ccf::ds::time_point_from_string(time_str);
auto conv = ccf::ds::to_x509_time_string(tp);
REQUIRE(conv == "22620411234716Z");

time_str = "2262-04-11 23:47:17";
tp = ccf::ds::time_point_from_string(time_str);
conv = ccf::ds::to_x509_time_string(tp);
CHECK(conv == "22620411234717Z");
}

{
INFO("Approx ASN.1 format bounds");
auto time_str = "9999-12-31 23:59:59";
auto tp = ccf::ds::time_point_from_string(time_str);
auto conv = ccf::ds::to_x509_time_string(tp);
REQUIRE(conv == "99991231235959Z");

INFO("sscanf variants of near-min value");
for (auto time_str : {
"0001-02-03 04:05:06",
"0001-02-03 04:05:06.700000 +0:00",
"0001-02-03 12:14:06.700000 +8:09",
"0001-02-02 19:56:06.700000 -8:09",

"0001-02-03T04:05:06.700000 +0:00",
"0001-02-03T12:14:06.700000 +8:09",
"0001-02-02T19:56:06.700000 -8:09",

"0001-02-03 04:05:06.700000 +00 00",
"0001-02-03 12:14:06.700000 +08:09",
"0001-02-02 19:56:06.700000 -08:09",

"00010203040506.700000+0000",
"00010203121406.700000+0809",
"00010202195606.700000-0809",

"0001-02-03T04:05:06.700000",
"0001-02-03 04:05:06.700000",
})
{
tp = ccf::ds::time_point_from_string(time_str);
conv = ccf::ds::to_x509_time_string(tp);
CHECK(conv == "00010203040506Z");
}
}
}

TEST_CASE("Invalid times")
{
REQUIRE_THROWS_WITH(
ccf::ds::time_point_from_string("hello"),
"'hello' does not match any accepted time format");

REQUIRE_THROWS_WITH(
ccf::ds::time_point_from_string("Monday"),
"'Monday' does not match any accepted time format");

REQUIRE_THROWS_WITH(
ccf::ds::time_point_from_string("April 1st, 1984"),
"'April 1st, 1984' does not match any accepted time format");

REQUIRE_THROWS_WITH(
ccf::ds::time_point_from_string("1111-1111"),
"'1111-1111' does not match any accepted time format");
}

TEST_CASE("Create sign and verify certificates")
{
bool corrupt_csr = false;
Expand Down Expand Up @@ -885,7 +975,7 @@ TEST_CASE("AES-GCM convenience functions")

TEST_CASE("x509 time")
{
auto time = std::chrono::system_clock::now();
auto time = ccf::nonstd::SystemClock::now();

auto next_minute_time = time + 1min;
auto next_day_time = time + 24h;
Expand All @@ -897,8 +987,8 @@ TEST_CASE("x509 time")
{
struct Input
{
std::chrono::system_clock::time_point from;
std::chrono::system_clock::time_point to;
ccf::nonstd::SystemClock::time_point from;
ccf::nonstd::SystemClock::time_point to;
std::optional<uint32_t> maximum_validity_period_days = std::nullopt;
};
Input input;
Expand Down Expand Up @@ -933,7 +1023,7 @@ TEST_CASE("x509 time")

INFO("Adjust time");
{
std::vector<std::chrono::system_clock::time_point> times = {
std::vector<ccf::nonstd::SystemClock::time_point> times = {
time, next_day_time, next_day_time};
size_t days_offset = 100;

Expand Down Expand Up @@ -1415,7 +1505,7 @@ TEST_CASE("Do not trust non-ca certs")
constexpr size_t certificate_validity_period_days = 365;
using namespace std::literals;
auto valid_from =
ccf::ds::to_x509_time_string(std::chrono::system_clock::now() - 24h);
ccf::ds::to_x509_time_string(ccf::nonstd::SystemClock::now() - 24h);
auto valid_to = compute_cert_valid_to_string(
valid_from, certificate_validity_period_days);
std::vector<SubjectAltName> subject_alt_names = {};
Expand Down
Loading