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[]) destroy(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[]) destroy(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 }