/src/botan/src/lib/asn1/asn1_time.cpp
Line | Count | Source (jump to first uncovered line) |
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_obj.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 <iomanip> |
18 | | |
19 | | namespace Botan { |
20 | | |
21 | 674 | ASN1_Time::ASN1_Time(const std::chrono::system_clock::time_point& time) { |
22 | 674 | calendar_point cal(time); |
23 | | |
24 | 674 | m_year = cal.year(); |
25 | 674 | m_month = cal.month(); |
26 | 674 | m_day = cal.day(); |
27 | 674 | m_hour = cal.hour(); |
28 | 674 | m_minute = cal.minutes(); |
29 | 674 | m_second = cal.seconds(); |
30 | | |
31 | 674 | m_tag = (m_year >= 2050) ? ASN1_Type::GeneralizedTime : ASN1_Type::UtcTime; |
32 | 674 | } |
33 | | |
34 | 0 | ASN1_Time::ASN1_Time(std::string_view t_spec, ASN1_Type tag) { |
35 | 0 | set_to(t_spec, tag); |
36 | 0 | } |
37 | | |
38 | 0 | ASN1_Time::ASN1_Time(std::string_view t_spec) { |
39 | 0 | if(t_spec.size() == 13) { |
40 | 0 | set_to(t_spec, ASN1_Type::UtcTime); |
41 | 0 | } else if(t_spec.size() == 15) { |
42 | 0 | set_to(t_spec, ASN1_Type::GeneralizedTime); |
43 | 0 | } else { |
44 | 0 | throw Invalid_Argument("Time string could not be parsed as GeneralizedTime or UTCTime."); |
45 | 0 | } |
46 | 0 | } |
47 | | |
48 | 0 | void ASN1_Time::encode_into(DER_Encoder& der) const { |
49 | 0 | BOTAN_ARG_CHECK(m_tag == ASN1_Type::UtcTime || m_tag == ASN1_Type::GeneralizedTime, "ASN1_Time: Bad encoding tag"); |
50 | |
|
51 | 0 | der.add_object(m_tag, ASN1_Class::Universal, to_string()); |
52 | 0 | } |
53 | | |
54 | 58.4k | void ASN1_Time::decode_from(BER_Decoder& source) { |
55 | 58.4k | BER_Object ber_time = source.get_next_object(); |
56 | | |
57 | 58.4k | set_to(ASN1::to_string(ber_time), ber_time.type()); |
58 | 58.4k | } |
59 | | |
60 | 0 | std::string ASN1_Time::to_string() const { |
61 | 0 | if(time_is_set() == false) { |
62 | 0 | throw Invalid_State("ASN1_Time::to_string: No time set"); |
63 | 0 | } |
64 | | |
65 | 0 | uint32_t full_year = m_year; |
66 | |
|
67 | 0 | if(m_tag == ASN1_Type::UtcTime) { |
68 | 0 | if(m_year < 1950 || m_year >= 2050) { |
69 | 0 | throw Encoding_Error(fmt("ASN_Time: The time {} cannot be encoded as UTCTime", readable_string())); |
70 | 0 | } |
71 | | |
72 | 0 | full_year = (m_year >= 2000) ? (m_year - 2000) : (m_year - 1900); |
73 | 0 | } |
74 | | |
75 | 0 | const uint64_t year_factor = 10000000000; |
76 | 0 | const uint64_t mon_factor = 100000000; |
77 | 0 | const uint64_t day_factor = 1000000; |
78 | 0 | const uint64_t hour_factor = 10000; |
79 | 0 | const uint64_t min_factor = 100; |
80 | |
|
81 | 0 | const uint64_t int_repr = year_factor * full_year + mon_factor * m_month + day_factor * m_day + |
82 | 0 | hour_factor * m_hour + min_factor * m_minute + m_second; |
83 | |
|
84 | 0 | std::string repr = std::to_string(int_repr) + "Z"; |
85 | |
|
86 | 0 | const size_t desired_size = (m_tag == ASN1_Type::UtcTime) ? 13 : 15; |
87 | |
|
88 | 0 | const std::string zero_padding(desired_size - repr.size(), '0'); |
89 | |
|
90 | 0 | return zero_padding + repr; |
91 | 0 | } |
92 | | |
93 | 1.08k | std::string ASN1_Time::readable_string() const { |
94 | 1.08k | if(time_is_set() == false) { |
95 | 0 | throw Invalid_State("ASN1_Time::readable_string: No time set"); |
96 | 0 | } |
97 | | |
98 | | // desired format: "%04d/%02d/%02d %02d:%02d:%02d UTC" |
99 | 1.08k | std::stringstream output; |
100 | 1.08k | output << std::setfill('0') << std::setw(4) << m_year << "/" << std::setw(2) << m_month << "/" << std::setw(2) |
101 | 1.08k | << m_day << " " << std::setw(2) << m_hour << ":" << std::setw(2) << m_minute << ":" << std::setw(2) |
102 | 1.08k | << m_second << " UTC"; |
103 | | |
104 | 1.08k | return output.str(); |
105 | 1.08k | } |
106 | | |
107 | 3.66k | bool ASN1_Time::time_is_set() const { |
108 | 3.66k | return (m_year != 0); |
109 | 3.66k | } |
110 | | |
111 | 4 | int32_t ASN1_Time::cmp(const ASN1_Time& other) const { |
112 | 4 | if(!time_is_set() || !other.time_is_set()) { |
113 | 0 | throw Invalid_State("ASN1_Time::cmp: Cannot compare empty times"); |
114 | 0 | } |
115 | | |
116 | 4 | const int32_t EARLIER = -1, LATER = 1, SAME_TIME = 0; |
117 | | |
118 | 4 | if(m_year < other.m_year) { |
119 | 0 | return EARLIER; |
120 | 0 | } |
121 | 4 | if(m_year > other.m_year) { |
122 | 4 | return LATER; |
123 | 4 | } |
124 | 0 | if(m_month < other.m_month) { |
125 | 0 | return EARLIER; |
126 | 0 | } |
127 | 0 | if(m_month > other.m_month) { |
128 | 0 | return LATER; |
129 | 0 | } |
130 | 0 | if(m_day < other.m_day) { |
131 | 0 | return EARLIER; |
132 | 0 | } |
133 | 0 | if(m_day > other.m_day) { |
134 | 0 | return LATER; |
135 | 0 | } |
136 | 0 | if(m_hour < other.m_hour) { |
137 | 0 | return EARLIER; |
138 | 0 | } |
139 | 0 | if(m_hour > other.m_hour) { |
140 | 0 | return LATER; |
141 | 0 | } |
142 | 0 | if(m_minute < other.m_minute) { |
143 | 0 | return EARLIER; |
144 | 0 | } |
145 | 0 | if(m_minute > other.m_minute) { |
146 | 0 | return LATER; |
147 | 0 | } |
148 | 0 | if(m_second < other.m_second) { |
149 | 0 | return EARLIER; |
150 | 0 | } |
151 | 0 | if(m_second > other.m_second) { |
152 | 0 | return LATER; |
153 | 0 | } |
154 | | |
155 | 0 | return SAME_TIME; |
156 | 0 | } |
157 | | |
158 | 58.4k | void ASN1_Time::set_to(std::string_view t_spec, ASN1_Type spec_tag) { |
159 | 58.4k | BOTAN_ARG_CHECK(spec_tag == ASN1_Type::UtcTime || spec_tag == ASN1_Type::GeneralizedTime, |
160 | 58.4k | "Invalid tag for ASN1_Time"); |
161 | | |
162 | 58.4k | if(spec_tag == ASN1_Type::GeneralizedTime) { |
163 | 4.58k | BOTAN_ARG_CHECK(t_spec.size() == 15, "Invalid GeneralizedTime input string"); |
164 | 53.8k | } else if(spec_tag == ASN1_Type::UtcTime) { |
165 | 52.2k | BOTAN_ARG_CHECK(t_spec.size() == 13, "Invalid UTCTime input string"); |
166 | 52.2k | } |
167 | | |
168 | 58.4k | BOTAN_ARG_CHECK(t_spec.back() == 'Z', "Botan does not support ASN1 times with timezones other than Z"); |
169 | | |
170 | 58.4k | const size_t field_len = 2; |
171 | | |
172 | 58.4k | const size_t year_start = 0; |
173 | 58.4k | const size_t year_len = (spec_tag == ASN1_Type::UtcTime) ? 2 : 4; |
174 | 58.4k | const size_t month_start = year_start + year_len; |
175 | 58.4k | const size_t day_start = month_start + field_len; |
176 | 58.4k | const size_t hour_start = day_start + field_len; |
177 | 58.4k | const size_t min_start = hour_start + field_len; |
178 | 58.4k | const size_t sec_start = min_start + field_len; |
179 | | |
180 | 58.4k | m_year = to_u32bit(t_spec.substr(year_start, year_len)); |
181 | 58.4k | m_month = to_u32bit(t_spec.substr(month_start, field_len)); |
182 | 58.4k | m_day = to_u32bit(t_spec.substr(day_start, field_len)); |
183 | 58.4k | m_hour = to_u32bit(t_spec.substr(hour_start, field_len)); |
184 | 58.4k | m_minute = to_u32bit(t_spec.substr(min_start, field_len)); |
185 | 58.4k | m_second = to_u32bit(t_spec.substr(sec_start, field_len)); |
186 | 58.4k | m_tag = spec_tag; |
187 | | |
188 | 58.4k | if(spec_tag == ASN1_Type::UtcTime) { |
189 | 50.9k | if(m_year >= 50) { |
190 | 6.26k | m_year += 1900; |
191 | 44.7k | } else { |
192 | 44.7k | m_year += 2000; |
193 | 44.7k | } |
194 | 50.9k | } |
195 | | |
196 | 58.4k | if(!passes_sanity_check()) { |
197 | 2.76k | throw Invalid_Argument(fmt("ASN1_Time string '{}' does not seem to be valid", t_spec)); |
198 | 2.76k | } |
199 | 58.4k | } |
200 | | |
201 | | /* |
202 | | * Do a general sanity check on the time |
203 | | */ |
204 | 55.1k | bool ASN1_Time::passes_sanity_check() const { |
205 | | // AppVeyor's trust store includes a cert with expiration date in 3016 ... |
206 | 55.1k | if(m_year < 1950 || m_year > 3100) { |
207 | 476 | return false; |
208 | 476 | } |
209 | 54.7k | if(m_month == 0 || m_month > 12) { |
210 | 452 | return false; |
211 | 452 | } |
212 | | |
213 | 54.2k | const uint32_t days_in_month[12] = {31, 28 + 1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
214 | | |
215 | 54.2k | if(m_day == 0 || m_day > days_in_month[m_month - 1]) { |
216 | 554 | return false; |
217 | 554 | } |
218 | | |
219 | 53.7k | if(m_month == 2 && m_day == 29) { |
220 | 1.44k | if(m_year % 4 != 0) { |
221 | 208 | return false; // not a leap year |
222 | 208 | } |
223 | | |
224 | 1.24k | if(m_year % 100 == 0 && m_year % 400 != 0) { |
225 | 200 | return false; |
226 | 200 | } |
227 | 1.24k | } |
228 | | |
229 | 53.2k | if(m_hour >= 24 || m_minute >= 60 || m_second > 60) { |
230 | 671 | return false; |
231 | 671 | } |
232 | | |
233 | 52.6k | if(m_tag == ASN1_Type::UtcTime) { |
234 | | /* |
235 | | UTCTime limits the value of components such that leap seconds |
236 | | are not covered. See "UNIVERSAL 23" in "Information technology |
237 | | Abstract Syntax Notation One (ASN.1): Specification of basic notation" |
238 | | |
239 | | http://www.itu.int/ITU-T/studygroups/com17/languages/ |
240 | | */ |
241 | 49.1k | if(m_second > 59) { |
242 | 205 | return false; |
243 | 205 | } |
244 | 49.1k | } |
245 | | |
246 | 52.4k | return true; |
247 | 52.6k | } |
248 | | |
249 | 0 | std::chrono::system_clock::time_point ASN1_Time::to_std_timepoint() const { |
250 | 0 | return calendar_point(m_year, m_month, m_day, m_hour, m_minute, m_second).to_std_timepoint(); |
251 | 0 | } |
252 | | |
253 | 0 | uint64_t ASN1_Time::time_since_epoch() const { |
254 | 0 | return calendar_point(m_year, m_month, m_day, m_hour, m_minute, m_second).seconds_since_epoch(); |
255 | 0 | } |
256 | | |
257 | | /* |
258 | | * Compare two ASN1_Times for in various ways |
259 | | */ |
260 | 0 | bool operator==(const ASN1_Time& t1, const ASN1_Time& t2) { |
261 | 0 | return (t1.cmp(t2) == 0); |
262 | 0 | } |
263 | | |
264 | 0 | bool operator!=(const ASN1_Time& t1, const ASN1_Time& t2) { |
265 | 0 | return (t1.cmp(t2) != 0); |
266 | 0 | } |
267 | | |
268 | 0 | bool operator<=(const ASN1_Time& t1, const ASN1_Time& t2) { |
269 | 0 | return (t1.cmp(t2) <= 0); |
270 | 0 | } |
271 | | |
272 | 0 | bool operator>=(const ASN1_Time& t1, const ASN1_Time& t2) { |
273 | 0 | return (t1.cmp(t2) >= 0); |
274 | 0 | } |
275 | | |
276 | 2 | bool operator<(const ASN1_Time& t1, const ASN1_Time& t2) { |
277 | 2 | return (t1.cmp(t2) < 0); |
278 | 2 | } |
279 | | |
280 | 2 | bool operator>(const ASN1_Time& t1, const ASN1_Time& t2) { |
281 | 2 | return (t1.cmp(t2) > 0); |
282 | 2 | } |
283 | | |
284 | | } // namespace Botan |