1 /**
2 * ASN.1 Time Representation
3 *
4 * Copyright:
5 * (C) 1999-2007,2012 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.asn1.asn1_time;
12
13 import botan.constants;
14 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO):
15
16 import std.datetime;
17 import botan.asn1.der_enc;
18 import botan.asn1.ber_dec;
19 import botan.utils.charset;
20 import botan.utils.parsing;
21 import botan.utils.types;
22 import memutils.refcounted;
23 import std.conv : to;
24 import std.array : Appender;
25
26 public alias X509Time = RefCounted!X509TimeImpl;
27
28 /**
29 * X.509 Time
30 */
31 final class X509TimeImpl : ASN1Object
32 {
33 public:
34
35 /*
36 * DER encode a X509Time
37 */
38 override void encodeInto(ref DEREncoder der) const
39 {
40 if (m_tag != ASN1Tag.GENERALIZED_TIME && m_tag != ASN1Tag.UTC_TIME)
41 throw new InvalidArgument("X509Time: Bad encoding m_tag");
42
43 der.addObject(m_tag, ASN1Tag.UNIVERSAL,
44 transcode(toString(), LOCAL_CHARSET, LATIN1_CHARSET));
45 }
46
47 /*
48 * Decode a BER encoded X509Time
49 */
50 override void decodeFrom(ref BERDecoder source)
51 {
52 BERObject ber_time = source.getNextObject();
53
54 setTo(transcode(ber_time.toString(),
55 LATIN1_CHARSET,
56 LOCAL_CHARSET),
57 ber_time.type_tag);
58 }
59
60 /*
61 * Return a string representation of the time
62 */
63 override string toString() const
64 {
65 if (timeIsSet() == false)
66 throw new InvalidState("toTimeString: No time set");
67
68 uint full_year = m_year;
69
70 if (m_tag == ASN1Tag.UTC_TIME)
71 {
72 if (m_year < 1950 || m_year >= 2050)
73 throw new EncodingError("X509Time: The time " ~ readableString() ~ " cannot be encoded as a UTCTime");
74
75 full_year = (m_year >= 2000) ? (m_year - 2000) : (m_year - 1900);
76 }
77
78 string repr = to!string(full_year*10000000000 +
79 m_month*100000000 +
80 m_day*1000000 +
81 m_hour*10000 +
82 m_minute*100 +
83 m_second) ~ "Z";
84
85 uint desired_size = (m_tag == ASN1Tag.UTC_TIME) ? 13 : 15;
86
87 while (repr.length < desired_size)
88 repr = "0" ~ repr;
89
90 return repr;
91 }
92
93 /*
94 * Return a human readable string representation
95 */
96 string readableString() const
97 {
98 if (timeIsSet() == false)
99 throw new InvalidState("readableString: No time set");
100 import std..string : format;
101
102 return format("%04d/%02d/%02d %02d:%02d:%02d UTC", m_year, m_month, m_day, m_hour, m_minute, m_second);
103 }
104
105 /*
106 * Return if the time has been set somehow
107 */
108 bool timeIsSet() const
109 {
110 return (m_year != 0);
111 }
112
113 string toPrettyString() const { return readableString(); }
114
115 /*
116 * Compare this time against another
117 */
118 int cmp(in X509Time other) const
119 {
120 if (timeIsSet() == false)
121 throw new InvalidState("cmp: No time set");
122
123 const int EARLIER = -1, LATER = 1, SAME_TIME = 0;
124
125 if (m_year < other.m_year) return EARLIER;
126 if (m_year > other.m_year) return LATER;
127 if (m_month < other.m_month) return EARLIER;
128 if (m_month > other.m_month) return LATER;
129 if (m_day < other.m_day) return EARLIER;
130 if (m_day > other.m_day) return LATER;
131 if (m_hour < other.m_hour) return EARLIER;
132 if (m_hour > other.m_hour) return LATER;
133 if (m_minute < other.m_minute) return EARLIER;
134 if (m_minute > other.m_minute) return LATER;
135 if (m_second < other.m_second) return EARLIER;
136 if (m_second > other.m_second) return LATER;
137
138 return SAME_TIME;
139 }
140
141 /*
142 * Set the time with a human readable string
143 */
144 void setTo(in string time_str)
145 {
146 if (time_str == "")
147 {
148 m_year = m_month = m_day = m_hour = m_minute = m_second = 0;
149 m_tag = ASN1Tag.NO_OBJECT;
150 return;
151 }
152
153 Vector!string params;
154 Vector!ubyte current;
155
156 for (size_t j = 0; j != time_str.length; ++j)
157 {
158 if (isDigit(time_str[j]))
159 current ~= time_str[j];
160 else
161 {
162 if (current.length > 0)
163 params.pushBack(cast(string) current[].idup);
164 current.clear();
165 }
166 }
167 if (current.length > 0)
168 params.pushBack(cast(string) current[].idup);
169
170 if (params.length < 3 || params.length > 6)
171 throw new InvalidArgument("Invalid time specification " ~ time_str);
172
173 m_year = to!uint(params[0]);
174 m_month = to!uint(params[1]);
175 m_day = to!uint(params[2]);
176 m_hour = (params.length >= 4) ? to!uint(params[3]) : 0;
177 m_minute = (params.length >= 5) ? to!uint(params[4]) : 0;
178 m_second = (params.length == 6) ? to!uint(params[5]) : 0;
179
180 foreach(string param; params[]) destroy(param);
181
182 m_tag = (m_year >= 2050) ? ASN1Tag.GENERALIZED_TIME : ASN1Tag.UTC_TIME;
183
184 if (!passesSanityCheck())
185 throw new InvalidArgument("Invalid time specification " ~ time_str);
186 }
187
188
189 /*
190 * Set the time with an ISO time format string
191 */
192 void setTo(in string t_spec, ASN1Tag spec_tag)
193 {
194 if (spec_tag == ASN1Tag.GENERALIZED_TIME)
195 {
196 if (t_spec.length != 13 && t_spec.length != 15)
197 throw new InvalidArgument("Invalid GeneralizedTime: " ~ t_spec);
198 }
199 else if (spec_tag == ASN1Tag.UTC_TIME)
200 {
201 if (t_spec.length != 11 && t_spec.length != 13)
202 throw new InvalidArgument("Invalid UTCTime: " ~ t_spec);
203 }
204 else
205 {
206 throw new InvalidArgument("Invalid time m_tag " ~ to!string(spec_tag) ~ " val " ~ t_spec);
207 }
208
209 if (t_spec[t_spec.length-1] != 'Z')
210 throw new InvalidArgument("Invalid time encoding: " ~ t_spec);
211
212 const size_t YEAR_SIZE = (spec_tag == ASN1Tag.UTC_TIME) ? 2 : 4;
213
214 Vector!(string) params;
215 Vector!ubyte current;
216 current.reserve(YEAR_SIZE);
217 foreach (size_t j; 0 .. YEAR_SIZE)
218 current ~= t_spec[j];
219 params.pushBack(current[].idup);
220 current.clear();
221
222 for (size_t j = YEAR_SIZE; j != t_spec.length - 1; ++j)
223 {
224 current ~= t_spec[j];
225 if (current.length == 2)
226 {
227 params.pushBack(current[].idup);
228 current.clear();
229 }
230 }
231
232 m_year = to!uint(params[0]);
233 m_month = to!uint(params[1]);
234 m_day = to!uint(params[2]);
235 m_hour = to!uint(params[3]);
236 m_minute = to!uint(params[4]);
237 m_second = (params.length == 6) ? to!uint(params[5]) : 0;
238 m_tag = spec_tag;
239
240 foreach(string param; params[]) destroy(param);
241
242 if (spec_tag == ASN1Tag.UTC_TIME)
243 {
244 if (m_year >= 50) m_year += 1900;
245 else m_year += 2000;
246 }
247
248 if (!passesSanityCheck())
249 throw new InvalidArgument("Invalid time specification " ~ t_spec);
250 }
251
252 this() {}
253
254 /*
255 * Create a X509Time from a time point
256 */
257 this(in SysTime time)
258 {
259 m_year = time.year;
260 m_month = time.month;
261 m_day = time.day;
262 m_hour = time.hour;
263 m_minute = time.minute;
264 m_second = time.second;
265
266 m_tag = (m_year >= 2050) ? ASN1Tag.GENERALIZED_TIME : ASN1Tag.UTC_TIME;
267
268 if (!passesSanityCheck())
269 throw new InvalidArgument("Invalid time specification from SysTime");
270 }
271
272 /*
273 * Create an X509Time
274 */
275 this(in string t_spec, ASN1Tag t)
276 {
277 m_tag = t;
278 logTrace("Time ctor: ", t_spec);
279 setTo(t_spec, m_tag);
280 }
281
282 /*
283 * Create an X509Time
284 */
285 this(in string time_str)
286 {
287 logTrace("Time ctor: ", time_str);
288 setTo(time_str);
289 }
290
291 /*
292 * Compare two X509Times for in various ways
293 */
294 bool opEquals(in X509Time t2) const
295 { return (cmp(t2) == 0); }
296
297 int opCmp(in X509Time t2) const
298 { return cmp(t2); }
299
300
301 private:
302 /*
303 * Do a general sanity check on the time
304 */
305 bool passesSanityCheck() const
306 {
307 //logTrace("Decoded time: ", readableString());
308 if (m_year < 1950 || m_year > 2100)
309 return false;
310 if (m_month == 0 || m_month > 12)
311 return false;
312 if (m_day == 0 || m_day > 31)
313 return false;
314 if (m_hour >= 24 || m_minute > 60 || m_second > 60)
315 return false;
316 return true;
317 }
318
319 uint m_year, m_month, m_day, m_hour, m_minute, m_second;
320 ASN1Tag m_tag;
321 }