Botan 3.13.0
Crypto and TLS for C&
asn1_time.cpp
Go to the documentation of this file.
1/*
2* X.509 Time Types
3* (C) 1999-2007 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/asn1_time.h>
9
10#include <botan/assert.h>
11#include <botan/ber_dec.h>
12#include <botan/der_enc.h>
13#include <botan/exceptn.h>
14#include <botan/internal/calendar.h>
15#include <botan/internal/fmt.h>
16#include <botan/internal/parsing.h>
17#include <sstream>
18
19namespace Botan {
20
21namespace {
22
23// Format an integer as exactly `digits` zero-padded decimal digits
24std::string zero_pad(uint32_t value, size_t digits) {
25 std::string s = std::to_string(value);
26 BOTAN_ASSERT_NOMSG(s.size() <= digits);
27 const size_t padding = digits - s.size();
28 if(padding == 0) {
29 return s;
30 } else {
31 return std::string(padding, '0') + s;
32 }
33}
34
35} // namespace
36
38 return ASN1_Time::from_time_point(std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)));
39}
40
42 uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, ASN1_Type tag) :
43 m_year(year), m_month(month), m_day(day), m_hour(hour), m_minute(minute), m_second(second), m_tag(tag) {
45 throw Invalid_Argument("ASN1_Time tag must be UtcTime or GeneralizedTime");
46 }
47
48 /*
49 * RFC 5280 Section 4.1.2.5:
50 * To indicate that a certificate has no well-defined expiration date,
51 * the notAfter SHOULD be assigned the GeneralizedTime value of
52 * 99991231235959Z.
53 */
54 const uint16_t min_year = 1950;
55 const uint16_t max_year = (tag == ASN1_Type::UtcTime) ? 2049 : 9999;
56
57 if(m_year < min_year || m_year > max_year) {
58 throw Invalid_Argument(fmt("ASN1_Time year {} is out of range ({} to {})", m_year, min_year, max_year));
59 }
60
61 if(m_month < 1 || m_month > 12) {
62 throw Invalid_Argument(fmt("ASN1_Time month {} is out of range", static_cast<uint32_t>(m_month)));
63 }
64
65 constexpr uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
66
67 const bool is_leap_year = (m_year % 4 == 0) && (m_year % 100 != 0 || m_year % 400 == 0);
68 const uint8_t max_day = (m_month == 2 && is_leap_year) ? 29 : days_in_month[m_month - 1];
69
70 if(m_day < 1 || m_day > max_day) {
71 throw Invalid_Argument(fmt("ASN1_Time day {} is out of range for month {}",
72 static_cast<uint32_t>(m_day),
73 static_cast<uint32_t>(m_month)));
74 }
75
76 if(m_hour > 23) {
77 throw Invalid_Argument(fmt("ASN1_Time hour {} is out of range", static_cast<uint32_t>(m_hour)));
78 }
79
80 if(m_minute > 59) {
81 throw Invalid_Argument(fmt("ASN1_Time minute {} is out of range", static_cast<uint32_t>(m_minute)));
82 }
83
84 /*
85 * RFC 5280 is silent on the issue of leap seconds in certificate fields, but both
86 * OpenSSL and Go reject which suggests they are rarely if ever used in practice.
87 */
88 if(m_second > 59) {
89 throw Invalid_Argument(fmt("ASN1_Time second {} is out of range", static_cast<uint32_t>(m_second)));
90 }
91}
92
93//static
94ASN1_Time ASN1_Time::from_time_point(const std::chrono::system_clock::time_point& time) {
95 const calendar_point cal(time);
96
97 const ASN1_Type tag = (cal.year() >= 2050) ? ASN1_Type::GeneralizedTime : ASN1_Type::UtcTime;
98
99 return ASN1_Time(static_cast<uint16_t>(cal.year()),
100 static_cast<uint8_t>(cal.month()),
101 static_cast<uint8_t>(cal.day()),
102 static_cast<uint8_t>(cal.hour()),
103 static_cast<uint8_t>(cal.minutes()),
104 static_cast<uint8_t>(cal.seconds()),
105 tag);
106}
107
108//static
109ASN1_Time ASN1_Time::from_string(std::string_view t_spec, ASN1_Type tag) {
110 BOTAN_ARG_CHECK(tag == ASN1_Type::UtcTime || tag == ASN1_Type::GeneralizedTime, "Invalid tag for ASN1_Time");
111
112 if(tag == ASN1_Type::GeneralizedTime) {
113 BOTAN_ARG_CHECK(t_spec.size() == 15, "Invalid GeneralizedTime input string");
114 } else {
115 BOTAN_ARG_CHECK(t_spec.size() == 13, "Invalid UTCTime input string");
116 }
117
118 BOTAN_ARG_CHECK(t_spec.back() == 'Z', "Botan does not support ASN1 times with timezones other than Z");
119
120 const size_t field_len = 2;
121 const size_t year_len = (tag == ASN1_Type::UtcTime) ? 2 : 4;
122
123 const size_t year_start = 0;
124 const size_t month_start = year_start + year_len;
125 const size_t day_start = month_start + field_len;
126 const size_t hour_start = day_start + field_len;
127 const size_t min_start = hour_start + field_len;
128 const size_t sec_start = min_start + field_len;
129
130 uint32_t year = to_u32bit(t_spec.substr(year_start, year_len));
131 const uint32_t month = to_u32bit(t_spec.substr(month_start, field_len));
132 const uint32_t day = to_u32bit(t_spec.substr(day_start, field_len));
133 const uint32_t hour = to_u32bit(t_spec.substr(hour_start, field_len));
134 const uint32_t minute = to_u32bit(t_spec.substr(min_start, field_len));
135 const uint32_t second = to_u32bit(t_spec.substr(sec_start, field_len));
136
137 if(tag == ASN1_Type::UtcTime) {
138 // Interpret the two digit year by the 1950/2050 split (RFC 5280 Section 4.1.2.5.1)
139 year += (year >= 50) ? 1900 : 2000;
140 }
141
142 return ASN1_Time(static_cast<uint16_t>(year),
143 static_cast<uint8_t>(month),
144 static_cast<uint8_t>(day),
145 static_cast<uint8_t>(hour),
146 static_cast<uint8_t>(minute),
147 static_cast<uint8_t>(second),
148 tag);
149}
150
151//static
152ASN1_Time ASN1_Time::from_string(std::string_view t_spec) {
153 if(t_spec.size() == 13) {
155 } else if(t_spec.size() == 15) {
157 } else {
158 throw Invalid_Argument("Time string could not be parsed as GeneralizedTime or UTCTime.");
159 }
160}
161
163 BOTAN_ARG_CHECK(m_tag == ASN1_Type::UtcTime || m_tag == ASN1_Type::GeneralizedTime, "ASN1_Time: Bad encoding tag");
164
166}
167
169 const BER_Object ber_time = source.get_next_object();
170
171 if(ber_time.get_class() != ASN1_Class::Universal ||
172 (ber_time.type() != ASN1_Type::UtcTime && ber_time.type() != ASN1_Type::GeneralizedTime)) {
173 throw Decoding_Error(fmt("ASN1_Time: Unexpected tag {}/{}",
174 static_cast<uint32_t>(ber_time.type()),
175 static_cast<uint32_t>(ber_time.get_class())));
176 }
177
178 try {
179 // Assigning only after a successful parse means that a decoding error
180 // cannot leave this object in a partially written state
181 *this = ASN1_Time::from_string(ASN1::to_string(ber_time), ber_time.type());
182 } catch(Invalid_Argument& e) {
183 throw Decoding_Error(fmt("Invalid ASN1_Time encoding: {}", e.what()));
184 }
185}
186
187std::string ASN1_Time::to_string() const {
188 if(!time_is_set()) {
189 throw Invalid_State("ASN1_Time::to_string: No time set");
190 }
191
192 BOTAN_ASSERT_NOMSG(m_year <= 9999);
193
194 std::ostringstream out;
195
196 // UTCTime uses a 2 digit year, GeneralizedTime a 4 digit year
197 if(m_tag == ASN1_Type::UtcTime) {
198 if(m_year < 1950 || m_year >= 2050) {
199 throw Encoding_Error(fmt("ASN_Time: The time {} cannot be encoded as UTCTime", readable_string()));
200 }
201
202 out << (zero_pad((m_year >= 2000) ? (m_year - 2000) : (m_year - 1900), 2));
203 } else {
204 out << zero_pad(m_year, 4);
205 }
206
207 // clang-format off
208 out << zero_pad(m_month, 2)
209 << zero_pad(m_day, 2)
210 << zero_pad(m_hour, 2)
211 << zero_pad(m_minute, 2)
212 << zero_pad(m_second, 2) << "Z";
213 // clang-format on
214
215 return out.str();
216}
217
218std::string ASN1_Time::readable_string() const {
219 if(!time_is_set()) {
220 throw Invalid_State("ASN1_Time::readable_string: No time set");
221 }
222
223 // desired format: "YYYY/MM/DD HH:MM:SS UTC"
224
225 std::ostringstream out;
226
227 out << zero_pad(m_year, 4) << "/" << zero_pad(m_month, 2) << "/" << zero_pad(m_day, 2) << " ";
228 out << zero_pad(m_hour, 2) << ":" << zero_pad(m_minute, 2) << ":" << zero_pad(m_second, 2) << " UTC";
229
230 return out.str();
231}
232
234 return (m_year != 0);
235}
236
237int32_t ASN1_Time::cmp(const ASN1_Time& other) const {
238 if(!time_is_set() || !other.time_is_set()) {
239 throw Invalid_State("ASN1_Time::cmp: Cannot compare empty times");
240 }
241
242 constexpr int32_t EARLIER = -1;
243 constexpr int32_t LATER = 1;
244 constexpr int32_t SAME_TIME = 0;
245
246 if(m_year < other.m_year) {
247 return EARLIER;
248 }
249 if(m_year > other.m_year) {
250 return LATER;
251 }
252 if(m_month < other.m_month) {
253 return EARLIER;
254 }
255 if(m_month > other.m_month) {
256 return LATER;
257 }
258 if(m_day < other.m_day) {
259 return EARLIER;
260 }
261 if(m_day > other.m_day) {
262 return LATER;
263 }
264 if(m_hour < other.m_hour) {
265 return EARLIER;
266 }
267 if(m_hour > other.m_hour) {
268 return LATER;
269 }
270 if(m_minute < other.m_minute) {
271 return EARLIER;
272 }
273 if(m_minute > other.m_minute) {
274 return LATER;
275 }
276 if(m_second < other.m_second) {
277 return EARLIER;
278 }
279 if(m_second > other.m_second) {
280 return LATER;
281 }
282
283 return SAME_TIME;
284}
285
286std::chrono::system_clock::time_point ASN1_Time::to_std_timepoint() const {
287 return calendar_point(m_year, m_month, m_day, m_hour, m_minute, m_second).to_std_timepoint();
288}
289
291 const int64_t tse = calendar_point(m_year, m_month, m_day, m_hour, m_minute, m_second).seconds_since_epoch();
292
293 if(tse >= 0) {
294 return static_cast<uint64_t>(tse);
295 } else {
296 throw Encoding_Error("ASN1_Time::time_since_epoch value is prior to epoch");
297 }
298}
299
300/*
301* Compare two ASN1_Times for in various ways
302*/
303bool operator==(const ASN1_Time& t1, const ASN1_Time& t2) {
304 return (t1.cmp(t2) == 0);
305}
306
307bool operator!=(const ASN1_Time& t1, const ASN1_Time& t2) {
308 return (t1.cmp(t2) != 0);
309}
310
311bool operator<=(const ASN1_Time& t1, const ASN1_Time& t2) {
312 return (t1.cmp(t2) <= 0);
313}
314
315bool operator>=(const ASN1_Time& t1, const ASN1_Time& t2) {
316 return (t1.cmp(t2) >= 0);
317}
318
319bool operator<(const ASN1_Time& t1, const ASN1_Time& t2) {
320 return (t1.cmp(t2) < 0);
321}
322
323bool operator>(const ASN1_Time& t1, const ASN1_Time& t2) {
324 return (t1.cmp(t2) > 0);
325}
326
327} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
uint64_t time_since_epoch() const
Return time since epoch.
std::chrono::system_clock::time_point to_std_timepoint() const
Returns a STL timepoint object.
void decode_from(BER_Decoder &from) override
static ASN1_Time from_seconds_since_epoch(uint64_t seconds)
Create an ASN1_Time from seconds since epoch.
Definition asn1_time.cpp:37
std::string to_string() const
Return an internal string representation of the time.
static ASN1_Time from_string(std::string_view t_spec)
ASN1_Time()=default
Create an invalid ASN1_Time.
static ASN1_Time from_time_point(const std::chrono::system_clock::time_point &time)
Create a ASN1_Time from a time point.
Definition asn1_time.cpp:94
bool time_is_set() const
Return if the time has been set somehow.
int32_t cmp(const ASN1_Time &other) const
Compare this time against another.
void encode_into(DER_Encoder &to) const override
DER encode a ASN1_Time.
std::string readable_string() const
Returns a human friendly string representation of no particular formatting.
BER_Object get_next_object()
Definition ber_dec.cpp:516
ASN1_Type type() const
Definition asn1_obj.h:287
ASN1_Class get_class() const
Definition asn1_obj.h:292
DER_Encoder & add_object(ASN1_Type type_tag, ASN1_Class class_tag, const uint8_t rep[], size_t length)
Definition der_enc.cpp:285
const char * what() const noexcept override
Definition exceptn.h:94
int64_t seconds_since_epoch() const
Definition calendar.cpp:98
uint32_t hour() const
Definition calendar.h:32
uint32_t seconds() const
Definition calendar.h:38
uint32_t day() const
Definition calendar.h:29
std::chrono::system_clock::time_point to_std_timepoint() const
Definition calendar.cpp:102
uint32_t minutes() const
Definition calendar.h:35
uint32_t month() const
Definition calendar.h:26
uint32_t year() const
Definition calendar.h:23
std::string to_string(const BER_Object &obj)
Definition asn1_obj.cpp:224
bool operator>(const ASN1_Time &t1, const ASN1_Time &t2)
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
ASN1_Type
Definition asn1_obj.h:47
bool operator<(const OID &a, const OID &b)
Definition asn1_oid.cpp:175
bool operator>=(const ASN1_Time &t1, const ASN1_Time &t2)
bool operator<=(const ASN1_Time &t1, const ASN1_Time &t2)
bool operator!=(const AlgorithmIdentifier &x, const AlgorithmIdentifier &y)
Definition alg_id.cpp:58
bool operator==(const AlgorithmIdentifier &x, const AlgorithmIdentifier &y)
Definition alg_id.cpp:54
uint32_t to_u32bit(std::string_view input)
Definition parsing.cpp:76