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.Locale; 8 import java.util.SimpleTimeZone; 9 10 import org.bouncycastle.util.Arrays; 11 import org.bouncycastle.util.Strings; 12 13 /** 14 - * UTC time object. 15 * Internal facade of {@link ASN1UTCTime}. 16 * <p> 17 * This datatype is valid only from 1950-01-01 00:00:00 UTC until 2049-12-31 23:59:59 UTC. 18 * </p> 19 * <hr> 20 * <p><b>X.690</b></p> 21 * <p><b>11: Restrictions on BER employed by both CER and DER</b></p> 22 * <p><b>11.8 UTCTime </b></p> 23 * <b>11.8.1</b> The encoding shall terminate with "Z", 24 * as described in the ITU-T X.680 | ISO/IEC 8824-1 clause on UTCTime. 25 * <p> 26 * <b>11.8.2</b> The seconds element shall always be present. 27 * <p> 28 * <b>11.8.3</b> Midnight (GMT) shall be represented in the form: 29 * <blockquote> 30 * "YYMMDD000000Z" 31 * </blockquote> 32 * where "YYMMDD" represents the day following the midnight in question. 33 */ 34 public class ASN1UTCTime 35 extends ASN1Primitive 36 { 37 private byte[] time; 38 39 /** 40 * Return an UTC Time from the passed in object. 41 * 42 * @param obj an ASN1UTCTime or an object that can be converted into one. 43 * @exception IllegalArgumentException if the object cannot be converted. 44 * @return an ASN1UTCTime instance, or null. 45 */ getInstance( Object obj)46 public static ASN1UTCTime getInstance( 47 Object obj) 48 { 49 if (obj == null || obj instanceof ASN1UTCTime) 50 { 51 return (ASN1UTCTime)obj; 52 } 53 54 if (obj instanceof byte[]) 55 { 56 try 57 { 58 return (ASN1UTCTime)fromByteArray((byte[])obj); 59 } 60 catch (Exception e) 61 { 62 throw new IllegalArgumentException("encoding error in getInstance: " + e.toString()); 63 } 64 } 65 66 throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); 67 } 68 69 /** 70 * Return an UTC Time from a tagged object. 71 * 72 * @param obj the tagged object holding the object we want 73 * @param explicit true if the object is meant to be explicitly 74 * tagged false otherwise. 75 * @exception IllegalArgumentException if the tagged object cannot 76 * be converted. 77 * @return an ASN1UTCTime instance, or null. 78 */ getInstance( ASN1TaggedObject obj, boolean explicit)79 public static ASN1UTCTime getInstance( 80 ASN1TaggedObject obj, 81 boolean explicit) 82 { 83 ASN1Object o = obj.getObject(); 84 85 if (explicit || o instanceof ASN1UTCTime) 86 { 87 return getInstance(o); 88 } 89 else 90 { 91 return new ASN1UTCTime(ASN1OctetString.getInstance(o).getOctets()); 92 } 93 } 94 95 /** 96 * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were 97 * never encoded. When you're creating one of these objects from scratch, that's 98 * what you want to use, otherwise we'll try to deal with whatever gets read from 99 * the input stream... (this is why the input format is different from the getTime() 100 * method output). 101 * <p> 102 * 103 * @param time the time string. 104 */ ASN1UTCTime( String time)105 public ASN1UTCTime( 106 String time) 107 { 108 this.time = Strings.toByteArray(time); 109 try 110 { 111 this.getDate(); 112 } 113 catch (ParseException e) 114 { 115 throw new IllegalArgumentException("invalid date string: " + e.getMessage()); 116 } 117 } 118 119 /** 120 * Base constructor from a java.util.date object 121 * @param time the Date to build the time from. 122 */ ASN1UTCTime( Date time)123 public ASN1UTCTime( 124 Date time) 125 { 126 SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'", DateUtil.EN_Locale); 127 128 dateF.setTimeZone(new SimpleTimeZone(0,"Z")); 129 130 this.time = Strings.toByteArray(dateF.format(time)); 131 } 132 133 /** 134 * Base constructor from a java.util.date and Locale - you may need to use this if the default locale 135 * doesn't use a Gregorian calender so that the GeneralizedTime produced is compatible with other ASN.1 implementations. 136 * 137 * @param time a date object representing the time of interest. 138 * @param locale an appropriate Locale for producing an ASN.1 UTCTime value. 139 */ ASN1UTCTime( Date time, Locale locale)140 public ASN1UTCTime( 141 Date time, 142 Locale locale) 143 { 144 SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'", locale); 145 146 dateF.setTimeZone(new SimpleTimeZone(0,"Z")); 147 148 this.time = Strings.toByteArray(dateF.format(time)); 149 } 150 ASN1UTCTime( byte[] time)151 ASN1UTCTime( 152 byte[] time) 153 { 154 if (time.length < 2) 155 { 156 throw new IllegalArgumentException("UTCTime string too short"); 157 } 158 this.time = time; 159 if (!(isDigit(0) && isDigit(1))) 160 { 161 throw new IllegalArgumentException("illegal characters in UTCTime string"); 162 } 163 } 164 165 /** 166 * Return the time as a date based on whatever a 2 digit year will return. For 167 * standardised processing use getAdjustedDate(). 168 * 169 * @return the resulting date 170 * @exception ParseException if the date string cannot be parsed. 171 */ getDate()172 public Date getDate() 173 throws ParseException 174 { 175 SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmssz"); 176 177 return DateUtil.epochAdjust(dateF.parse(getTime())); 178 } 179 180 /** 181 * Return the time as an adjusted date 182 * in the range of 1950 - 2049. 183 * 184 * @return a date in the range of 1950 to 2049. 185 * @exception ParseException if the date string cannot be parsed. 186 */ getAdjustedDate()187 public Date getAdjustedDate() 188 throws ParseException 189 { 190 SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 191 192 dateF.setTimeZone(new SimpleTimeZone(0,"Z")); 193 194 return DateUtil.epochAdjust(dateF.parse(getAdjustedTime())); 195 } 196 197 /** 198 * Return the time - always in the form of 199 * YYMMDDhhmmssGMT(+hh:mm|-hh:mm). 200 * <p> 201 * Normally in a certificate we would expect "Z" rather than "GMT", 202 * however adding the "GMT" means we can just use: 203 * <pre> 204 * dateF = new SimpleDateFormat("yyMMddHHmmssz"); 205 * </pre> 206 * To read in the time and get a date which is compatible with our local 207 * time zone. 208 * <p> 209 * <b>Note:</b> In some cases, due to the local date processing, this 210 * may lead to unexpected results. If you want to stick the normal 211 * convention of 1950 to 2049 use the getAdjustedTime() method. 212 */ getTime()213 public String getTime() 214 { 215 String stime = Strings.fromByteArray(time); 216 217 // 218 // standardise the format. 219 // 220 if (stime.indexOf('-') < 0 && stime.indexOf('+') < 0) 221 { 222 if (stime.length() == 11) 223 { 224 return stime.substring(0, 10) + "00GMT+00:00"; 225 } 226 else 227 { 228 return stime.substring(0, 12) + "GMT+00:00"; 229 } 230 } 231 else 232 { 233 int index = stime.indexOf('-'); 234 if (index < 0) 235 { 236 index = stime.indexOf('+'); 237 } 238 String d = stime; 239 240 if (index == stime.length() - 3) 241 { 242 d += "00"; 243 } 244 245 if (index == 10) 246 { 247 return d.substring(0, 10) + "00GMT" + d.substring(10, 13) + ":" + d.substring(13, 15); 248 } 249 else 250 { 251 return d.substring(0, 12) + "GMT" + d.substring(12, 15) + ":" + d.substring(15, 17); 252 } 253 } 254 } 255 256 /** 257 * Return a time string as an adjusted date with a 4 digit year. This goes 258 * in the range of 1950 - 2049. 259 */ getAdjustedTime()260 public String getAdjustedTime() 261 { 262 String d = this.getTime(); 263 264 if (d.charAt(0) < '5') 265 { 266 return "20" + d; 267 } 268 else 269 { 270 return "19" + d; 271 } 272 } 273 isDigit(int pos)274 private boolean isDigit(int pos) 275 { 276 return time.length > pos && time[pos] >= '0' && time[pos] <= '9'; 277 } 278 isConstructed()279 boolean isConstructed() 280 { 281 return false; 282 } 283 encodedLength()284 int encodedLength() 285 { 286 int length = time.length; 287 288 return 1 + StreamUtil.calculateBodyLength(length) + length; 289 } 290 encode(ASN1OutputStream out, boolean withTag)291 void encode(ASN1OutputStream out, boolean withTag) throws IOException 292 { 293 out.writeEncoded(withTag, BERTags.UTC_TIME, time); 294 } 295 asn1Equals( ASN1Primitive o)296 boolean asn1Equals( 297 ASN1Primitive o) 298 { 299 if (!(o instanceof ASN1UTCTime)) 300 { 301 return false; 302 } 303 304 return Arrays.areEqual(time, ((ASN1UTCTime)o).time); 305 } 306 hashCode()307 public int hashCode() 308 { 309 return Arrays.hashCode(time); 310 } 311 toString()312 public String toString() 313 { 314 return Strings.fromByteArray(time); 315 } 316 } 317