Home | History | Annotate | Download | only in asn1
      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