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 }