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.time.Instant;
42 import java.util.Calendar;
43 import java.util.Date;
44 import java.util.TimeZone;
45 
46 /**
47  * Implements the ASN.1 KerberosTime type. This is an immutable class.
48  *
49  * {@code KerberosTime ::= GeneralizedTime} -- with no fractional seconds
50  *
51  * The timestamps used in Kerberos are encoded as GeneralizedTimes. A
52  * KerberosTime value shall not include any fractional portions of the
53  * seconds.  As required by the DER, it further shall not include any
54  * separators, and it shall specify the UTC time zone (Z).
55  *
56  * <p>
57  * This definition reflects the Network Working Group RFC 4120
58  * specification available at
59  * <a href="http://www.ietf.org/rfc/rfc4120.txt">
60  * http://www.ietf.org/rfc/rfc4120.txt</a>.
61  *
62  * The implementation also includes the microseconds info so that the
63  * same class can be used as a precise timestamp in Authenticator etc.
64  */
65 
66 public class KerberosTime {
67 
68     private final long kerberosTime; // milliseconds since epoch, Date.getTime()
69     private final int  microSeconds; // last 3 digits of the real microsecond
70 
71     // The time when this class is loaded. Used in setNow()
72     private static long initMilli = System.currentTimeMillis();
73     private static long initMicro = System.nanoTime() / 1000;
74 
75     private static boolean DEBUG = Krb5.DEBUG;
76 
77     // Do not make this public. It's a little confusing that micro
78     // is only the last 3 digits of microsecond.
KerberosTime(long time, int micro)79     private KerberosTime(long time, int micro) {
80         kerberosTime = time;
81         microSeconds = micro;
82     }
83 
84     /**
85      * Creates a KerberosTime object from milliseconds since epoch.
86      */
KerberosTime(long time)87     public KerberosTime(long time) {
88         this(time, 0);
89     }
90 
91     // Warning: called by NativeCreds.c and nativeccache.c
KerberosTime(String time)92     public KerberosTime(String time) throws Asn1Exception {
93         this(toKerberosTime(time), 0);
94     }
95 
toKerberosTime(String time)96     private static long toKerberosTime(String time) throws Asn1Exception {
97         // ASN.1 GeneralizedTime format:
98 
99         // "19700101000000Z"
100         //  |   | | | | | |
101         //  0   4 6 8 | | |
102         //           10 | |
103         //             12 |
104         //               14
105 
106         if (time.length() != 15)
107             throw new Asn1Exception(Krb5.ASN1_BAD_TIMEFORMAT);
108         if (time.charAt(14) != 'Z')
109             throw new Asn1Exception(Krb5.ASN1_BAD_TIMEFORMAT);
110         int year = Integer.parseInt(time.substring(0, 4));
111         Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
112         calendar.clear(); // so that millisecond is zero
113         calendar.set(year,
114                      Integer.parseInt(time.substring(4, 6)) - 1,
115                      Integer.parseInt(time.substring(6, 8)),
116                      Integer.parseInt(time.substring(8, 10)),
117                      Integer.parseInt(time.substring(10, 12)),
118                      Integer.parseInt(time.substring(12, 14)));
119         return calendar.getTimeInMillis();
120     }
121 
122     /**
123      * Creates a KerberosTime object from a Date object.
124      */
KerberosTime(Date time)125     public KerberosTime(Date time) {
126         this(time.getTime(), 0);
127     }
128 
129     /**
130      * Creates a KerberosTime object from an Instant object
131      */
KerberosTime(Instant instant)132     public KerberosTime(Instant instant) {
133         this(instant.getEpochSecond()*1000 + instant.getNano()/1000000L,
134                 instant.getNano()/1000%1000);
135     }
136 
137     /**
138      * Creates a KerberosTime object for now. It uses System.nanoTime()
139      * to get a more precise time than "new Date()".
140      */
now()141     public static KerberosTime now() {
142         long newMilli = System.currentTimeMillis();
143         long newMicro = System.nanoTime() / 1000;
144         long microElapsed = newMicro - initMicro;
145         long calcMilli = initMilli + microElapsed/1000;
146         if (calcMilli - newMilli > 100 || newMilli - calcMilli > 100) {
147             if (DEBUG) {
148                 System.out.println("System time adjusted");
149             }
150             initMilli = newMilli;
151             initMicro = newMicro;
152             return new KerberosTime(newMilli, 0);
153         } else {
154             return new KerberosTime(calcMilli, (int)(microElapsed % 1000));
155         }
156     }
157 
158     /**
159      * Returns a string representation of KerberosTime object.
160      * @return a string representation of this object.
161      */
toGeneralizedTimeString()162     public String toGeneralizedTimeString() {
163         Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
164         calendar.clear();
165 
166         calendar.setTimeInMillis(kerberosTime);
167         return String.format("%04d%02d%02d%02d%02d%02dZ",
168                 calendar.get(Calendar.YEAR),
169                 calendar.get(Calendar.MONTH) + 1,
170                 calendar.get(Calendar.DAY_OF_MONTH),
171                 calendar.get(Calendar.HOUR_OF_DAY),
172                 calendar.get(Calendar.MINUTE),
173                 calendar.get(Calendar.SECOND));
174     }
175 
176     /**
177      * Encodes this object to a byte array.
178      * @return a byte array of encoded data.
179      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
180      * @exception IOException if an I/O error occurs while reading encoded data.
181      */
asn1Encode()182     public byte[] asn1Encode() throws Asn1Exception, IOException {
183         DerOutputStream out = new DerOutputStream();
184         out.putGeneralizedTime(this.toDate());
185         return out.toByteArray();
186     }
187 
getTime()188     public long getTime() {
189         return kerberosTime;
190     }
191 
toDate()192     public Date toDate() {
193         return new Date(kerberosTime);
194     }
195 
getMicroSeconds()196     public int getMicroSeconds() {
197         int temp_int = (int) ((kerberosTime % 1000L) * 1000L);
198         return temp_int + microSeconds;
199     }
200 
201     /**
202      * Returns a new KerberosTime object with the original seconds
203      * and the given microseconds.
204      */
withMicroSeconds(int usec)205     public KerberosTime withMicroSeconds(int usec) {
206         return new KerberosTime(
207                 kerberosTime - kerberosTime%1000L + usec/1000L,
208                 usec%1000);
209     }
210 
inClockSkew(int clockSkew)211     private boolean inClockSkew(int clockSkew) {
212         return java.lang.Math.abs(kerberosTime - System.currentTimeMillis())
213                 <= clockSkew * 1000L;
214     }
215 
inClockSkew()216     public boolean inClockSkew() {
217         return inClockSkew(getDefaultSkew());
218     }
219 
greaterThanWRTClockSkew(KerberosTime time, int clockSkew)220     public boolean greaterThanWRTClockSkew(KerberosTime time, int clockSkew) {
221         if ((kerberosTime - time.kerberosTime) > clockSkew * 1000L)
222             return true;
223         return false;
224     }
225 
greaterThanWRTClockSkew(KerberosTime time)226     public boolean greaterThanWRTClockSkew(KerberosTime time) {
227         return greaterThanWRTClockSkew(time, getDefaultSkew());
228     }
229 
greaterThan(KerberosTime time)230     public boolean greaterThan(KerberosTime time) {
231         return kerberosTime > time.kerberosTime ||
232             kerberosTime == time.kerberosTime &&
233                     microSeconds > time.microSeconds;
234     }
235 
equals(Object obj)236     public boolean equals(Object obj) {
237         if (this == obj) {
238             return true;
239         }
240 
241         if (!(obj instanceof KerberosTime)) {
242             return false;
243         }
244 
245         return kerberosTime == ((KerberosTime)obj).kerberosTime &&
246                 microSeconds == ((KerberosTime)obj).microSeconds;
247     }
248 
hashCode()249     public int hashCode() {
250         int result = 37 * 17 + (int)(kerberosTime ^ (kerberosTime >>> 32));
251         return result * 17 + microSeconds;
252     }
253 
isZero()254     public boolean isZero() {
255         return kerberosTime == 0 && microSeconds == 0;
256     }
257 
getSeconds()258     public int getSeconds() {
259         return (int) (kerberosTime / 1000L);
260     }
261 
262     /**
263      * Parse (unmarshal) a kerberostime from a DER input stream.  This form
264      * parsing might be used when expanding a value which is part of
265      * a constructed sequence and uses explicitly tagged type.
266      *
267      * @exception Asn1Exception on error.
268      * @param data the Der input stream value, which contains
269      *             one or more marshaled value.
270      * @param explicitTag tag number.
271      * @param optional indicates if this data field is optional
272      * @return an instance of KerberosTime.
273      *
274      */
parse( DerInputStream data, byte explicitTag, boolean optional)275     public static KerberosTime parse(
276             DerInputStream data, byte explicitTag, boolean optional)
277             throws Asn1Exception, IOException {
278         if ((optional) && (((byte)data.peekByte() & (byte)0x1F)!= explicitTag))
279             return null;
280         DerValue der = data.getDerValue();
281         if (explicitTag != (der.getTag() & (byte)0x1F))  {
282             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
283         }
284         else {
285             DerValue subDer = der.getData().getDerValue();
286             Date temp = subDer.getGeneralizedTime();
287             return new KerberosTime(temp.getTime(), 0);
288         }
289     }
290 
getDefaultSkew()291     public static int getDefaultSkew() {
292         int tdiff = Krb5.DEFAULT_ALLOWABLE_CLOCKSKEW;
293         try {
294             if ((tdiff = Config.getInstance().getIntValue(
295                     "libdefaults", "clockskew"))
296                         == Integer.MIN_VALUE) {   //value is not defined
297                 tdiff = Krb5.DEFAULT_ALLOWABLE_CLOCKSKEW;
298             }
299         } catch (KrbException e) {
300             if (DEBUG) {
301                 System.out.println("Exception in getting clockskew from " +
302                                    "Configuration " +
303                                    "using default value: " +
304                                    e.getMessage());
305             }
306         }
307         return tdiff;
308     }
309 
toString()310     public String toString() {
311         return toGeneralizedTimeString();
312     }
313 }
314