Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
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::ds::EpochClock::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/epoch_clock.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::ds::EpochClock::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::ds::EpochClock::time_point& now) const = 0;

/** The subject name of the certificate */
[[nodiscard]] virtual std::string subject() const = 0;
Expand Down
36 changes: 36 additions & 0 deletions include/ccf/ds/epoch_clock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once

#include <chrono>

namespace ccf::ds
{
// 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 EpochClock
{
using duration = std::chrono::seconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<EpochClock>;
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));
}
};
}
21 changes: 15 additions & 6 deletions include/ccf/ds/x509_time_fmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#pragma once

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

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

static inline std::string to_x509_time_string(
const ccf::ds::EpochClock::time_point& time)
{
return to_x509_time_string(fmt::gmtime(EpochClock::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::ds::EpochClock::time_point time_point_from_string(
const std::string& time)
{
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
Expand All @@ -44,7 +52,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::ds::EpochClock::from_time_t(timegm(&t));
r -= std::chrono::seconds(t.tm_gmtoff);
return r;
}
Expand Down Expand Up @@ -94,16 +102,17 @@ namespace ccf::ds
continue;
}

system_clock::time_point r = (sys_days)date;
std::chrono::system_clock::time_point tp = sys_days(date);
if (rs >= 6)
{
r += hours(h) + minutes(mn) + microseconds((long)(s * 1e6));
tp += hours(h) + minutes(mn) + microseconds((long)(s * 1e6));
}
if (rs >= 8)
{
r -= hours(oh) + minutes(om);
tp -= hours(oh) + minutes(om);
}
return r;

return ccf::ds::EpochClock::from_time_t(system_clock::to_time_t(tp));
}
}
}
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::ds::EpochClock::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::ds::EpochClock::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::ds::EpochClock::time_point& now) const override;

double remaining_percentage(
const std::chrono::system_clock::time_point& now) const override;
const ccf::ds::EpochClock::time_point& now) const override;

std::string subject() const override;
};
Expand Down
74 changes: 68 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::ds::EpochClock::now() - 24h);

return ccf::crypto::create_self_signed_cert(
kp, name, {}, valid_from, certificate_validity_period_days);
Expand Down Expand Up @@ -781,6 +781,68 @@ 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");

time_str = "0000-01-01 00:00:00";
tp = ccf::ds::time_point_from_string(time_str);
conv = ccf::ds::to_x509_time_string(tp);
CHECK(conv == "00000101000000Z");
}
}

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 +947,7 @@ TEST_CASE("AES-GCM convenience functions")

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

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

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

Expand Down Expand Up @@ -1415,7 +1477,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::ds::EpochClock::now() - 24h);
auto valid_to = compute_cert_valid_to_string(
valid_from, certificate_validity_period_days);
std::vector<SubjectAltName> subject_alt_names = {};
Expand Down
26 changes: 26 additions & 0 deletions tests/js-custom-authorization/custom_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,32 @@ def create_keypair(local_id, valid_from, validity_days):
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "Not After" in parse_error_message(r), r

LOG.info("Long-lived cert doesn't wraparound")
local_user_id = "long_lived"
valid_from = datetime.datetime.utcnow()
create_keypair(local_user_id, valid_from, 1_000_000)
network.consortium.add_user(primary, local_user_id)

with primary.client(local_user_id) as c:
r = c.get("/app/cert")
assert r.status_code == HTTPStatus.OK, r

LOG.info("Future Not-Before doesn't wraparound")
local_user_id = "distant_future"
# system_clock max representable time is currently 2262-04-11, so use a date after that to check for wraparound
valid_from = datetime.datetime(year=2262, month=4, day=12)
create_keypair(local_user_id, valid_from, 4)
network.consortium.add_user(primary, local_user_id)

with primary.client(local_user_id) as c:
r = c.get("/app/cert")
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
expected = (
f"certificate's Not Before validity period {int(valid_from.timestamp())}"
)
actual = parse_error_message(r)
assert expected in actual, r

return network


Expand Down
Loading