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     this() { }
36 
37     /*
38     * DER encode a X509Time
39     */
40     override void encodeInto(ref DEREncoder der) const
41     {
42         if (m_tag != ASN1Tag.GENERALIZED_TIME && m_tag != ASN1Tag.UTC_TIME)
43             throw new InvalidArgument("X509Time: Bad encoding m_tag");
44         
45         der.addObject(m_tag, ASN1Tag.UNIVERSAL,
46                        transcode(toString(), LOCAL_CHARSET, LATIN1_CHARSET));
47     }
48 
49     /*
50     * Decode a BER encoded X509Time
51     */
52     override void decodeFrom(ref BERDecoder source)
53     {
54         BERObject ber_time = source.getNextObject();
55         
56         setTo(transcode(ber_time.toString(),
57                          LATIN1_CHARSET,
58                          LOCAL_CHARSET),
59                ber_time.type_tag);
60     }
61 
62     /*
63     * Return a string representation of the time
64     */
65     override string toString() const
66     {
67         if (timeIsSet() == false)
68             throw new InvalidState("toTimeString: No time set");
69         
70         uint full_year = m_year;
71         
72         if (m_tag == ASN1Tag.UTC_TIME)
73         {
74             if (m_year < 1950 || m_year >= 2050)
75                 throw new EncodingError("X509Time: The time " ~ readableString() ~ " cannot be encoded as a UTCTime");
76             
77             full_year = (m_year >= 2000) ? (m_year - 2000) : (m_year - 1900);
78         }
79         
80         string repr = to!string(full_year*10000000000 +
81                                 m_month*100000000 +
82                                 m_day*1000000 +
83                                 m_hour*10000 +
84                                 m_minute*100 +
85                                 m_second) ~ "Z";
86         
87         uint desired_size = (m_tag == ASN1Tag.UTC_TIME) ? 13 : 15;
88         
89         while (repr.length < desired_size)
90             repr = "0" ~ repr;
91         
92         return repr;
93     }
94 
95     /*
96     * Return a human readable string representation
97     */
98     string readableString() const
99     {
100         if (timeIsSet() == false)
101             throw new InvalidState("readableString: No time set");
102         import std.string : format;
103         
104         return format("%04d/%02d/%02d %02d:%02d:%02d UTC", m_year, m_month, m_day, m_hour, m_minute, m_second);
105     }
106 
107     /*
108     * Return if the time has been set somehow
109     */
110     bool timeIsSet() const
111     {
112         return (m_year != 0);
113     }
114 
115     string toPrettyString() const { return readableString(); }
116 
117     /*
118     * Compare this time against another
119     */
120     int cmp(in X509Time other) const
121     {
122         if (timeIsSet() == false)
123             throw new InvalidState("cmp: No time set");
124         
125         const int EARLIER = -1, LATER = 1, SAME_TIME = 0;
126         
127         if (m_year < other.m_year)          return EARLIER;
128         if (m_year > other.m_year)          return LATER;
129         if (m_month < other.m_month)        return EARLIER;
130         if (m_month > other.m_month)        return LATER;
131         if (m_day < other.m_day)            return EARLIER;
132         if (m_day > other.m_day)            return LATER;
133         if (m_hour < other.m_hour)          return EARLIER;
134         if (m_hour > other.m_hour)          return LATER;
135         if (m_minute < other.m_minute)      return EARLIER;
136         if (m_minute > other.m_minute)      return LATER;
137         if (m_second < other.m_second)      return EARLIER;
138         if (m_second > other.m_second)      return LATER;
139         
140         return SAME_TIME;
141     }
142 
143     /*
144     * Set the time with a human readable string
145     */
146     void setTo(in string time_str)
147     {
148         if (time_str == "")
149         {
150             m_year = m_month = m_day = m_hour = m_minute = m_second = 0;
151             m_tag = ASN1Tag.NO_OBJECT;
152             return;
153         }
154         
155         Vector!string params;
156         Vector!ubyte current;
157         
158         for (size_t j = 0; j != time_str.length; ++j)
159         {
160             if (isDigit(time_str[j]))
161                 current ~= time_str[j];
162             else
163             {
164                 if (current.length > 0)
165                     params.pushBack(cast(string) current[].idup);
166                 current.clear();
167             }
168         }
169         if (current.length > 0)
170             params.pushBack(cast(string) current[].idup);
171         
172         if (params.length < 3 || params.length > 6)
173             throw new InvalidArgument("Invalid time specification " ~ time_str);
174         
175         m_year      = to!uint(params[0]);
176         m_month     = to!uint(params[1]);
177         m_day       = to!uint(params[2]);
178         m_hour      = (params.length >= 4) ? to!uint(params[3]) : 0;
179         m_minute    = (params.length >= 5) ? to!uint(params[4]) : 0;
180         m_second    = (params.length == 6) ? to!uint(params[5]) : 0;
181 
182         foreach(string param; params[]) delete param;
183 
184         m_tag = (m_year >= 2050) ? ASN1Tag.GENERALIZED_TIME : ASN1Tag.UTC_TIME;
185         
186         if (!passesSanityCheck())
187             throw new InvalidArgument("Invalid time specification " ~ time_str);
188     }
189 
190 
191     /*
192     * Set the time with an ISO time format string
193     */
194     void setTo(in string t_spec, ASN1Tag spec_tag)
195     {
196         if (spec_tag == ASN1Tag.GENERALIZED_TIME)
197         {
198             if (t_spec.length != 13 && t_spec.length != 15)
199                 throw new InvalidArgument("Invalid GeneralizedTime: " ~ t_spec);
200         }
201         else if (spec_tag == ASN1Tag.UTC_TIME)
202         {
203             if (t_spec.length != 11 && t_spec.length != 13)
204                 throw new InvalidArgument("Invalid UTCTime: " ~ t_spec);
205         }
206         else
207         {
208             throw new InvalidArgument("Invalid time m_tag " ~ to!string(spec_tag) ~ " val " ~ t_spec);
209         }
210         
211         if (t_spec[t_spec.length-1] != 'Z')
212             throw new InvalidArgument("Invalid time encoding: " ~ t_spec);
213         
214         const size_t YEAR_SIZE = (spec_tag == ASN1Tag.UTC_TIME) ? 2 : 4;
215         
216         Vector!(string) params;
217         Vector!ubyte current;
218         current.reserve(YEAR_SIZE);
219         foreach (size_t j; 0 .. YEAR_SIZE)
220             current ~= t_spec[j];
221         params.pushBack(current[].idup);
222         current.clear();
223         
224         for (size_t j = YEAR_SIZE; j != t_spec.length - 1; ++j)
225         {
226             current ~= t_spec[j];
227             if (current.length == 2)
228             {
229                 params.pushBack(current[].idup);
230                 current.clear();
231             }
232         }
233         
234         m_year    = to!uint(params[0]);
235         m_month   = to!uint(params[1]);
236         m_day     = to!uint(params[2]);
237         m_hour    = to!uint(params[3]);
238         m_minute  = to!uint(params[4]);
239         m_second  = (params.length == 6) ? to!uint(params[5]) : 0;
240         m_tag     = spec_tag;
241         
242         foreach(string param; params[]) delete param;
243 
244         if (spec_tag == ASN1Tag.UTC_TIME)
245         {
246             if (m_year >= 50) m_year += 1900;
247             else              m_year += 2000;
248         }
249         
250         if (!passesSanityCheck())
251             throw new InvalidArgument("Invalid time specification " ~ t_spec);
252     }
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 }