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 }