001package org.apache.commons.ssl.org.bouncycastle.asn1; 002 003import java.io.IOException; 004import java.text.ParseException; 005import java.text.SimpleDateFormat; 006import java.util.Date; 007import java.util.Locale; 008import java.util.SimpleTimeZone; 009import java.util.TimeZone; 010 011import org.bouncycastle.util.Arrays; 012import org.bouncycastle.util.Strings; 013 014/** 015 * Base class representing the ASN.1 GeneralizedTime type. 016 * <p> 017 * The main difference between these and UTC time is a 4 digit year. 018 * </p> 019 */ 020public class ASN1GeneralizedTime 021 extends ASN1Primitive 022{ 023 private byte[] time; 024 025 /** 026 * return a generalized time from the passed in object 027 * 028 * @param obj an ASN1GeneralizedTime or an object that can be converted into one. 029 * @return an ASN1GeneralizedTime instance, or null. 030 * @throws IllegalArgumentException if the object cannot be converted. 031 */ 032 public static ASN1GeneralizedTime getInstance( 033 Object obj) 034 { 035 if (obj == null || obj instanceof ASN1GeneralizedTime) 036 { 037 return (ASN1GeneralizedTime)obj; 038 } 039 040 if (obj instanceof byte[]) 041 { 042 try 043 { 044 return (ASN1GeneralizedTime)fromByteArray((byte[])obj); 045 } 046 catch (Exception e) 047 { 048 throw new IllegalArgumentException("encoding error in getInstance: " + e.toString()); 049 } 050 } 051 052 throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); 053 } 054 055 /** 056 * return a Generalized Time object from a tagged object. 057 * 058 * @param obj the tagged object holding the object we want 059 * @param explicit true if the object is meant to be explicitly 060 * tagged false otherwise. 061 * @return an ASN1GeneralizedTime instance. 062 * @throws IllegalArgumentException if the tagged object cannot 063 * be converted. 064 */ 065 public static ASN1GeneralizedTime getInstance( 066 ASN1TaggedObject obj, 067 boolean explicit) 068 { 069 ASN1Primitive o = obj.getObject(); 070 071 if (explicit || o instanceof ASN1GeneralizedTime) 072 { 073 return getInstance(o); 074 } 075 else 076 { 077 return new ASN1GeneralizedTime(((ASN1OctetString)o).getOctets()); 078 } 079 } 080 081 /** 082 * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z 083 * for local time, or Z+-HHMM on the end, for difference between local 084 * time and UTC time. The fractional second amount f must consist of at 085 * least one number with trailing zeroes removed. 086 * 087 * @param time the time string. 088 * @throws IllegalArgumentException if String is an illegal format. 089 */ 090 public ASN1GeneralizedTime( 091 String time) 092 { 093 this.time = Strings.toByteArray(time); 094 try 095 { 096 this.getDate(); 097 } 098 catch (ParseException e) 099 { 100 throw new IllegalArgumentException("invalid date string: " + e.getMessage()); 101 } 102 } 103 104 /** 105 * Base constructor from a java.util.date object 106 * 107 * @param time a date object representing the time of interest. 108 */ 109 public ASN1GeneralizedTime( 110 Date time) 111 { 112 SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); 113 114 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 115 116 this.time = Strings.toByteArray(dateF.format(time)); 117 } 118 119 /** 120 * Base constructor from a java.util.date and Locale - you may need to use this if the default locale 121 * doesn't use a Gregorian calender so that the GeneralizedTime produced is compatible with other ASN.1 implementations. 122 * 123 * @param time a date object representing the time of interest. 124 * @param locale an appropriate Locale for producing an ASN.1 GeneralizedTime value. 125 */ 126 public ASN1GeneralizedTime( 127 Date time, 128 Locale locale) 129 { 130 SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'", locale); 131 132 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 133 134 this.time = Strings.toByteArray(dateF.format(time)); 135 } 136 137 ASN1GeneralizedTime( 138 byte[] bytes) 139 { 140 this.time = bytes; 141 } 142 143 /** 144 * Return the time. 145 * 146 * @return The time string as it appeared in the encoded object. 147 */ 148 public String getTimeString() 149 { 150 return Strings.fromByteArray(time); 151 } 152 153 /** 154 * return the time - always in the form of 155 * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm). 156 * <p> 157 * Normally in a certificate we would expect "Z" rather than "GMT", 158 * however adding the "GMT" means we can just use: 159 * <pre> 160 * dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 161 * </pre> 162 * To read in the time and get a date which is compatible with our local 163 * time zone. 164 * </p> 165 */ 166 public String getTime() 167 { 168 String stime = Strings.fromByteArray(time); 169 170 // 171 // standardise the format. 172 // 173 if (stime.charAt(stime.length() - 1) == 'Z') 174 { 175 return stime.substring(0, stime.length() - 1) + "GMT+00:00"; 176 } 177 else 178 { 179 int signPos = stime.length() - 5; 180 char sign = stime.charAt(signPos); 181 if (sign == '-' || sign == '+') 182 { 183 return stime.substring(0, signPos) 184 + "GMT" 185 + stime.substring(signPos, signPos + 3) 186 + ":" 187 + stime.substring(signPos + 3); 188 } 189 else 190 { 191 signPos = stime.length() - 3; 192 sign = stime.charAt(signPos); 193 if (sign == '-' || sign == '+') 194 { 195 return stime.substring(0, signPos) 196 + "GMT" 197 + stime.substring(signPos) 198 + ":00"; 199 } 200 } 201 } 202 return stime + calculateGMTOffset(); 203 } 204 205 private String calculateGMTOffset() 206 { 207 String sign = "+"; 208 TimeZone timeZone = TimeZone.getDefault(); 209 int offset = timeZone.getRawOffset(); 210 if (offset < 0) 211 { 212 sign = "-"; 213 offset = -offset; 214 } 215 int hours = offset / (60 * 60 * 1000); 216 int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000); 217 218 try 219 { 220 if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate())) 221 { 222 hours += sign.equals("+") ? 1 : -1; 223 } 224 } 225 catch (ParseException e) 226 { 227 // we'll do our best and ignore daylight savings 228 } 229 230 return "GMT" + sign + convert(hours) + ":" + convert(minutes); 231 } 232 233 private String convert(int time) 234 { 235 if (time < 10) 236 { 237 return "0" + time; 238 } 239 240 return Integer.toString(time); 241 } 242 243 public Date getDate() 244 throws ParseException 245 { 246 SimpleDateFormat dateF; 247 String stime = Strings.fromByteArray(time); 248 String d = stime; 249 250 if (stime.endsWith("Z")) 251 { 252 if (hasFractionalSeconds()) 253 { 254 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'"); 255 } 256 else 257 { 258 dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); 259 } 260 261 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 262 } 263 else if (stime.indexOf('-') > 0 || stime.indexOf('+') > 0) 264 { 265 d = this.getTime(); 266 if (hasFractionalSeconds()) 267 { 268 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSz"); 269 } 270 else 271 { 272 dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 273 } 274 275 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 276 } 277 else 278 { 279 if (hasFractionalSeconds()) 280 { 281 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); 282 } 283 else 284 { 285 dateF = new SimpleDateFormat("yyyyMMddHHmmss"); 286 } 287 288 dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID())); 289 } 290 291 if (hasFractionalSeconds()) 292 { 293 // java misinterprets extra digits as being milliseconds... 294 String frac = d.substring(14); 295 int index; 296 for (index = 1; index < frac.length(); index++) 297 { 298 char ch = frac.charAt(index); 299 if (!('0' <= ch && ch <= '9')) 300 { 301 break; 302 } 303 } 304 305 if (index - 1 > 3) 306 { 307 frac = frac.substring(0, 4) + frac.substring(index); 308 d = d.substring(0, 14) + frac; 309 } 310 else if (index - 1 == 1) 311 { 312 frac = frac.substring(0, index) + "00" + frac.substring(index); 313 d = d.substring(0, 14) + frac; 314 } 315 else if (index - 1 == 2) 316 { 317 frac = frac.substring(0, index) + "0" + frac.substring(index); 318 d = d.substring(0, 14) + frac; 319 } 320 } 321 322 return dateF.parse(d); 323 } 324 325 private boolean hasFractionalSeconds() 326 { 327 for (int i = 0; i != time.length; i++) 328 { 329 if (time[i] == '.') 330 { 331 if (i == 14) 332 { 333 return true; 334 } 335 } 336 } 337 return false; 338 } 339 340 boolean isConstructed() 341 { 342 return false; 343 } 344 345 int encodedLength() 346 { 347 int length = time.length; 348 349 return 1 + StreamUtil.calculateBodyLength(length) + length; 350 } 351 352 void encode( 353 ASN1OutputStream out) 354 throws IOException 355 { 356 out.writeEncoded(BERTags.GENERALIZED_TIME, time); 357 } 358 359 boolean asn1Equals( 360 ASN1Primitive o) 361 { 362 if (!(o instanceof ASN1GeneralizedTime)) 363 { 364 return false; 365 } 366 367 return Arrays.areEqual(time, ((ASN1GeneralizedTime)o).time); 368 } 369 370 public int hashCode() 371 { 372 return Arrays.hashCode(time); 373 } 374}