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