    001package org.apache.commons.ssl.asn1;
    002
    003import java.io.IOException;
    004import java.text.ParseException;
    005import java.text.SimpleDateFormat;
    006import java.util.Date;
    007import java.util.SimpleTimeZone;
    008import java.util.TimeZone;
    009
    010/** Generalized time object. */
    011public class DERGeneralizedTime
    012    extends ASN1Object {
    013    String time;
    014
    015    /**
    016     * return a generalized time from the passed in object
    017     *
    018     * @throws IllegalArgumentException if the object cannot be converted.
    019     */
    020    public static DERGeneralizedTime getInstance(
    021        Object obj) {
    022        if (obj == null || obj instanceof DERGeneralizedTime) {
    023            return (DERGeneralizedTime) obj;
    024        }
    025
    026        if (obj instanceof ASN1OctetString) {
    027            return new DERGeneralizedTime(((ASN1OctetString) obj).getOctets());
    028        }
    029
    030        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
    031    }
    032
    033    /**
    034     * return a Generalized Time object from a tagged object.
    035     *
    036     * @param obj      the tagged object holding the object we want
    037     * @param explicit true if the object is meant to be explicitly
    038     *                 tagged false otherwise.
    039     * @throws IllegalArgumentException if the tagged object cannot
    040     *                                  be converted.
    041     */
    042    public static DERGeneralizedTime getInstance(
    043        ASN1TaggedObject obj,
    044        boolean explicit) {
    045        return getInstance(obj.getObject());
    046    }
    047
    048    /**
    049     * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z
    050     * for local time, or Z+-HHMM on the end, for difference between local
    051     * time and UTC time. The fractional second amount f must consist of at
    052     * least one number with trailing zeroes removed.
    053     *
    054     * @param time the time string.
    055     * @throws IllegalArgumentException if String is an illegal format.
    056     */
    057    public DERGeneralizedTime(
    058        String time) {
    059        this.time = time;
    060        try {
    061            this.getDate();
    062        }
    063        catch (ParseException e) {
    064            throw new IllegalArgumentException("invalid date string: " + e.getMessage());
    065        }
    066    }
    067
    068    /** base constructer from a java.util.date object */
    069    public DERGeneralizedTime(
    070        Date time) {
    071        SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
    072
    073        dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
    074
    075        this.time = dateF.format(time);
    076    }
    077
    078    DERGeneralizedTime(
    079        byte[] bytes) {
    080        //
    081        // explicitly convert to characters
    082        //
    083        char[] dateC = new char[bytes.length];
    084
    085        for (int i = 0; i != dateC.length; i++) {
    086            dateC[i] = (char) (bytes[i] & 0xff);
    087        }
    088
    089        this.time = new String(dateC);
    090    }
    091
    092    /**
    093     * Return the time.
    094     *
    095     * @return The time string as it appeared in the encoded object.
    096     */
    097    public String getTimeString() {
    098        return time;
    099    }
    100
    101    /**
    102     * return the time - always in the form of
    103     * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
    104     * <p/>
    105     * Normally in a certificate we would expect "Z" rather than "GMT",
    106     * however adding the "GMT" means we can just use:
    107     * <pre>
    108     *     dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
    109     * </pre>
    110     * To read in the time and get a date which is compatible with our local
    111     * time zone.
    112     */
    113    public String getTime() {
    114        //
    115        // standardise the format.
    116        //
    117        if (time.charAt(time.length() - 1) == 'Z') {
    118            return time.substring(0, time.length() - 1) + "GMT+00:00";
    119        } else {
    120            int signPos = time.length() - 5;
    121            char sign = time.charAt(signPos);
    122            if (sign == '-' || sign == '+') {
    123                return time.substring(0, signPos)
    124                       + "GMT"
    125                       + time.substring(signPos, signPos + 3)
    126                       + ":"
    127                       + time.substring(signPos + 3);
    128            } else {
    129                signPos = time.length() - 3;
    130                sign = time.charAt(signPos);
    131                if (sign == '-' || sign == '+') {
    132                    return time.substring(0, signPos)
    133                           + "GMT"
    134                           + time.substring(signPos)
    135                           + ":00";
    136                }
    137            }
    138        }
    139        return time + calculateGMTOffset();
    140    }
    141
    142    private String calculateGMTOffset() {
    143        String sign = "+";
    144        TimeZone timeZone = TimeZone.getDefault();
    145        int offset = timeZone.getRawOffset();
    146        if (offset < 0) {
    147            sign = "-";
    148            offset = -offset;
    149        }
    150        int hours = offset / (60 * 60 * 1000);
    151        int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000);
    152
    153        try {
    154            if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate())) {
    155                hours += sign.equals("+") ? 1 : -1;
    156            }
    157        }
    158        catch (ParseException e) {
    159            // we'll do our best and ignore daylight savings
    160        }
    161
    162        return "GMT" + sign + convert(hours) + ":" + convert(minutes);
    163    }
    164
    165    private String convert(int time) {
    166        if (time < 10) {
    167            return "0" + time;
    168        }
    169
    170        return Integer.toString(time);
    171    }
    172
    173    public Date getDate()
    174        throws ParseException {
    175        SimpleDateFormat dateF;
    176        String d = time;
    177
    178        if (time.endsWith("Z")) {
    179            if (hasFractionalSeconds()) {
    180                dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSS'Z'");
    181            } else {
    182                dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
    183            }
    184
    185            dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
    186        } else if (time.indexOf('-') > 0 || time.indexOf('+') > 0) {
    187            d = this.getTime();
    188            if (hasFractionalSeconds()) {
    189                dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSSz");
    190            } else {
    191                dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
    192            }
    193
    194            dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
    195        } else {
    196            if (hasFractionalSeconds()) {
    197                dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSS");
    198            } else {
    199                dateF = new SimpleDateFormat("yyyyMMddHHmmss");
    200            }
    201
    202            dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID()));
    203        }
    204
    205        return dateF.parse(d);
    206    }
    207
    208    private boolean hasFractionalSeconds() {
    209        return time.indexOf('.') == 14;
    210    }
    211
    212    private byte[] getOctets() {
    213        char[] cs = time.toCharArray();
    214        byte[] bs = new byte[cs.length];
    215
    216        for (int i = 0; i != cs.length; i++) {
    217            bs[i] = (byte) cs[i];
    218        }
    219
    220        return bs;
    221    }
    222
    223
    224    void encode(
    225        DEROutputStream out)
    226        throws IOException {
    227        out.writeEncoded(GENERALIZED_TIME, this.getOctets());
    228    }
    229
    230    boolean asn1Equals(
    231        DERObject o) {
    232        if (!(o instanceof DERGeneralizedTime)) {
    233            return false;
    234        }
    235
    236        return time.equals(((DERGeneralizedTime) o).time);
    237    }
    238
    239    public int hashCode() {
    240        return time.hashCode();
    241    }
    242}