1 package org.bouncycastle.asn1; 2 3 import java.io.IOException; 4 import java.text.ParseException; 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 import java.util.SimpleTimeZone; 8 import java.util.TimeZone; 9 10 import org.bouncycastle.util.Arrays; 11 import org.bouncycastle.util.Strings; 12 13 /** 14 * Generalized time object. 15 */ 16 public class DERGeneralizedTime 17 extends ASN1Primitive 18 { 19 private byte[] time; 20 21 /** 22 * return a generalized time from the passed in object 23 * 24 * @exception IllegalArgumentException if the object cannot be converted. 25 */ 26 public static ASN1GeneralizedTime getInstance( 27 Object obj) 28 { 29 if (obj == null || obj instanceof ASN1GeneralizedTime) 30 { 31 return (ASN1GeneralizedTime)obj; 32 } 33 34 if (obj instanceof DERGeneralizedTime) 35 { 36 return new ASN1GeneralizedTime(((DERGeneralizedTime)obj).time); 37 } 38 39 if (obj instanceof byte[]) 40 { 41 try 42 { 43 return (ASN1GeneralizedTime)fromByteArray((byte[])obj); 44 } 45 catch (Exception e) 46 { 47 throw new IllegalArgumentException("encoding error in getInstance: " + e.toString()); 48 } 49 } 50 51 throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); 52 } 53 54 /** 55 * return a Generalized Time object from a tagged object. 56 * 57 * @param obj the tagged object holding the object we want 58 * @param explicit true if the object is meant to be explicitly 59 * tagged false otherwise. 60 * @exception IllegalArgumentException if the tagged object cannot 61 * be converted. 62 */ 63 public static ASN1GeneralizedTime getInstance( 64 ASN1TaggedObject obj, 65 boolean explicit) 66 { 67 ASN1Primitive o = obj.getObject(); 68 69 if (explicit || o instanceof DERGeneralizedTime) 70 { 71 return getInstance(o); 72 } 73 else 74 { 75 return new ASN1GeneralizedTime(((ASN1OctetString)o).getOctets()); 76 } 77 } 78 79 /** 80 * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z 81 * for local time, or Z+-HHMM on the end, for difference between local 82 * time and UTC time. The fractional second amount f must consist of at 83 * least one number with trailing zeroes removed. 84 * 85 * @param time the time string. 86 * @exception IllegalArgumentException if String is an illegal format. 87 */ 88 public DERGeneralizedTime( 89 String time) 90 { 91 this.time = Strings.toByteArray(time); 92 try 93 { 94 this.getDate(); 95 } 96 catch (ParseException e) 97 { 98 throw new IllegalArgumentException("invalid date string: " + e.getMessage()); 99 } 100 } 101 102 /** 103 * base constructor from a java.util.date object 104 */ 105 public DERGeneralizedTime( 106 Date time) 107 { 108 SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); 109 110 dateF.setTimeZone(new SimpleTimeZone(0,"Z")); 111 112 this.time = Strings.toByteArray(dateF.format(time)); 113 } 114 115 DERGeneralizedTime( 116 byte[] bytes) 117 { 118 this.time = bytes; 119 } 120 121 /** 122 * Return the time. 123 * @return The time string as it appeared in the encoded object. 124 */ 125 public String getTimeString() 126 { 127 return Strings.fromByteArray(time); 128 } 129 130 /** 131 * return the time - always in the form of 132 * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm). 133 * <p> 134 * Normally in a certificate we would expect "Z" rather than "GMT", 135 * however adding the "GMT" means we can just use: 136 * <pre> 137 * dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 138 * </pre> 139 * To read in the time and get a date which is compatible with our local 140 * time zone. 141 */ 142 public String getTime() 143 { 144 String stime = Strings.fromByteArray(time); 145 146 // 147 // standardise the format. 148 // 149 if (stime.charAt(stime.length() - 1) == 'Z') 150 { 151 return stime.substring(0, stime.length() - 1) + "GMT+00:00"; 152 } 153 else 154 { 155 int signPos = stime.length() - 5; 156 char sign = stime.charAt(signPos); 157 if (sign == '-' || sign == '+') 158 { 159 return stime.substring(0, signPos) 160 + "GMT" 161 + stime.substring(signPos, signPos + 3) 162 + ":" 163 + stime.substring(signPos + 3); 164 } 165 else 166 { 167 signPos = stime.length() - 3; 168 sign = stime.charAt(signPos); 169 if (sign == '-' || sign == '+') 170 { 171 return stime.substring(0, signPos) 172 + "GMT" 173 + stime.substring(signPos) 174 + ":00"; 175 } 176 } 177 } 178 return stime + calculateGMTOffset(); 179 } 180 181 private String calculateGMTOffset() 182 { 183 String sign = "+"; 184 TimeZone timeZone = TimeZone.getDefault(); 185 int offset = timeZone.getRawOffset(); 186 if (offset < 0) 187 { 188 sign = "-"; 189 offset = -offset; 190 } 191 int hours = offset / (60 * 60 * 1000); 192 int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000); 193 194 try 195 { 196 if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate())) 197 { 198 hours += sign.equals("+") ? 1 : -1; 199 } 200 } 201 catch (ParseException e) 202 { 203 // we'll do our best and ignore daylight savings 204 } 205 206 return "GMT" + sign + convert(hours) + ":" + convert(minutes); 207 } 208 209 private String convert(int time) 210 { 211 if (time < 10) 212 { 213 return "0" + time; 214 } 215 216 return Integer.toString(time); 217 } 218 219 public Date getDate() 220 throws ParseException 221 { 222 SimpleDateFormat dateF; 223 String stime = Strings.fromByteArray(time); 224 String d = stime; 225 226 if (stime.endsWith("Z")) 227 { 228 if (hasFractionalSeconds()) 229 { 230 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'"); 231 } 232 else 233 { 234 dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); 235 } 236 237 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 238 } 239 else if (stime.indexOf('-') > 0 || stime.indexOf('+') > 0) 240 { 241 d = this.getTime(); 242 if (hasFractionalSeconds()) 243 { 244 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSz"); 245 } 246 else 247 { 248 dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 249 } 250 251 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 252 } 253 else 254 { 255 if (hasFractionalSeconds()) 256 { 257 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); 258 } 259 else 260 { 261 dateF = new SimpleDateFormat("yyyyMMddHHmmss"); 262 } 263 264 dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID())); 265 } 266 267 if (hasFractionalSeconds()) 268 { 269 // java misinterprets extra digits as being milliseconds... 270 String frac = d.substring(14); 271 int index; 272 for (index = 1; index < frac.length(); index++) 273 { 274 char ch = frac.charAt(index); 275 if (!('0' <= ch && ch <= '9')) 276 { 277 break; 278 } 279 } 280 281 if (index - 1 > 3) 282 { 283 frac = frac.substring(0, 4) + frac.substring(index); 284 d = d.substring(0, 14) + frac; 285 } 286 else if (index - 1 == 1) 287 { 288 frac = frac.substring(0, index) + "00" + frac.substring(index); 289 d = d.substring(0, 14) + frac; 290 } 291 else if (index - 1 == 2) 292 { 293 frac = frac.substring(0, index) + "0" + frac.substring(index); 294 d = d.substring(0, 14) + frac; 295 } 296 } 297 298 return dateF.parse(d); 299 } 300 301 private boolean hasFractionalSeconds() 302 { 303 for (int i = 0; i != time.length; i++) 304 { 305 if (time[i] == '.') 306 { 307 if (i == 14) 308 { 309 return true; 310 } 311 } 312 } 313 return false; 314 } 315 316 boolean isConstructed() 317 { 318 return false; 319 } 320 321 int encodedLength() 322 { 323 int length = time.length; 324 325 return 1 + StreamUtil.calculateBodyLength(length) + length; 326 } 327 328 void encode( 329 ASN1OutputStream out) 330 throws IOException 331 { 332 out.writeEncoded(BERTags.GENERALIZED_TIME, time); 333 } 334 335 boolean asn1Equals( 336 ASN1Primitive o) 337 { 338 if (!(o instanceof DERGeneralizedTime)) 339 { 340 return false; 341 } 342 343 return Arrays.areEqual(time, ((DERGeneralizedTime)o).time); 344 } 345 346 public int hashCode() 347 { 348 return Arrays.hashCode(time); 349 } 350 } 351