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;
009
010import org.bouncycastle.util.Arrays;
011import org.bouncycastle.util.Strings;
012
013/**
014- * UTC time object.
015 * Internal facade of {@link ASN1UTCTime}.
016 * <p>
017 * This datatype is valid only from 1950-01-01 00:00:00 UTC until 2049-12-31 23:59:59 UTC.
018 * <p>
019 * <hr>
020 * <p><b>X.690</b></p>
021 * <p><b>11: Restrictions on BER employed by both CER and DER</b></p>
022 * <p><b>11.8 UTCTime </b></p>
023 * <b>11.8.1</b> The encoding shall terminate with "Z",
024 * as described in the ITU-T X.680 | ISO/IEC 8824-1 clause on UTCTime.
025 * <p>
026 * <b>11.8.2</b> The seconds element shall always be present.
027 * <p>
028 * <b>11.8.3</b> Midnight (GMT) shall be represented in the form:
029 * <blockquote>
030 * "YYMMDD000000Z"
031 * </blockquote>
032 * where "YYMMDD" represents the day following the midnight in question.
033 */
034public class ASN1UTCTime
035    extends ASN1Primitive
036{
037    private byte[]      time;
038
039    /**
040     * return an UTC Time from the passed in object.
041     *
042     * @param obj an ASN1UTCTime or an object that can be converted into one.
043     * @exception IllegalArgumentException if the object cannot be converted.
044     * @return an ASN1UTCTime instance, or null.
045     */
046    public static ASN1UTCTime getInstance(
047        Object  obj)
048    {
049        if (obj == null || obj instanceof ASN1UTCTime)
050        {
051            return (ASN1UTCTime)obj;
052        }
053
054        if (obj instanceof byte[])
055        {
056            try
057            {
058                return (ASN1UTCTime)fromByteArray((byte[])obj);
059            }
060            catch (Exception e)
061            {
062                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
063            }
064        }
065
066        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
067    }
068
069    /**
070     * return an UTC Time from a tagged object.
071     *
072     * @param obj the tagged object holding the object we want
073     * @param explicit true if the object is meant to be explicitly
074     *              tagged false otherwise.
075     * @exception IllegalArgumentException if the tagged object cannot
076     *               be converted.
077     * @return an ASN1UTCTime instance, or null.
078     */
079    public static ASN1UTCTime getInstance(
080        ASN1TaggedObject obj,
081        boolean          explicit)
082    {
083        ASN1Object o = obj.getObject();
084
085        if (explicit || o instanceof ASN1UTCTime)
086        {
087            return getInstance(o);
088        }
089        else
090        {
091            return new ASN1UTCTime(((ASN1OctetString)o).getOctets());
092        }
093    }
094
095    /**
096     * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
097     * never encoded. When you're creating one of these objects from scratch, that's
098     * what you want to use, otherwise we'll try to deal with whatever gets read from
099     * 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     */
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     */
123    public ASN1UTCTime(
124        Date time)
125    {
126        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'");
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     */
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
151    ASN1UTCTime(
152        byte[] time)
153    {
154        this.time = time;
155    }
156
157    /**
158     * return the time as a date based on whatever a 2 digit year will return. For
159     * standardised processing use getAdjustedDate().
160     *
161     * @return the resulting date
162     * @exception ParseException if the date string cannot be parsed.
163     */
164    public Date getDate()
165        throws ParseException
166    {
167        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmssz");
168
169        return dateF.parse(getTime());
170    }
171
172    /**
173     * return the time as an adjusted date
174     * in the range of 1950 - 2049.
175     *
176     * @return a date in the range of 1950 to 2049.
177     * @exception ParseException if the date string cannot be parsed.
178     */
179    public Date getAdjustedDate()
180        throws ParseException
181    {
182        SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
183
184        dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
185
186        return dateF.parse(getAdjustedTime());
187    }
188
189    /**
190     * return the time - always in the form of
191     *  YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
192     * <p>
193     * Normally in a certificate we would expect "Z" rather than "GMT",
194     * however adding the "GMT" means we can just use:
195     * <pre>
196     *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
197     * </pre>
198     * To read in the time and get a date which is compatible with our local
199     * time zone.
200     * <p>
201     * <b>Note:</b> In some cases, due to the local date processing, this
202     * may lead to unexpected results. If you want to stick the normal
203     * convention of 1950 to 2049 use the getAdjustedTime() method.
204     */
205    public String getTime()
206    {
207        String stime = Strings.fromByteArray(time);
208
209        //
210        // standardise the format.
211        //
212        if (stime.indexOf('-') < 0 && stime.indexOf('+') < 0)
213        {
214            if (stime.length() == 11)
215            {
216                return stime.substring(0, 10) + "00GMT+00:00";
217            }
218            else
219            {
220                return stime.substring(0, 12) + "GMT+00:00";
221            }
222        }
223        else
224        {
225            int index = stime.indexOf('-');
226            if (index < 0)
227            {
228                index = stime.indexOf('+');
229            }
230            String d = stime;
231
232            if (index == stime.length() - 3)
233            {
234                d += "00";
235            }
236
237            if (index == 10)
238            {
239                return d.substring(0, 10) + "00GMT" + d.substring(10, 13) + ":" + d.substring(13, 15);
240            }
241            else
242            {
243                return d.substring(0, 12) + "GMT" + d.substring(12, 15) + ":" +  d.substring(15, 17);
244            }
245        }
246    }
247
248    /**
249     * return a time string as an adjusted date with a 4 digit year. This goes
250     * in the range of 1950 - 2049.
251     */
252    public String getAdjustedTime()
253    {
254        String   d = this.getTime();
255
256        if (d.charAt(0) < '5')
257        {
258            return "20" + d;
259        }
260        else
261        {
262            return "19" + d;
263        }
264    }
265
266    boolean isConstructed()
267    {
268        return false;
269    }
270
271    int encodedLength()
272    {
273        int length = time.length;
274
275        return 1 + StreamUtil.calculateBodyLength(length) + length;
276    }
277
278    void encode(
279        ASN1OutputStream  out)
280        throws IOException
281    {
282        out.write(BERTags.UTC_TIME);
283
284        int length = time.length;
285
286        out.writeLength(length);
287
288        for (int i = 0; i != length; i++)
289        {
290            out.write((byte)time[i]);
291        }
292    }
293
294    boolean asn1Equals(
295        ASN1Primitive o)
296    {
297        if (!(o instanceof ASN1UTCTime))
298        {
299            return false;
300        }
301
302        return Arrays.areEqual(time, ((ASN1UTCTime)o).time);
303    }
304
305    public int hashCode()
306    {
307        return Arrays.hashCode(time);
308    }
309
310    public String toString()
311    {
312      return Strings.fromByteArray(time);
313    }
314}