1 /*
2  * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.security.auth.kerberos;
27 
28 import java.io.*;
29 import java.util.Date;
30 import java.util.Arrays;
31 import java.net.InetAddress;
32 import java.util.Objects;
33 import javax.crypto.SecretKey;
34 import javax.security.auth.Refreshable;
35 import javax.security.auth.Destroyable;
36 import javax.security.auth.RefreshFailedException;
37 import javax.security.auth.DestroyFailedException;
38 
39 import sun.misc.HexDumpEncoder;
40 import sun.security.krb5.EncryptionKey;
41 import sun.security.krb5.Asn1Exception;
42 import sun.security.util.*;
43 
44 /**
45  * This class encapsulates a Kerberos ticket and associated
46  * information as viewed from the client's point of view. It captures all
47  * information that the Key Distribution Center (KDC) sends to the client
48  * in the reply message KDC-REP defined in the Kerberos Protocol
49  * Specification (<a href=http://www.ietf.org/rfc/rfc4120.txt>RFC 4120</a>).
50  * <p>
51  * All Kerberos JAAS login modules that authenticate a user to a KDC should
52  * use this class. Where available, the login module might even read this
53  * information from a ticket cache in the operating system instead of
54  * directly communicating with the KDC. During the commit phase of the JAAS
55  * authentication process, the JAAS login module should instantiate this
56  * class and store the instance in the private credential set of a
57  * {@link javax.security.auth.Subject Subject}.<p>
58  *
59  * It might be necessary for the application to be granted a
60  * {@link javax.security.auth.PrivateCredentialPermission
61  * PrivateCredentialPermission} if it needs to access a KerberosTicket
62  * instance from a Subject. This permission is not needed when the
63  * application depends on the default JGSS Kerberos mechanism to access the
64  * KerberosTicket. In that case, however, the application will need an
65  * appropriate
66  * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}.
67  * <p>
68  * Note that this class is applicable to both ticket granting tickets and
69  * other regular service tickets. A ticket granting ticket is just a
70  * special case of a more generalized service ticket.
71  *
72  * @see javax.security.auth.Subject
73  * @see javax.security.auth.PrivateCredentialPermission
74  * @see javax.security.auth.login.LoginContext
75  * @see org.ietf.jgss.GSSCredential
76  * @see org.ietf.jgss.GSSManager
77  *
78  * @author Mayank Upadhyay
79  * @since 1.4
80  */
81 public class KerberosTicket implements Destroyable, Refreshable,
82          java.io.Serializable {
83 
84     private static final long serialVersionUID = 7395334370157380539L;
85 
86     // XXX Make these flag indices public
87     private static final int FORWARDABLE_TICKET_FLAG = 1;
88     private static final int FORWARDED_TICKET_FLAG   = 2;
89     private static final int PROXIABLE_TICKET_FLAG   = 3;
90     private static final int PROXY_TICKET_FLAG       = 4;
91     private static final int POSTDATED_TICKET_FLAG   = 6;
92     private static final int RENEWABLE_TICKET_FLAG   = 8;
93     private static final int INITIAL_TICKET_FLAG     = 9;
94 
95     private static final int NUM_FLAGS = 32;
96 
97     /**
98      *
99      * ASN.1 DER Encoding of the Ticket as defined in the
100      * Kerberos Protocol Specification RFC4120.
101      *
102      * @serial
103      */
104 
105     private byte[] asn1Encoding;
106 
107     /**
108      *{@code KeyImpl} is serialized by writing out the ASN1 Encoded bytes
109      * of the encryption key. The ASN1 encoding is defined in RFC4120 and as
110      * follows:
111      * <pre>
112      * EncryptionKey   ::= SEQUENCE {
113      *          keytype    [0] Int32 -- actually encryption type --,
114      *          keyvalue   [1] OCTET STRING
115      * }
116      * </pre>
117      *
118      * @serial
119      */
120 
121     private KeyImpl sessionKey;
122 
123     /**
124      *
125      * Ticket Flags as defined in the Kerberos Protocol Specification RFC4120.
126      *
127      * @serial
128      */
129 
130     private boolean[] flags;
131 
132     /**
133      *
134      * Time of initial authentication
135      *
136      * @serial
137      */
138 
139     private Date authTime;
140 
141     /**
142      *
143      * Time after which the ticket is valid.
144      * @serial
145      */
146     private Date startTime;
147 
148     /**
149      *
150      * Time after which the ticket will not be honored. (its expiration time).
151      *
152      * @serial
153      */
154 
155     private Date endTime;
156 
157     /**
158      *
159      * For renewable Tickets it indicates the maximum endtime that may be
160      * included in a renewal. It can be thought of as the absolute expiration
161      * time for the ticket, including all renewals. This field may be null
162      * for tickets that are not renewable.
163      *
164      * @serial
165      */
166 
167     private Date renewTill;
168 
169     /**
170      *
171      * Client that owns the service ticket
172      *
173      * @serial
174      */
175 
176     private KerberosPrincipal client;
177 
178     /**
179      *
180      * The service for which the ticket was issued.
181      *
182      * @serial
183      */
184 
185     private KerberosPrincipal server;
186 
187     /**
188      *
189      * The addresses from where the ticket may be used by the client.
190      * This field may be null when the ticket is usable from any address.
191      *
192      * @serial
193      */
194 
195     private InetAddress[] clientAddresses;
196 
197     transient KerberosPrincipal clientAlias = null;
198 
199     transient KerberosPrincipal serverAlias = null;
200 
201     /**
202      * Evidence ticket if proxy_impersonator. This field can be accessed
203      * by KerberosSecrets. It's serialized.
204      */
205     KerberosTicket proxy = null;
206 
207     private transient boolean destroyed = false;
208 
209     /**
210      * Constructs a KerberosTicket using credentials information that a
211      * client either receives from a KDC or reads from a cache.
212      *
213      * @param asn1Encoding the ASN.1 encoding of the ticket as defined by
214      * the Kerberos protocol specification.
215      * @param client the client that owns this service
216      * ticket
217      * @param server the service that this ticket is for
218      * @param sessionKey the raw bytes for the session key that must be
219      * used to encrypt the authenticator that will be sent to the server
220      * @param keyType the key type for the session key as defined by the
221      * Kerberos protocol specification.
222      * @param flags the ticket flags. Each element in this array indicates
223      * the value for the corresponding bit in the ASN.1 BitString that
224      * represents the ticket flags. If the number of elements in this array
225      * is less than the number of flags used by the Kerberos protocol,
226      * then the missing flags will be filled in with false.
227      * @param authTime the time of initial authentication for the client
228      * @param startTime the time after which the ticket will be valid. This
229      * may be null in which case the value of authTime is treated as the
230      * startTime.
231      * @param endTime the time after which the ticket will no longer be
232      * valid
233      * @param renewTill an absolute expiration time for the ticket,
234      * including all renewal that might be possible. This field may be null
235      * for tickets that are not renewable.
236      * @param clientAddresses the addresses from where the ticket may be
237      * used by the client. This field may be null when the ticket is usable
238      * from any address.
239      */
KerberosTicket(byte[] asn1Encoding, KerberosPrincipal client, KerberosPrincipal server, byte[] sessionKey, int keyType, boolean[] flags, Date authTime, Date startTime, Date endTime, Date renewTill, InetAddress[] clientAddresses)240     public KerberosTicket(byte[] asn1Encoding,
241                          KerberosPrincipal client,
242                          KerberosPrincipal server,
243                          byte[] sessionKey,
244                          int keyType,
245                          boolean[] flags,
246                          Date authTime,
247                          Date startTime,
248                          Date endTime,
249                          Date renewTill,
250                          InetAddress[] clientAddresses) {
251 
252         init(asn1Encoding, client, server, sessionKey, keyType, flags,
253             authTime, startTime, endTime, renewTill, clientAddresses);
254     }
255 
init(byte[] asn1Encoding, KerberosPrincipal client, KerberosPrincipal server, byte[] sessionKey, int keyType, boolean[] flags, Date authTime, Date startTime, Date endTime, Date renewTill, InetAddress[] clientAddresses)256     private void init(byte[] asn1Encoding,
257                          KerberosPrincipal client,
258                          KerberosPrincipal server,
259                          byte[] sessionKey,
260                          int keyType,
261                          boolean[] flags,
262                          Date authTime,
263                          Date startTime,
264                          Date endTime,
265                          Date renewTill,
266                          InetAddress[] clientAddresses) {
267         if (sessionKey == null)
268            throw new IllegalArgumentException("Session key for ticket"
269                                               + " cannot be null");
270         init(asn1Encoding, client, server,
271              new KeyImpl(sessionKey, keyType), flags, authTime,
272              startTime, endTime, renewTill, clientAddresses);
273     }
274 
init(byte[] asn1Encoding, KerberosPrincipal client, KerberosPrincipal server, KeyImpl sessionKey, boolean[] flags, Date authTime, Date startTime, Date endTime, Date renewTill, InetAddress[] clientAddresses)275     private void init(byte[] asn1Encoding,
276                          KerberosPrincipal client,
277                          KerberosPrincipal server,
278                          KeyImpl sessionKey,
279                          boolean[] flags,
280                          Date authTime,
281                          Date startTime,
282                          Date endTime,
283                          Date renewTill,
284                          InetAddress[] clientAddresses) {
285         if (asn1Encoding == null)
286            throw new IllegalArgumentException("ASN.1 encoding of ticket"
287                                               + " cannot be null");
288         this.asn1Encoding = asn1Encoding.clone();
289 
290         if (client == null)
291            throw new IllegalArgumentException("Client name in ticket"
292                                               + " cannot be null");
293         this.client = client;
294 
295         if (server == null)
296            throw new IllegalArgumentException("Server name in ticket"
297                                               + " cannot be null");
298         this.server = server;
299 
300         // Caller needs to make sure `sessionKey` will not be null
301         this.sessionKey = sessionKey;
302 
303         if (flags != null) {
304            if (flags.length >= NUM_FLAGS)
305                 this.flags = flags.clone();
306            else {
307                 this.flags = new boolean[NUM_FLAGS];
308                 // Fill in whatever we have
309                 for (int i = 0; i < flags.length; i++)
310                     this.flags[i] = flags[i];
311            }
312         } else
313            this.flags = new boolean[NUM_FLAGS];
314 
315         if (this.flags[RENEWABLE_TICKET_FLAG] && renewTill != null) {
316            this.renewTill = new Date(renewTill.getTime());
317         }
318 
319         if (authTime != null) {
320             this.authTime = new Date(authTime.getTime());
321         }
322         if (startTime != null) {
323             this.startTime = new Date(startTime.getTime());
324         } else {
325             this.startTime = this.authTime;
326         }
327 
328         if (endTime == null)
329            throw new IllegalArgumentException("End time for ticket validity"
330                                               + " cannot be null");
331         this.endTime = new Date(endTime.getTime());
332 
333         if (clientAddresses != null)
334            this.clientAddresses = clientAddresses.clone();
335     }
336 
337     /**
338      * Returns the client principal associated with this ticket.
339      *
340      * @return the client principal.
341      */
getClient()342     public final KerberosPrincipal getClient() {
343         return client;
344     }
345 
346     /**
347      * Returns the service principal associated with this ticket.
348      *
349      * @return the service principal.
350      */
getServer()351     public final KerberosPrincipal getServer() {
352         return server;
353     }
354 
355     /**
356      * Returns the session key associated with this ticket.
357      *
358      * @return the session key.
359      */
getSessionKey()360     public final SecretKey getSessionKey() {
361         if (destroyed)
362             throw new IllegalStateException("This ticket is no longer valid");
363         return sessionKey;
364     }
365 
366     /**
367      * Returns the key type of the session key associated with this
368      * ticket as defined by the Kerberos Protocol Specification.
369      *
370      * @return the key type of the session key associated with this
371      * ticket.
372      *
373      * @see #getSessionKey()
374      */
getSessionKeyType()375     public final int getSessionKeyType() {
376         if (destroyed)
377             throw new IllegalStateException("This ticket is no longer valid");
378         return sessionKey.getKeyType();
379     }
380 
381     /**
382      * Determines if this ticket is forwardable.
383      *
384      * @return true if this ticket is forwardable, false if not.
385      */
isForwardable()386     public final boolean isForwardable() {
387         return flags == null? false: flags[FORWARDABLE_TICKET_FLAG];
388     }
389 
390     /**
391      * Determines if this ticket had been forwarded or was issued based on
392      * authentication involving a forwarded ticket-granting ticket.
393      *
394      * @return true if this ticket had been forwarded or was issued based on
395      * authentication involving a forwarded ticket-granting ticket,
396      * false otherwise.
397      */
isForwarded()398     public final boolean isForwarded() {
399         return flags == null? false: flags[FORWARDED_TICKET_FLAG];
400     }
401 
402     /**
403      * Determines if this ticket is proxiable.
404      *
405      * @return true if this ticket is proxiable, false if not.
406      */
isProxiable()407     public final boolean isProxiable() {
408         return flags == null? false: flags[PROXIABLE_TICKET_FLAG];
409     }
410 
411     /**
412      * Determines is this ticket is a proxy-ticket.
413      *
414      * @return true if this ticket is a proxy-ticket, false if not.
415      */
isProxy()416     public final boolean isProxy() {
417         return flags == null? false: flags[PROXY_TICKET_FLAG];
418     }
419 
420 
421     /**
422      * Determines is this ticket is post-dated.
423      *
424      * @return true if this ticket is post-dated, false if not.
425      */
isPostdated()426     public final boolean isPostdated() {
427         return flags == null? false: flags[POSTDATED_TICKET_FLAG];
428     }
429 
430     /**
431      * Determines is this ticket is renewable. If so, the {@link #refresh()
432      * refresh} method can be called, assuming the validity period for
433      * renewing is not already over.
434      *
435      * @return true if this ticket is renewable, false if not.
436      */
isRenewable()437     public final boolean isRenewable() {
438         return flags == null? false: flags[RENEWABLE_TICKET_FLAG];
439     }
440 
441     /**
442      * Determines if this ticket was issued using the Kerberos AS-Exchange
443      * protocol, and not issued based on some ticket-granting ticket.
444      *
445      * @return true if this ticket was issued using the Kerberos AS-Exchange
446      * protocol, false if not.
447      */
isInitial()448     public final boolean isInitial() {
449         return flags == null? false: flags[INITIAL_TICKET_FLAG];
450     }
451 
452     /**
453      * Returns the flags associated with this ticket. Each element in the
454      * returned array indicates the value for the corresponding bit in the
455      * ASN.1 BitString that represents the ticket flags.
456      *
457      * @return the flags associated with this ticket.
458      */
getFlags()459     public final boolean[]  getFlags() {
460         return (flags == null? null: flags.clone());
461     }
462 
463     /**
464      * Returns the time that the client was authenticated.
465      *
466      * @return the time that the client was authenticated
467      *         or null if not set.
468      */
getAuthTime()469     public final java.util.Date getAuthTime() {
470         return (authTime == null) ? null : (Date)authTime.clone();
471     }
472 
473     /**
474      * Returns the start time for this ticket's validity period.
475      *
476      * @return the start time for this ticket's validity period
477      *         or null if not set.
478      */
getStartTime()479     public final java.util.Date getStartTime() {
480         return (startTime == null) ? null : (Date)startTime.clone();
481     }
482 
483     /**
484      * Returns the expiration time for this ticket's validity period.
485      *
486      * @return the expiration time for this ticket's validity period.
487      */
getEndTime()488     public final java.util.Date getEndTime() {
489         return (endTime == null) ? null : (Date) endTime.clone();
490     }
491 
492     /**
493      * Returns the latest expiration time for this ticket, including all
494      * renewals. This will return a null value for non-renewable tickets.
495      *
496      * @return the latest expiration time for this ticket.
497      */
getRenewTill()498     public final java.util.Date getRenewTill() {
499         return (renewTill == null) ? null: (Date)renewTill.clone();
500     }
501 
502     /**
503      * Returns a list of addresses from where the ticket can be used.
504      *
505      * @return ths list of addresses or null, if the field was not
506      * provided.
507      */
getClientAddresses()508     public final java.net.InetAddress[] getClientAddresses() {
509         return (clientAddresses == null) ? null: clientAddresses.clone();
510     }
511 
512     /**
513      * Returns an ASN.1 encoding of the entire ticket.
514      *
515      * @return an ASN.1 encoding of the entire ticket.
516      */
getEncoded()517     public final byte[] getEncoded() {
518         if (destroyed)
519             throw new IllegalStateException("This ticket is no longer valid");
520         return asn1Encoding.clone();
521     }
522 
523     /** Determines if this ticket is still current.  */
isCurrent()524     public boolean isCurrent() {
525         return endTime == null? false: (System.currentTimeMillis() <= endTime.getTime());
526     }
527 
528     /**
529      * Extends the validity period of this ticket. The ticket will contain
530      * a new session key if the refresh operation succeeds. The refresh
531      * operation will fail if the ticket is not renewable or the latest
532      * allowable renew time has passed. Any other error returned by the
533      * KDC will also cause this method to fail.
534      *
535      * Note: This method is not synchronized with the the accessor
536      * methods of this object. Hence callers need to be aware of multiple
537      * threads that might access this and try to renew it at the same
538      * time.
539      *
540      * @throws RefreshFailedException if the ticket is not renewable, or
541      * the latest allowable renew time has passed, or the KDC returns some
542      * error.
543      *
544      * @see #isRenewable()
545      * @see #getRenewTill()
546      */
refresh()547     public void refresh() throws RefreshFailedException {
548 
549         if (destroyed)
550             throw new RefreshFailedException("A destroyed ticket "
551                                              + "cannot be renewd.");
552 
553         if (!isRenewable())
554             throw new RefreshFailedException("This ticket is not renewable");
555 
556         if (getRenewTill() == null) {
557             // Renewable ticket without renew-till. Illegal and ignored.
558             return;
559         }
560 
561         if (System.currentTimeMillis() > getRenewTill().getTime())
562             throw new RefreshFailedException("This ticket is past "
563                                              + "its last renewal time.");
564         Throwable e = null;
565         sun.security.krb5.Credentials krb5Creds = null;
566 
567         try {
568             krb5Creds = new sun.security.krb5.Credentials(asn1Encoding,
569                                                     client.toString(),
570                                                     (clientAlias != null ?
571                                                             clientAlias.getName() : null),
572                                                     server.toString(),
573                                                     (serverAlias != null ?
574                                                             serverAlias.getName() : null),
575                                                     sessionKey.getEncoded(),
576                                                     sessionKey.getKeyType(),
577                                                     flags,
578                                                     authTime,
579                                                     startTime,
580                                                     endTime,
581                                                     renewTill,
582                                                     clientAddresses);
583             krb5Creds = krb5Creds.renew();
584         } catch (sun.security.krb5.KrbException krbException) {
585             e = krbException;
586         } catch (java.io.IOException ioException) {
587             e = ioException;
588         }
589 
590         if (e != null) {
591             RefreshFailedException rfException
592                 = new RefreshFailedException("Failed to renew Kerberos Ticket "
593                                              + "for client " + client
594                                              + " and server " + server
595                                              + " - " + e.getMessage());
596             rfException.initCause(e);
597             throw rfException;
598         }
599 
600         /*
601          * In case multiple threads try to refresh it at the same time.
602          */
603         synchronized (this) {
604             try {
605                 this.destroy();
606             } catch (DestroyFailedException dfException) {
607                 // Squelch it since we don't care about the old ticket.
608             }
609             init(krb5Creds.getEncoded(),
610                  new KerberosPrincipal(krb5Creds.getClient().getName()),
611                  new KerberosPrincipal(krb5Creds.getServer().getName(),
612                                         KerberosPrincipal.KRB_NT_SRV_INST),
613                  krb5Creds.getSessionKey().getBytes(),
614                  krb5Creds.getSessionKey().getEType(),
615                  krb5Creds.getFlags(),
616                  krb5Creds.getAuthTime(),
617                  krb5Creds.getStartTime(),
618                  krb5Creds.getEndTime(),
619                  krb5Creds.getRenewTill(),
620                  krb5Creds.getClientAddresses());
621             destroyed = false;
622         }
623     }
624 
625     /**
626      * Destroys the ticket and destroys any sensitive information stored in
627      * it.
628      */
destroy()629     public void destroy() throws DestroyFailedException {
630         if (!destroyed) {
631             Arrays.fill(asn1Encoding, (byte) 0);
632             client = null;
633             server = null;
634             sessionKey.destroy();
635             flags = null;
636             authTime = null;
637             startTime = null;
638             endTime = null;
639             renewTill = null;
640             clientAddresses = null;
641             destroyed = true;
642         }
643     }
644 
645     /**
646      * Determines if this ticket has been destroyed.
647      */
isDestroyed()648     public boolean isDestroyed() {
649         return destroyed;
650     }
651 
toString()652     public String toString() {
653         if (destroyed) {
654             return "Destroyed KerberosTicket";
655         }
656         StringBuffer caddrBuf = new StringBuffer();
657         if (clientAddresses != null) {
658             for (int i = 0; i < clientAddresses.length; i++) {
659                 caddrBuf.append("clientAddresses[" + i + "] = " +
660                                  clientAddresses[i].toString());
661             }
662         }
663         return ("Ticket (hex) = " + "\n" +
664                  (new HexDumpEncoder()).encodeBuffer(asn1Encoding) + "\n" +
665                 "Client Principal = " + client.toString() + "\n" +
666                 "Server Principal = " + server.toString() + "\n" +
667                 "Session Key = " + sessionKey.toString() + "\n" +
668                 "Forwardable Ticket " + flags[FORWARDABLE_TICKET_FLAG] + "\n" +
669                 "Forwarded Ticket " + flags[FORWARDED_TICKET_FLAG] + "\n" +
670                 "Proxiable Ticket " + flags[PROXIABLE_TICKET_FLAG] + "\n" +
671                 "Proxy Ticket " + flags[PROXY_TICKET_FLAG] + "\n" +
672                 "Postdated Ticket " + flags[POSTDATED_TICKET_FLAG] + "\n" +
673                 "Renewable Ticket " + flags[RENEWABLE_TICKET_FLAG] + "\n" +
674                 "Initial Ticket " + flags[RENEWABLE_TICKET_FLAG] + "\n" +
675                 "Auth Time = " + String.valueOf(authTime) + "\n" +
676                 "Start Time = " + String.valueOf(startTime) + "\n" +
677                 "End Time = " + endTime.toString() + "\n" +
678                 "Renew Till = " + String.valueOf(renewTill) + "\n" +
679                 "Client Addresses " +
680                 (clientAddresses == null ? " Null " : caddrBuf.toString() +
681                 (proxy == null ? "" : "\nwith a proxy ticket") +
682                 "\n"));
683     }
684 
685     /**
686      * Returns a hashcode for this KerberosTicket.
687      *
688      * @return a hashCode() for the {@code KerberosTicket}
689      * @since 1.6
690      */
hashCode()691     public int hashCode() {
692         int result = 17;
693         if (isDestroyed()) {
694             return result;
695         }
696         result = result * 37 + Arrays.hashCode(getEncoded());
697         result = result * 37 + endTime.hashCode();
698         result = result * 37 + client.hashCode();
699         result = result * 37 + server.hashCode();
700         result = result * 37 + sessionKey.hashCode();
701 
702         // authTime may be null
703         if (authTime != null) {
704             result = result * 37 + authTime.hashCode();
705         }
706 
707         // startTime may be null
708         if (startTime != null) {
709             result = result * 37 + startTime.hashCode();
710         }
711 
712         // renewTill may be null
713         if (renewTill != null) {
714             result = result * 37 + renewTill.hashCode();
715         }
716 
717         // clientAddress may be null, the array's hashCode is 0
718         result = result * 37 + Arrays.hashCode(clientAddresses);
719 
720         if (proxy != null) {
721             result = result * 37 + proxy.hashCode();
722         }
723         return result * 37 + Arrays.hashCode(flags);
724     }
725 
726     /**
727      * Compares the specified Object with this KerberosTicket for equality.
728      * Returns true if the given object is also a
729      * {@code KerberosTicket} and the two
730      * {@code KerberosTicket} instances are equivalent.
731      *
732      * @param other the Object to compare to
733      * @return true if the specified object is equal to this KerberosTicket,
734      * false otherwise. NOTE: Returns false if either of the KerberosTicket
735      * objects has been destroyed.
736      * @since 1.6
737      */
equals(Object other)738     public boolean equals(Object other) {
739 
740         if (other == this)
741             return true;
742 
743         if (! (other instanceof KerberosTicket)) {
744             return false;
745         }
746 
747         KerberosTicket otherTicket = ((KerberosTicket) other);
748         if (isDestroyed() || otherTicket.isDestroyed()) {
749             return false;
750         }
751 
752         if (!Arrays.equals(getEncoded(), otherTicket.getEncoded()) ||
753                 !endTime.equals(otherTicket.getEndTime()) ||
754                 !server.equals(otherTicket.getServer()) ||
755                 !client.equals(otherTicket.getClient()) ||
756                 !sessionKey.equals(otherTicket.getSessionKey()) ||
757                 !Arrays.equals(clientAddresses, otherTicket.getClientAddresses()) ||
758                 !Arrays.equals(flags, otherTicket.getFlags())) {
759             return false;
760         }
761 
762         // authTime may be null
763         if (authTime == null) {
764             if (otherTicket.getAuthTime() != null)
765                 return false;
766         } else {
767             if (!authTime.equals(otherTicket.getAuthTime()))
768                 return false;
769         }
770 
771         // startTime may be null
772         if (startTime == null) {
773             if (otherTicket.getStartTime() != null)
774                 return false;
775         } else {
776             if (!startTime.equals(otherTicket.getStartTime()))
777                 return false;
778         }
779 
780         if (renewTill == null) {
781             if (otherTicket.getRenewTill() != null)
782                 return false;
783         } else {
784             if (!renewTill.equals(otherTicket.getRenewTill()))
785                 return false;
786         }
787 
788         if (!Objects.equals(proxy, otherTicket.proxy)) {
789             return false;
790         }
791 
792         return true;
793     }
794 
readObject(ObjectInputStream s)795     private void readObject(ObjectInputStream s)
796         throws IOException, ClassNotFoundException {
797         s.defaultReadObject();
798         if (sessionKey == null) {
799            throw new InvalidObjectException("Session key cannot be null");
800         }
801         try {
802             init(asn1Encoding, client, server, sessionKey,
803                  flags, authTime, startTime, endTime,
804                  renewTill, clientAddresses);
805         } catch (IllegalArgumentException iae) {
806             throw (InvalidObjectException)
807                 new InvalidObjectException(iae.getMessage()).initCause(iae);
808         }
809     }
810 }
811