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