1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.  Oracle designates this
   7  * particular file as subject to the "Classpath" exception as provided
   8  * by Oracle in the LICENSE file that accompanied this code.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 /*
  26  *
  27  *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
  28  *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
  29  */
  30 
  31 package sun.security.krb5.internal;
  32 
  33 import sun.security.krb5.Asn1Exception;
  34 import sun.security.krb5.Config;
  35 import sun.security.krb5.KrbException;
  36 import sun.security.util.DerInputStream;
  37 import sun.security.util.DerOutputStream;
  38 import sun.security.util.DerValue;
  39 
  40 import java.io.IOException;
  41 import java.util.Calendar;
  42 import java.util.Date;
  43 import java.util.TimeZone;
  44 
  45 /**
  46  * Implements the ASN.1 KerberosTime type. This is an immutable class.
  47  *
  48  * <xmp>
  49  * KerberosTime    ::= GeneralizedTime -- with no fractional seconds
  50  * </xmp>
  51  *
  52  * The timestamps used in Kerberos are encoded as GeneralizedTimes. A
  53  * KerberosTime value shall not include any fractional portions of the
  54  * seconds.  As required by the DER, it further shall not include any
  55  * separators, and it shall specify the UTC time zone (Z).
  56  *
  57  * <p>
  58  * This definition reflects the Network Working Group RFC 4120
  59  * specification available at
  60  * <a href="http://www.ietf.org/rfc/rfc4120.txt">
  61  * http://www.ietf.org/rfc/rfc4120.txt</a>.
  62  *
  63  * The implementation also includes the microseconds info so that the
  64  * same class can be used as a precise timestamp in Authenticator etc.
  65  */
  66 
  67 public class KerberosTime {
  68 
  69     private final long kerberosTime; // milliseconds since epoch, Date.getTime()
  70     private final int  microSeconds; // last 3 digits of the real microsecond
  71 
  72     // The time when this class is loaded. Used in setNow()
  73     private static long initMilli = System.currentTimeMillis();
  74     private static long initMicro = System.nanoTime() / 1000;
  75 
  76     private static boolean DEBUG = Krb5.DEBUG;
  77 
  78     // Do not make this public. It's a little confusing that micro
  79     // is only the last 3 digits of microsecond.
  80     private KerberosTime(long time, int micro) {
  81         kerberosTime = time;
  82         microSeconds = micro;
  83     }
  84 
  85     /**
  86      * Creates a KerberosTime object from milliseconds since epoch.
  87      */
  88     public KerberosTime(long time) {
  89         this(time, 0);
  90     }
  91 
  92     // This constructor is used in the native code
  93     // src/windows/native/sun/security/krb5/NativeCreds.c
  94     public KerberosTime(String time) throws Asn1Exception {
  95         this(toKerberosTime(time), 0);
  96     }
  97 
  98     private static long toKerberosTime(String time) throws Asn1Exception {
  99         // ASN.1 GeneralizedTime format:
 100 
 101         // "19700101000000Z"
 102         //  |   | | | | | |
 103         //  0   4 6 8 | | |
 104         //           10 | |
 105         //             12 |
 106         //               14
 107 
 108         if (time.length() != 15)
 109             throw new Asn1Exception(Krb5.ASN1_BAD_TIMEFORMAT);
 110         if (time.charAt(14) != 'Z')
 111             throw new Asn1Exception(Krb5.ASN1_BAD_TIMEFORMAT);
 112         int year = Integer.parseInt(time.substring(0, 4));
 113         Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
 114         calendar.clear(); // so that millisecond is zero
 115         calendar.set(year,
 116                      Integer.parseInt(time.substring(4, 6)) - 1,
 117                      Integer.parseInt(time.substring(6, 8)),
 118                      Integer.parseInt(time.substring(8, 10)),
 119                      Integer.parseInt(time.substring(10, 12)),
 120                      Integer.parseInt(time.substring(12, 14)));
 121         return calendar.getTimeInMillis();
 122     }
 123 
 124     /**
 125      * Creates a KerberosTime object from a Date object.
 126      */
 127     public KerberosTime(Date time) {
 128         this(time.getTime(), 0);
 129     }
 130 
 131     /**
 132      * Creates a KerberosTime object for now. It uses System.nanoTime()
 133      * to get a more precise time than "new Date()".
 134      */
 135     public static KerberosTime now() {
 136         long newMilli = System.currentTimeMillis();
 137         long newMicro = System.nanoTime() / 1000;
 138         long microElapsed = newMicro - initMicro;
 139         long calcMilli = initMilli + microElapsed/1000;
 140         if (calcMilli - newMilli > 100 || newMilli - calcMilli > 100) {
 141             if (DEBUG) {
 142                 System.out.println("System time adjusted");
 143             }
 144             initMilli = newMilli;
 145             initMicro = newMicro;
 146             return new KerberosTime(newMilli, 0);
 147         } else {
 148             return new KerberosTime(calcMilli, (int)(microElapsed % 1000));
 149         }
 150     }
 151 
 152     /**
 153      * Returns a string representation of KerberosTime object.
 154      * @return a string representation of this object.
 155      */
 156     public String toGeneralizedTimeString() {
 157         Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
 158         calendar.clear();
 159 
 160         calendar.setTimeInMillis(kerberosTime);
 161         return String.format("%04d%02d%02d%02d%02d%02dZ",
 162                 calendar.get(Calendar.YEAR),
 163                 calendar.get(Calendar.MONTH) + 1,
 164                 calendar.get(Calendar.DAY_OF_MONTH),
 165                 calendar.get(Calendar.HOUR_OF_DAY),
 166                 calendar.get(Calendar.MINUTE),
 167                 calendar.get(Calendar.SECOND));
 168     }
 169 
 170     /**
 171      * Encodes this object to a byte array.
 172      * @return a byte array of encoded data.
 173      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
 174      * @exception IOException if an I/O error occurs while reading encoded data.
 175      */
 176     public byte[] asn1Encode() throws Asn1Exception, IOException {
 177         DerOutputStream out = new DerOutputStream();
 178         out.putGeneralizedTime(this.toDate());
 179         return out.toByteArray();
 180     }
 181 
 182     public long getTime() {
 183         return kerberosTime;
 184     }
 185 
 186     public Date toDate() {
 187         return new Date(kerberosTime);
 188     }
 189 
 190     public int getMicroSeconds() {
 191         Long temp_long = new Long((kerberosTime % 1000L) * 1000L);
 192         return temp_long.intValue() + microSeconds;
 193     }
 194 
 195     /**
 196      * Returns a new KerberosTime object with the original seconds
 197      * and the given microseconds.
 198      */
 199     public KerberosTime withMicroSeconds(int usec) {
 200         return new KerberosTime(
 201                 kerberosTime - kerberosTime%1000L + usec/1000L,
 202                 usec%1000);
 203     }
 204 
 205     private boolean inClockSkew(int clockSkew) {
 206         return java.lang.Math.abs(kerberosTime - System.currentTimeMillis())
 207                 <= clockSkew * 1000L;
 208     }
 209 
 210     public boolean inClockSkew() {
 211         return inClockSkew(getDefaultSkew());
 212     }
 213 
 214     public boolean greaterThanWRTClockSkew(KerberosTime time, int clockSkew) {
 215         if ((kerberosTime - time.kerberosTime) > clockSkew * 1000L)
 216             return true;
 217         return false;
 218     }
 219 
 220     public boolean greaterThanWRTClockSkew(KerberosTime time) {
 221         return greaterThanWRTClockSkew(time, getDefaultSkew());
 222     }
 223 
 224     public boolean greaterThan(KerberosTime time) {
 225         return kerberosTime > time.kerberosTime ||
 226             kerberosTime == time.kerberosTime &&
 227                     microSeconds > time.microSeconds;
 228     }
 229 
 230     public boolean equals(Object obj) {
 231         if (this == obj) {
 232             return true;
 233         }
 234 
 235         if (!(obj instanceof KerberosTime)) {
 236             return false;
 237         }
 238 
 239         return kerberosTime == ((KerberosTime)obj).kerberosTime &&
 240                 microSeconds == ((KerberosTime)obj).microSeconds;
 241     }
 242 
 243     public int hashCode() {
 244         int result = 37 * 17 + (int)(kerberosTime ^ (kerberosTime >>> 32));
 245         return result * 17 + microSeconds;
 246     }
 247 
 248     public boolean isZero() {
 249         return kerberosTime == 0 && microSeconds == 0;
 250     }
 251 
 252     public int getSeconds() {
 253         Long temp_long = new Long(kerberosTime / 1000L);
 254         return temp_long.intValue();
 255     }
 256 
 257     /**
 258      * Parse (unmarshal) a kerberostime from a DER input stream.  This form
 259      * parsing might be used when expanding a value which is part of
 260      * a constructed sequence and uses explicitly tagged type.
 261      *
 262      * @exception Asn1Exception on error.
 263      * @param data the Der input stream value, which contains
 264      *             one or more marshaled value.
 265      * @param explicitTag tag number.
 266      * @param optional indicates if this data field is optional
 267      * @return an instance of KerberosTime.
 268      *
 269      */
 270     public static KerberosTime parse(
 271             DerInputStream data, byte explicitTag, boolean optional)
 272             throws Asn1Exception, IOException {
 273         if ((optional) && (((byte)data.peekByte() & (byte)0x1F)!= explicitTag))
 274             return null;
 275         DerValue der = data.getDerValue();
 276         if (explicitTag != (der.getTag() & (byte)0x1F))  {
 277             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 278         }
 279         else {
 280             DerValue subDer = der.getData().getDerValue();
 281             Date temp = subDer.getGeneralizedTime();
 282             return new KerberosTime(temp.getTime(), 0);
 283         }
 284     }
 285 
 286     public static int getDefaultSkew() {
 287         int tdiff = Krb5.DEFAULT_ALLOWABLE_CLOCKSKEW;
 288         try {
 289             if ((tdiff = Config.getInstance().getIntValue(
 290                     "libdefaults", "clockskew"))
 291                         == Integer.MIN_VALUE) {   //value is not defined
 292                 tdiff = Krb5.DEFAULT_ALLOWABLE_CLOCKSKEW;
 293             }
 294         } catch (KrbException e) {
 295             if (DEBUG) {
 296                 System.out.println("Exception in getting clockskew from " +
 297                                    "Configuration " +
 298                                    "using default value " +
 299                                    e.getMessage());
 300             }
 301         }
 302         return tdiff;
 303     }
 304 
 305     public String toString() {
 306         return toGeneralizedTimeString();
 307     }
 308 }