Skip to content

Commit d886b28

Browse files
authored
[release/6.x] Cherry pick: Time point parsing bounds (#7648) (#7656)
1 parent e634ba0 commit d886b28

File tree

11 files changed

+232
-27
lines changed

11 files changed

+232
-27
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## [6.0.22]
9+
10+
[6.0.22]: https://github.com/microsoft/CCF/releases/tag/ccf-6.0.22
11+
12+
### Fixed
13+
14+
- 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)
15+
816
## [6.0.21]
917

1018
[6.0.21]: https://github.com/microsoft/CCF/releases/tag/ccf-6.0.21

include/ccf/crypto/openssl/openssl_wrappers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ namespace ccf::crypto
363363
Unique_X509_TIME(ASN1_TIME* t) :
364364
Unique_SSL_OBJECT(t, ASN1_TIME_free, /*check_null=*/false)
365365
{}
366-
Unique_X509_TIME(const std::chrono::system_clock::time_point& t) :
366+
Unique_X509_TIME(const ccf::nonstd::SystemClock::time_point& t) :
367367
Unique_X509_TIME(ccf::ds::to_x509_time_string(t))
368368
{}
369369
};

include/ccf/crypto/verifier.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "ccf/crypto/key_pair.h"
77
#include "ccf/crypto/pem.h"
88
#include "ccf/crypto/public_key.h"
9+
#include "ccf/ds/nonstd.h"
910

1011
#include <chrono>
1112

@@ -208,11 +209,11 @@ namespace ccf::crypto
208209
/** The number of seconds of the validity period of the
209210
* certificate remaining */
210211
virtual size_t remaining_seconds(
211-
const std::chrono::system_clock::time_point& now) const = 0;
212+
const ccf::nonstd::SystemClock::time_point& now) const = 0;
212213

213214
/** The percentage of the validity period of the certificate remaining */
214215
virtual double remaining_percentage(
215-
const std::chrono::system_clock::time_point& now) const = 0;
216+
const ccf::nonstd::SystemClock::time_point& now) const = 0;
216217

217218
/** The subject name of the certificate */
218219
virtual std::string subject() const = 0;

include/ccf/ds/nonstd.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include <array>
66
#include <cctype>
7+
#include <chrono>
8+
#include <ctime>
79
#include <regex>
810
#include <string>
911
#include <string_view>
@@ -213,9 +215,38 @@ namespace ccf::nonstd
213215
*fd = -1;
214216
}
215217
}
218+
216219
using CloseFdGuard = std::unique_ptr<int, decltype(&close_fd)>;
217220
static inline CloseFdGuard make_close_fd_guard(int* fd)
218221
{
219222
return CloseFdGuard(fd, close_fd);
220223
}
224+
225+
// A custom clock type for handling certificate validity periods, which are
226+
// defined in terms of seconds since the epoch. This avoids issues with
227+
// system_clock::time_point being unable to represent times after 2262-04-11
228+
// 23:47:17 UTC (due to tracking nanosecond precision).
229+
struct SystemClock
230+
{
231+
using duration = std::chrono::seconds;
232+
using rep = duration::rep;
233+
using period = duration::period;
234+
using time_point = std::chrono::time_point<SystemClock>;
235+
static constexpr bool is_steady = false;
236+
237+
static time_point now() noexcept
238+
{
239+
return time_point(duration(std::time(nullptr)));
240+
}
241+
242+
static std::time_t to_time_t(const time_point& t) noexcept
243+
{
244+
return std::time_t(t.time_since_epoch().count());
245+
}
246+
247+
static time_point from_time_t(std::time_t t) noexcept
248+
{
249+
return time_point(duration(t));
250+
}
251+
};
221252
}

include/ccf/ds/x509_time_fmt.h

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// Licensed under the Apache 2.0 License.
33
#pragma once
44

5-
#define FMT_HEADER_ONLY
5+
#include "ccf/ds/nonstd.h"
6+
67
#include <chrono>
8+
#define FMT_HEADER_ONLY
79
#include <fmt/chrono.h>
810
#include <fmt/format.h>
911
#include <iomanip>
@@ -20,13 +22,20 @@ namespace ccf::ds
2022
return fmt::format("{:%Y%m%d%H%M%SZ}", time);
2123
}
2224

25+
static inline std::string to_x509_time_string(
26+
const ccf::nonstd::SystemClock::time_point& time)
27+
{
28+
return to_x509_time_string(
29+
fmt::gmtime(ccf::nonstd::SystemClock::to_time_t(time)));
30+
}
31+
2332
static inline std::string to_x509_time_string(
2433
const std::chrono::system_clock::time_point& time)
2534
{
2635
return to_x509_time_string(fmt::gmtime(time));
2736
}
2837

29-
static inline std::chrono::system_clock::time_point time_point_from_string(
38+
static inline ccf::nonstd::SystemClock::time_point time_point_from_string(
3039
const std::string& time)
3140
{
3241
const char* ts = time.c_str();
@@ -43,7 +52,7 @@ namespace ccf::ds
4352
auto sres = strptime(ts, afmt, &t);
4453
if (sres != NULL && *sres == '\0')
4554
{
46-
auto r = std::chrono::system_clock::from_time_t(timegm(&t));
55+
auto r = ccf::nonstd::SystemClock::from_time_t(timegm(&t));
4756
r -= std::chrono::seconds(t.tm_gmtoff);
4857
return r;
4958
}
@@ -55,8 +64,8 @@ namespace ccf::ds
5564
{"%04u-%02u-%02u %02u:%02u:%f %d:%02u", 8},
5665
{"%04u-%02u-%02uT%02u:%02u:%f %d:%02u", 8},
5766
{"%04u-%02u-%02u %02u:%02u:%f %03d %02u", 8},
58-
{"%02u%02u%02u%02u%02u%02f%03d%02u", 8},
59-
{"%04u%02u%02u%02u%02u%02f%03d%02u", 8},
67+
{"%02u%02u%02u%02u%02u%f%03d%02u", 8},
68+
{"%04u%02u%02u%02u%02u%f%03d%02u", 8},
6069
{"%04u-%02u-%02uT%02u:%02u:%f", 6},
6170
{"%04u-%02u-%02u %02u:%02u:%f", 6}};
6271

@@ -70,7 +79,6 @@ namespace ccf::ds
7079
if (rs >= 1 && rs == n)
7180
{
7281
using namespace std::chrono;
73-
7482
if (strncmp(fmt, "%02u", 4) == 0)
7583
{
7684
// ASN.1 two-digit year range
@@ -88,12 +96,30 @@ namespace ccf::ds
8896
continue;
8997
}
9098

91-
system_clock::time_point r = (sys_days)date;
99+
// Build a struct tm and use timegm() to convert to time_t
100+
// directly, avoiding system_clock::time_point which can
101+
// overflow for dates outside ~1677-2262.
102+
struct tm t = {};
103+
t.tm_year = static_cast<int>(y) - 1900;
104+
t.tm_mon = static_cast<int>(m) - 1;
105+
t.tm_mday = static_cast<int>(d);
92106
if (rs >= 6)
93-
r += hours(h) + minutes(mn) + microseconds((long)(s * 1e6));
107+
{
108+
t.tm_hour = static_cast<int>(h);
109+
t.tm_min = static_cast<int>(mn);
110+
t.tm_sec = static_cast<int>(s);
111+
}
112+
113+
auto tt = timegm(&t);
114+
94115
if (rs >= 8)
95-
r -= hours(oh) + minutes(om);
96-
return r;
116+
{
117+
auto offset_secs = oh * 3600 +
118+
(oh < 0 ? -static_cast<int>(om) : static_cast<int>(om)) * 60;
119+
tt -= offset_secs;
120+
}
121+
122+
return ccf::nonstd::SystemClock::from_time_t(tt);
97123
}
98124
}
99125
}

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ccf"
7-
version = "6.0.21"
7+
version = "6.0.22"
88
authors = [
99
{ name="CCF Team", email="CCF-Sec@microsoft.com" },
1010
]

src/crypto/openssl/verifier.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ namespace ccf::crypto
200200
}
201201

202202
size_t Verifier_OpenSSL::remaining_seconds(
203-
const std::chrono::system_clock::time_point& now) const
203+
const ccf::nonstd::SystemClock::time_point& now) const
204204
{
205205
auto [from, to] = validity_period();
206206
auto tp_to = ccf::ds::time_point_from_string(to);
@@ -210,7 +210,7 @@ namespace ccf::crypto
210210
}
211211

212212
double Verifier_OpenSSL::remaining_percentage(
213-
const std::chrono::system_clock::time_point& now) const
213+
const ccf::nonstd::SystemClock::time_point& now) const
214214
{
215215
auto [from, to] = validity_period();
216216
auto tp_from = ccf::ds::time_point_from_string(from);

src/crypto/openssl/verifier.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ namespace ccf::crypto
3939
const override;
4040

4141
virtual size_t remaining_seconds(
42-
const std::chrono::system_clock::time_point& now) const override;
42+
const ccf::nonstd::SystemClock::time_point& now) const override;
4343

4444
virtual double remaining_percentage(
45-
const std::chrono::system_clock::time_point& now) const override;
45+
const ccf::nonstd::SystemClock::time_point& now) const override;
4646

4747
virtual std::string subject() const override;
4848
};

src/crypto/test/crypto.cpp

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ ccf::crypto::Pem generate_self_signed_cert(
190190
constexpr size_t certificate_validity_period_days = 365;
191191
using namespace std::literals;
192192
auto valid_from =
193-
ccf::ds::to_x509_time_string(std::chrono::system_clock::now() - 24h);
193+
ccf::ds::to_x509_time_string(ccf::nonstd::SystemClock::now() - 24h);
194194

195195
return ccf::crypto::create_self_signed_cert(
196196
kp, name, {}, valid_from, certificate_validity_period_days);
@@ -793,6 +793,11 @@ TEST_CASE("Non-ASN.1 timepoint formats")
793793
conv = ccf::ds::to_x509_time_string(tp);
794794
REQUIRE(conv == "20220405215327Z");
795795

796+
time_str = "2026-02-09 05:00:00 -03:30";
797+
tp = ccf::ds::time_point_from_string(time_str);
798+
conv = ccf::ds::to_x509_time_string(tp);
799+
REQUIRE(conv == "20260209083000Z");
800+
796801
time_str = "2022-04-07T10:37:49.567612";
797802
tp = ccf::ds::time_point_from_string(time_str);
798803
conv = ccf::ds::to_x509_time_string(tp);
@@ -824,6 +829,91 @@ TEST_CASE("Non-ASN.1 timepoint formats")
824829
REQUIRE(conv == "20220425195619Z");
825830
}
826831

832+
TEST_CASE("Timepoint bounds")
833+
{
834+
// Can handle values beyond bounds of system_clock::time_point
835+
{
836+
INFO("Beyond system_clock::time_point min");
837+
auto time_str = "1677-09-21 00:12:44";
838+
auto tp = ccf::ds::time_point_from_string(time_str);
839+
auto conv = ccf::ds::to_x509_time_string(tp);
840+
REQUIRE(conv == "16770921001244Z");
841+
842+
time_str = "1677-09-21 00:12:43";
843+
tp = ccf::ds::time_point_from_string(time_str);
844+
conv = ccf::ds::to_x509_time_string(tp);
845+
REQUIRE(conv == "16770921001243Z");
846+
}
847+
848+
{
849+
INFO("Beyond system_clock::time_point max");
850+
auto time_str = "2262-04-11 23:47:16";
851+
auto tp = ccf::ds::time_point_from_string(time_str);
852+
auto conv = ccf::ds::to_x509_time_string(tp);
853+
REQUIRE(conv == "22620411234716Z");
854+
855+
time_str = "2262-04-11 23:47:17";
856+
tp = ccf::ds::time_point_from_string(time_str);
857+
conv = ccf::ds::to_x509_time_string(tp);
858+
CHECK(conv == "22620411234717Z");
859+
}
860+
861+
{
862+
INFO("Approx ASN.1 format bounds");
863+
auto time_str = "9999-12-31 23:59:59";
864+
auto tp = ccf::ds::time_point_from_string(time_str);
865+
auto conv = ccf::ds::to_x509_time_string(tp);
866+
REQUIRE(conv == "99991231235959Z");
867+
868+
INFO("sscanf variants of near-min value");
869+
for (auto time_str : {
870+
"0001-02-03 04:05:06",
871+
"0001-02-03 04:05:06.700000 +0:00",
872+
"0001-02-03 12:14:06.700000 +8:09",
873+
"0001-02-02 19:56:06.700000 -8:09",
874+
875+
"0001-02-03T04:05:06.700000 +0:00",
876+
"0001-02-03T12:14:06.700000 +8:09",
877+
"0001-02-02T19:56:06.700000 -8:09",
878+
879+
"0001-02-03 04:05:06.700000 +00 00",
880+
"0001-02-03 12:14:06.700000 +08:09",
881+
"0001-02-02 19:56:06.700000 -08:09",
882+
883+
"00010203040506.700000+0000",
884+
"00010203121406.700000+0809",
885+
"00010202195606.700000-0809",
886+
887+
"0001-02-03T04:05:06.700000",
888+
"0001-02-03 04:05:06.700000",
889+
})
890+
{
891+
tp = ccf::ds::time_point_from_string(time_str);
892+
conv = ccf::ds::to_x509_time_string(tp);
893+
CHECK(conv == "00010203040506Z");
894+
}
895+
}
896+
}
897+
898+
TEST_CASE("Invalid times")
899+
{
900+
REQUIRE_THROWS_WITH(
901+
ccf::ds::time_point_from_string("hello"),
902+
"'hello' does not match any accepted time format");
903+
904+
REQUIRE_THROWS_WITH(
905+
ccf::ds::time_point_from_string("Monday"),
906+
"'Monday' does not match any accepted time format");
907+
908+
REQUIRE_THROWS_WITH(
909+
ccf::ds::time_point_from_string("April 1st, 1984"),
910+
"'April 1st, 1984' does not match any accepted time format");
911+
912+
REQUIRE_THROWS_WITH(
913+
ccf::ds::time_point_from_string("1111-1111"),
914+
"'1111-1111' does not match any accepted time format");
915+
}
916+
827917
TEST_CASE("Create sign and verify certificates")
828918
{
829919
bool corrupt_csr = false;
@@ -928,7 +1018,7 @@ TEST_CASE("AES-GCM convenience functions")
9281018

9291019
TEST_CASE("x509 time")
9301020
{
931-
auto time = std::chrono::system_clock::now();
1021+
auto time = ccf::nonstd::SystemClock::now();
9321022

9331023
auto next_minute_time = time + 1min;
9341024
auto next_day_time = time + 24h;
@@ -940,8 +1030,8 @@ TEST_CASE("x509 time")
9401030
{
9411031
struct Input
9421032
{
943-
std::chrono::system_clock::time_point from;
944-
std::chrono::system_clock::time_point to;
1033+
ccf::nonstd::SystemClock::time_point from;
1034+
ccf::nonstd::SystemClock::time_point to;
9451035
std::optional<uint32_t> maximum_validity_period_days = std::nullopt;
9461036
};
9471037
Input input;
@@ -976,7 +1066,7 @@ TEST_CASE("x509 time")
9761066

9771067
INFO("Adjust time");
9781068
{
979-
std::vector<std::chrono::system_clock::time_point> times = {
1069+
std::vector<ccf::nonstd::SystemClock::time_point> times = {
9801070
time, next_day_time, next_day_time};
9811071
size_t days_offset = 100;
9821072

src/node/ccf_acme_client.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache 2.0 License.
33
#pragma once
44

5+
#include "ccf/ds/nonstd.h"
56
#include "ccf/http_status.h"
67
#include "ccf/service/acme_client_config.h"
78
#include "ccf/service/tables/acme_certificates.h"
@@ -108,7 +109,7 @@ namespace ccf
108109
std::shared_ptr<ccf::kv::Store> tables,
109110
std::unique_ptr<NetworkIdentity>& identity)
110111
{
111-
auto now = std::chrono::system_clock::now();
112+
auto now = ccf::nonstd::SystemClock::now();
112113
bool renew = false;
113114
auto tx = tables->create_read_only_tx();
114115
auto certs = tx.ro<ACMECertificates>(Tables::ACME_CERTIFICATES);

0 commit comments

Comments
 (0)