Coverage Report

Created: 2025-04-11 06:34

/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