001 /*
002 * Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.security.auth.kerberos;
027
028 import java.io.*;
029 import java.util.Date;
030 import java.util.Arrays;
031 import java.net.InetAddress;
032 import javax.crypto.SecretKey;
033 import javax.security.auth.Refreshable;
034 import javax.security.auth.Destroyable;
035 import javax.security.auth.RefreshFailedException;
036 import javax.security.auth.DestroyFailedException;
037 import sun.misc.HexDumpEncoder;
038 import sun.security.krb5.EncryptionKey;
039 import sun.security.krb5.Asn1Exception;
040 import sun.security.util.*;
041
042 /**
043 * This class encapsulates a Kerberos ticket and associated
044 * information as viewed from the client's point of view. It captures all
045 * information that the Key Distribution Center (KDC) sends to the client
046 * in the reply message KDC-REP defined in the Kerberos Protocol
047 * Specification (<a href=http://www.ietf.org/rfc/rfc4120.txt>RFC 4120</a>).
048 * <p>
049 * All Kerberos JAAS login modules that authenticate a user to a KDC should
050 * use this class. Where available, the login module might even read this
051 * information from a ticket cache in the operating system instead of
052 * directly communicating with the KDC. During the commit phase of the JAAS
053 * authentication process, the JAAS login module should instantiate this
054 * class and store the instance in the private credential set of a
055 * {@link javax.security.auth.Subject Subject}.<p>
056 *
057 * It might be necessary for the application to be granted a
058 * {@link javax.security.auth.PrivateCredentialPermission
059 * PrivateCredentialPermission} if it needs to access a KerberosTicket
060 * instance from a Subject. This permission is not needed when the
061 * application depends on the default JGSS Kerberos mechanism to access the
062 * KerberosTicket. In that case, however, the application will need an
063 * appropriate
064 * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}.
065 * <p>
066 * Note that this class is applicable to both ticket granting tickets and
067 * other regular service tickets. A ticket granting ticket is just a
068 * special case of a more generalized service ticket.
069 *
070 * @see javax.security.auth.Subject
071 * @see javax.security.auth.PrivateCredentialPermission
072 * @see javax.security.auth.login.LoginContext
073 * @see org.ietf.jgss.GSSCredential
074 * @see org.ietf.jgss.GSSManager
075 *
076 * @author Mayank Upadhyay
077 * @version 1.29, 05/05/07
078 * @since 1.4
079 */
080 public class KerberosTicket implements Destroyable, Refreshable,
081 java.io.Serializable {
082
083 private static final long serialVersionUID = 7395334370157380539L;
084
085 // XXX Make these flag indices public
086 private static final int FORWARDABLE_TICKET_FLAG = 1;
087 private static final int FORWARDED_TICKET_FLAG = 2;
088 private static final int PROXIABLE_TICKET_FLAG = 3;
089 private static final int PROXY_TICKET_FLAG = 4;
090 private static final int POSTDATED_TICKET_FLAG = 6;
091 private static final int RENEWABLE_TICKET_FLAG = 8;
092 private static final int INITIAL_TICKET_FLAG = 9;
093
094 private static final int NUM_FLAGS = 32;
095
096 /**
097 *
098 * ASN.1 DER Encoding of the Ticket as defined in the
099 * Kerberos Protocol Specification RFC4120.
100 *
101 * @serial
102 */
103
104 private byte[] asn1Encoding;
105
106 /**
107 *<code>KeyImpl</code> is serialized by writing out the ASN1 Encoded bytes
108 * of the encryption key. The ASN1 encoding is defined in RFC4120 and as
109 * follows:
110 * <pre>
111 * EncryptionKey ::= SEQUENCE {
112 * keytype [0] Int32 -- actually encryption type --,
113 * keyvalue [1] OCTET STRING
114 * }
115 * </pre>
116 *
117 * @serial
118 */
119
120 private KeyImpl sessionKey;
121
122 /**
123 *
124 * Ticket Flags as defined in the Kerberos Protocol Specification RFC4120.
125 *
126 * @serial
127 */
128
129 private boolean[] flags;
130
131 /**
132 *
133 * Time of initial authentication
134 *
135 * @serial
136 */
137
138 private Date authTime;
139
140 /**
141 *
142 * Time after which the ticket is valid.
143 * @serial
144 */
145 private Date startTime;
146
147 /**
148 *
149 * Time after which the ticket will not be honored. (its expiration time).
150 *
151 * @serial
152 */
153
154 private Date endTime;
155
156 /**
157 *
158 * For renewable Tickets it indicates the maximum endtime that may be
159 * included in a renewal. It can be thought of as the absolute expiration
160 * time for the ticket, including all renewals. This field may be null
161 * for tickets that are not renewable.
162 *
163 * @serial
164 */
165
166 private Date renewTill;
167
168 /**
169 *
170 * Client that owns the service ticket
171 *
172 * @serial
173 */
174
175 private KerberosPrincipal client;
176
177 /**
178 *
179 * The service for which the ticket was issued.
180 *
181 * @serial
182 */
183
184 private KerberosPrincipal server;
185
186 /**
187 *
188 * The addresses from where the ticket may be used by the client.
189 * This field may be null when the ticket is usable from any address.
190 *
191 * @serial
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, KerberosPrincipal server,
231 byte[] sessionKey, int keyType, boolean[] flags,
232 Date authTime, Date startTime, Date endTime,
233 Date renewTill, InetAddress[] clientAddresses) {
234
235 init(asn1Encoding, client, server, sessionKey, keyType, flags,
236 authTime, startTime, endTime, renewTill,
237 clientAddresses);
238 }
239
240 private void init(byte[] asn1Encoding, KerberosPrincipal client,
241 KerberosPrincipal server, byte[] sessionKey, int keyType,
242 boolean[] flags, Date authTime, Date startTime,
243 Date endTime, Date renewTill, InetAddress[] clientAddresses) {
244
245 if (asn1Encoding == null)
246 throw new IllegalArgumentException(
247 "ASN.1 encoding of ticket" + " cannot be null");
248 this .asn1Encoding = asn1Encoding.clone();
249
250 if (client == null)
251 throw new IllegalArgumentException("Client name in ticket"
252 + " cannot be null");
253 this .client = client;
254
255 if (server == null)
256 throw new IllegalArgumentException("Server name in ticket"
257 + " cannot be null");
258 this .server = server;
259
260 if (sessionKey == null)
261 throw new IllegalArgumentException("Session key for ticket"
262 + " cannot be null");
263 this .sessionKey = new KeyImpl(sessionKey, keyType);
264
265 if (flags != null) {
266 if (flags.length >= NUM_FLAGS)
267 this .flags = (boolean[]) flags.clone();
268 else {
269 this .flags = new boolean[NUM_FLAGS];
270 // Fill in whatever we have
271 for (int i = 0; i < flags.length; i++)
272 this .flags[i] = flags[i];
273 }
274 } else
275 this .flags = new boolean[NUM_FLAGS];
276
277 if (this .flags[RENEWABLE_TICKET_FLAG]) {
278 if (renewTill == null)
279 throw new IllegalArgumentException(
280 "The renewable period "
281 + "end time cannot be null for renewable tickets.");
282
283 this .renewTill = renewTill;
284 }
285
286 this .authTime = authTime;
287
288 this .startTime = (startTime != null ? startTime : authTime);
289
290 if (endTime == null)
291 throw new IllegalArgumentException(
292 "End time for ticket validity" + " cannot be null");
293 this .endTime = endTime;
294
295 if (clientAddresses != null)
296 this .clientAddresses = (InetAddress[]) clientAddresses
297 .clone();
298 }
299
300 /**
301 * Returns the client principal associated with this ticket.
302 *
303 * @return the client principal.
304 */
305 public final KerberosPrincipal getClient() {
306 return client;
307 }
308
309 /**
310 * Returns the service principal associated with this ticket.
311 *
312 * @return the service principal.
313 */
314 public final KerberosPrincipal getServer() {
315 return server;
316 }
317
318 /**
319 * Returns the session key associated with this ticket.
320 *
321 * @return the session key.
322 */
323 public final SecretKey getSessionKey() {
324 if (destroyed)
325 throw new IllegalStateException(
326 "This ticket is no longer valid");
327 return sessionKey;
328 }
329
330 /**
331 * Returns the key type of the session key associated with this
332 * ticket as defined by the Kerberos Protocol Specification.
333 *
334 * @return the key type of the session key associated with this
335 * ticket.
336 *
337 * @see #getSessionKey()
338 */
339 public final int getSessionKeyType() {
340 if (destroyed)
341 throw new IllegalStateException(
342 "This ticket is no longer valid");
343 return sessionKey.getKeyType();
344 }
345
346 /**
347 * Determines if this ticket is forwardable.
348 *
349 * @return true if this ticket is forwardable, false if not.
350 */
351 public final boolean isForwardable() {
352 return flags[FORWARDABLE_TICKET_FLAG];
353 }
354
355 /**
356 * Determines if this ticket had been forwarded or was issued based on
357 * authentication involving a forwarded ticket-granting ticket.
358 *
359 * @return true if this ticket had been forwarded or was issued based on
360 * authentication involving a forwarded ticket-granting ticket,
361 * false otherwise.
362 */
363 public final boolean isForwarded() {
364 return flags[FORWARDED_TICKET_FLAG];
365 }
366
367 /**
368 * Determines if this ticket is proxiable.
369 *
370 * @return true if this ticket is proxiable, false if not.
371 */
372 public final boolean isProxiable() {
373 return flags[PROXIABLE_TICKET_FLAG];
374 }
375
376 /**
377 * Determines is this ticket is a proxy-ticket.
378 *
379 * @return true if this ticket is a proxy-ticket, false if not.
380 */
381 public final boolean isProxy() {
382 return flags[PROXY_TICKET_FLAG];
383 }
384
385 /**
386 * Determines is this ticket is post-dated.
387 *
388 * @return true if this ticket is post-dated, false if not.
389 */
390 public final boolean isPostdated() {
391 return flags[POSTDATED_TICKET_FLAG];
392 }
393
394 /**
395 * Determines is this ticket is renewable. If so, the {@link #refresh()
396 * refresh} method can be called, assuming the validity period for
397 * renewing is not already over.
398 *
399 * @return true if this ticket is renewable, false if not.
400 */
401 public final boolean isRenewable() {
402 return flags[RENEWABLE_TICKET_FLAG];
403 }
404
405 /**
406 * Determines if this ticket was issued using the Kerberos AS-Exchange
407 * protocol, and not issued based on some ticket-granting ticket.
408 *
409 * @return true if this ticket was issued using the Kerberos AS-Exchange
410 * protocol, false if not.
411 */
412 public final boolean isInitial() {
413 return flags[INITIAL_TICKET_FLAG];
414 }
415
416 /**
417 * Returns the flags associated with this ticket. Each element in the
418 * returned array indicates the value for the corresponding bit in the
419 * ASN.1 BitString that represents the ticket flags.
420 *
421 * @return the flags associated with this ticket.
422 */
423 public final boolean[] getFlags() {
424 return (flags == null ? null : (boolean[]) flags.clone());
425 }
426
427 /**
428 * Returns the time that the client was authenticated.
429 *
430 * @return the time that the client was authenticated
431 * or null if not set.
432 */
433 public final java.util.Date getAuthTime() {
434 return (authTime == null) ? null : new Date(authTime.getTime());
435 }
436
437 /**
438 * Returns the start time for this ticket's validity period.
439 *
440 * @return the start time for this ticket's validity period
441 * or null if not set.
442 */
443 public final java.util.Date getStartTime() {
444 return (startTime == null) ? null : new Date(startTime
445 .getTime());
446 }
447
448 /**
449 * Returns the expiration time for this ticket's validity period.
450 *
451 * @return the expiration time for this ticket's validity period.
452 */
453 public final java.util.Date getEndTime() {
454 return endTime;
455 }
456
457 /**
458 * Returns the latest expiration time for this ticket, including all
459 * renewals. This will return a null value for non-renewable tickets.
460 *
461 * @return the latest expiration time for this ticket.
462 */
463 public final java.util.Date getRenewTill() {
464 return (renewTill == null) ? null : new Date(renewTill
465 .getTime());
466 }
467
468 /**
469 * Returns a list of addresses from where the ticket can be used.
470 *
471 * @return ths list of addresses or null, if the field was not
472 * provided.
473 */
474 public final java.net.InetAddress[] getClientAddresses() {
475 return (clientAddresses == null ? null
476 : (InetAddress[]) clientAddresses.clone());
477 }
478
479 /**
480 * Returns an ASN.1 encoding of the entire ticket.
481 *
482 * @return an ASN.1 encoding of the entire ticket.
483 */
484 public final byte[] getEncoded() {
485 if (destroyed)
486 throw new IllegalStateException(
487 "This ticket is no longer valid");
488 return (byte[]) asn1Encoding.clone();
489 }
490
491 /** Determines if this ticket is still current. */
492 public boolean isCurrent() {
493 return (System.currentTimeMillis() <= getEndTime().getTime());
494 }
495
496 /**
497 * Extends the validity period of this ticket. The ticket will contain
498 * a new session key if the refresh operation succeeds. The refresh
499 * operation will fail if the ticket is not renewable or the latest
500 * allowable renew time has passed. Any other error returned by the
501 * KDC will also cause this method to fail.
502 *
503 * Note: This method is not synchronized with the the accessor
504 * methods of this object. Hence callers need to be aware of multiple
505 * threads that might access this and try to renew it at the same
506 * time.
507 *
508 * @throws RefreshFailedException if the ticket is not renewable, or
509 * the latest allowable renew time has passed, or the KDC returns some
510 * error.
511 *
512 * @see #isRenewable()
513 * @see #getRenewTill()
514 */
515 public void refresh() throws RefreshFailedException {
516
517 if (destroyed)
518 throw new RefreshFailedException("A destroyed ticket "
519 + "cannot be renewd.");
520
521 if (!isRenewable())
522 throw new RefreshFailedException(
523 "This ticket is not renewable");
524
525 if (System.currentTimeMillis() > getRenewTill().getTime())
526 throw new RefreshFailedException("This ticket is past "
527 + "its last renewal time.");
528 Throwable e = null;
529 sun.security.krb5.Credentials krb5Creds = null;
530
531 try {
532 krb5Creds = new sun.security.krb5.Credentials(asn1Encoding,
533 client.toString(), server.toString(), sessionKey
534 .getEncoded(), sessionKey.getKeyType(),
535 flags, authTime, startTime, endTime, renewTill,
536 clientAddresses);
537 krb5Creds = krb5Creds.renew();
538 } catch (sun.security.krb5.KrbException krbException) {
539 e = krbException;
540 } catch (java.io.IOException ioException) {
541 e = ioException;
542 }
543
544 if (e != null) {
545 RefreshFailedException rfException = new RefreshFailedException(
546 "Failed to renew Kerberos Ticket " + "for client "
547 + client + " and server " + server + " - "
548 + e.getMessage());
549 rfException.initCause(e);
550 throw rfException;
551 }
552
553 /*
554 * In case multiple threads try to refresh it at the same time.
555 */
556 synchronized (this ) {
557 try {
558 this .destroy();
559 } catch (DestroyFailedException dfException) {
560 // Squelch it since we don't care about the old ticket.
561 }
562 init(krb5Creds.getEncoded(), new KerberosPrincipal(
563 krb5Creds.getClient().getName()),
564 new KerberosPrincipal(krb5Creds.getServer()
565 .getName(),
566 KerberosPrincipal.KRB_NT_SRV_INST),
567 krb5Creds.getSessionKey().getBytes(), krb5Creds
568 .getSessionKey().getEType(), krb5Creds
569 .getFlags(), krb5Creds.getAuthTime(),
570 krb5Creds.getStartTime(), krb5Creds.getEndTime(),
571 krb5Creds.getRenewTill(), krb5Creds
572 .getClientAddresses());
573 destroyed = false;
574 }
575 }
576
577 /**
578 * Destroys the ticket and destroys any sensitive information stored in
579 * it.
580 */
581 public void destroy() throws DestroyFailedException {
582 if (!destroyed) {
583 Arrays.fill(asn1Encoding, (byte) 0);
584 client = null;
585 server = null;
586 sessionKey.destroy();
587 flags = null;
588 authTime = null;
589 startTime = null;
590 endTime = null;
591 renewTill = null;
592 clientAddresses = null;
593 destroyed = true;
594 }
595 }
596
597 /**
598 * Determines if this ticket has been destroyed.
599 */
600 public boolean isDestroyed() {
601 return destroyed;
602 }
603
604 public String toString() {
605 if (destroyed)
606 throw new IllegalStateException(
607 "This ticket is no longer valid");
608 StringBuffer caddrBuf = new StringBuffer();
609 if (clientAddresses != null) {
610 for (int i = 0; i < clientAddresses.length; i++) {
611 caddrBuf.append("clientAddresses[" + i + "] = "
612 + clientAddresses[i].toString());
613 }
614 }
615 return ("Ticket (hex) = " + "\n"
616 + (new HexDumpEncoder()).encodeBuffer(asn1Encoding)
617 + "\n" + "Client Principal = " + client.toString()
618 + "\n" + "Server Principal = " + server.toString()
619 + "\n" + "Session Key = " + sessionKey.toString()
620 + "\n" + "Forwardable Ticket "
621 + flags[FORWARDABLE_TICKET_FLAG] + "\n"
622 + "Forwarded Ticket " + flags[FORWARDED_TICKET_FLAG]
623 + "\n" + "Proxiable Ticket "
624 + flags[PROXIABLE_TICKET_FLAG] + "\n" + "Proxy Ticket "
625 + flags[PROXY_TICKET_FLAG] + "\n" + "Postdated Ticket "
626 + flags[POSTDATED_TICKET_FLAG] + "\n"
627 + "Renewable Ticket " + flags[RENEWABLE_TICKET_FLAG]
628 + "\n" + "Initial Ticket "
629 + flags[RENEWABLE_TICKET_FLAG] + "\n" + "Auth Time = "
630 + String.valueOf(authTime) + "\n" + "Start Time = "
631 + String.valueOf(startTime) + "\n" + "End Time = "
632 + endTime.toString() + "\n" + "Renew Till = "
633 + String.valueOf(renewTill) + "\n"
634 + "Client Addresses " + (clientAddresses == null ? " Null "
635 : caddrBuf.toString() + "\n"));
636 }
637
638 /**
639 * Returns a hashcode for this KerberosTicket.
640 *
641 * @return a hashCode() for the <code>KerberosTicket</code>
642 * @since 1.6
643 */
644 public int hashCode() {
645 int result = 17;
646 if (isDestroyed()) {
647 return result;
648 }
649 result = result * 37 + Arrays.hashCode(getEncoded());
650 result = result * 37 + endTime.hashCode();
651 result = result * 37 + client.hashCode();
652 result = result * 37 + server.hashCode();
653 result = result * 37 + sessionKey.hashCode();
654
655 // authTime may be null
656 if (authTime != null) {
657 result = result * 37 + authTime.hashCode();
658 }
659
660 // startTime may be null
661 if (startTime != null) {
662 result = result * 37 + startTime.hashCode();
663 }
664
665 // renewTill may be null
666 if (renewTill != null) {
667 result = result * 37 + renewTill.hashCode();
668 }
669
670 // clientAddress may be null, the array's hashCode is 0
671 result = result * 37 + Arrays.hashCode(clientAddresses);
672 return result * 37 + Arrays.hashCode(flags);
673 }
674
675 /**
676 * Compares the specified Object with this KerberosTicket for equality.
677 * Returns true if the given object is also a
678 * <code>KerberosTicket</code> and the two
679 * <code>KerberosTicket</code> instances are equivalent.
680 *
681 * @param other the Object to compare to
682 * @return true if the specified object is equal to this KerberosTicket,
683 * false otherwise. NOTE: Returns false if either of the KerberosTicket
684 * objects has been destroyed.
685 * @since 1.6
686 */
687 public boolean equals(Object other) {
688
689 if (other == this )
690 return true;
691
692 if (!(other instanceof KerberosTicket)) {
693 return false;
694 }
695
696 KerberosTicket otherTicket = ((KerberosTicket) other);
697 if (isDestroyed() || otherTicket.isDestroyed()) {
698 return false;
699 }
700
701 if (!Arrays.equals(getEncoded(), otherTicket.getEncoded())
702 || !endTime.equals(otherTicket.getEndTime())
703 || !server.equals(otherTicket.getServer())
704 || !client.equals(otherTicket.getClient())
705 || !sessionKey.equals(otherTicket.getSessionKey())
706 || !Arrays.equals(clientAddresses, otherTicket
707 .getClientAddresses())
708 || !Arrays.equals(flags, otherTicket.getFlags())) {
709 return false;
710 }
711
712 // authTime may be null
713 if (authTime == null) {
714 if (otherTicket.getAuthTime() != null)
715 return false;
716 } else {
717 if (!authTime.equals(otherTicket.getAuthTime()))
718 return false;
719 }
720
721 // startTime may be null
722 if (startTime == null) {
723 if (otherTicket.getStartTime() != null)
724 return false;
725 } else {
726 if (!startTime.equals(otherTicket.getStartTime()))
727 return false;
728 }
729
730 if (renewTill == null) {
731 if (otherTicket.getRenewTill() != null)
732 return false;
733 } else {
734 if (!renewTill.equals(otherTicket.getRenewTill()))
735 return false;
736 }
737
738 return true;
739 }
740 }
|