1. /*
  2. * @(#)KerberosTicket.java 1.13 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.security.auth.kerberos;
  8. import java.io.*;
  9. import java.util.Date;
  10. import java.util.Arrays;
  11. import java.net.InetAddress;
  12. import javax.crypto.SecretKey;
  13. import javax.security.auth.Refreshable;
  14. import javax.security.auth.Destroyable;
  15. import javax.security.auth.RefreshFailedException;
  16. import javax.security.auth.DestroyFailedException;
  17. import sun.misc.HexDumpEncoder;
  18. import sun.security.krb5.EncryptionKey;
  19. import sun.security.krb5.Asn1Exception;
  20. import sun.security.util.*;
  21. /**
  22. * This class encapsulates a Kerberos ticket and associated
  23. * information as viewed from the client's point of view. It captures all
  24. * information that the Key Distribution Center (KDC) sends to the client
  25. * in the reply message KDC-REP defined in the Kerberos Protocol
  26. * Specification (<a href=http://www.ietf.org/rfc/rfc1510.txt>RFC 1510</a>).
  27. * <p>
  28. * All Kerberos JAAS login modules that authenticate a user to a KDC should
  29. * use this class. Where available, the login module might even read this
  30. * information from a ticket cache in the operating system instead of
  31. * directly communicating with the KDC. During the commit phase of the JAAS
  32. * authentication process, the JAAS login module should instantiate this
  33. * class and store the instance in the private credential set of a
  34. * {@link javax.security.auth.Subject Subject}.<p>
  35. *
  36. * It might be necessary for the application to be granted a
  37. * {@link javax.security.auth.PrivateCredentialPermission
  38. * PrivateCredentialPermission} if it needs to access a KerberosTicket
  39. * instance from a Subject. This permission is not needed when the
  40. * application depends on the default JGSS Kerberos mechanism to access the
  41. * KerberosTicket. In that case, however, the application will need an
  42. * appropriate
  43. * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}.
  44. * <p>
  45. * Note that this class is applicable to both ticket granting tickets and
  46. * other regular service tickets. A ticket granting ticket is just a
  47. * special case of a more generalized service ticket.
  48. *
  49. * @see javax.security.auth.Subject
  50. * @see javax.security.auth.PrivateCredentialPermission
  51. * @see javax.security.auth.login.LoginContext
  52. * @see org.ietf.jgss.GSSCredential
  53. * @see org.ietf.jgss.GSSManager
  54. *
  55. * @author Mayank Upadhyay
  56. * @version 1.13, 01/23/03
  57. * @since 1.4
  58. */
  59. public class KerberosTicket implements Destroyable, Refreshable,
  60. java.io.Serializable {
  61. // TBD: Make these flag indices public
  62. private static int FORWARDABLE_TICKET_FLAG = 1;
  63. private static int FORWARDED_TICKET_FLAG = 2;
  64. private static int PROXIABLE_TICKET_FLAG = 3;
  65. private static int PROXY_TICKET_FLAG = 4;
  66. private static int POSTDATED_TICKET_FLAG = 6;
  67. private static int RENEWABLE_TICKET_FLAG = 8;
  68. private static int INITIAL_TICKET_FLAG = 9;
  69. private static int NUM_FLAGS = 32;
  70. /**
  71. *
  72. * ASN.1 DER Encoding of the Ticket as defined in the
  73. * Kerberos Protocol Specification RFC1510.
  74. *
  75. * @serial
  76. */
  77. private byte[] asn1Encoding;
  78. /**
  79. *<code>KeyImpl</code> is serialized by writing out the ASN1 Encoded bytes
  80. * of the encryption key. The ASN1 encoding is defined in RFC1510 and as
  81. * follows:
  82. * <pre>
  83. * EncryptionKey ::= SEQUENCE {
  84. * keytype[0] INTEGER,
  85. * keyvalue[1] OCTET STRING
  86. * }
  87. * </pre>
  88. *
  89. * @serial
  90. */
  91. private KeyImpl sessionKey;
  92. /**
  93. *
  94. * Ticket Flags as defined in the Kerberos Protocol Specification RFC1510.
  95. * @serial
  96. */
  97. private boolean[] flags;
  98. /**
  99. *
  100. * Time of initial authentication
  101. *
  102. * @serial
  103. */
  104. private Date authTime;
  105. /**
  106. *
  107. * Time after which the ticket is valid.
  108. * @serial
  109. */
  110. private Date startTime;
  111. /**
  112. *
  113. * Time after which the ticket will not be honored. (its expiration time).
  114. *
  115. * @serial
  116. */
  117. private Date endTime;
  118. /**
  119. *
  120. * For renewable Tickets it indicates the maximum endtime that may be
  121. * included in a renewal. It can be thought of as the absolute expiration
  122. * time for the ticket, including all renewals. This field may be null
  123. * for tickets that are not renewable.
  124. *
  125. * @serial
  126. */
  127. private Date renewTill;
  128. /**
  129. *
  130. * Client that owns the service ticket
  131. *
  132. * @serial
  133. */
  134. private KerberosPrincipal client;
  135. /**
  136. *
  137. * The service for which the ticket was issued.
  138. *
  139. * @serial
  140. */
  141. private KerberosPrincipal server;
  142. /**
  143. *
  144. * The addresses from where the ticket may be used by the client.
  145. * This field may be null when the ticket is usable from any address.
  146. *
  147. * @serial
  148. */
  149. private InetAddress[] clientAddresses;
  150. private transient boolean destroyed = false;
  151. /**
  152. * Constructs a KerberosTicket using credentials information that a
  153. * client either receives from a KDC or reads from a cache.
  154. *
  155. * @param asn1Encoding the ASN.1 encoding of the ticket as defined by
  156. * the Kerberos protocol specification.
  157. * @param client the client that owns this service
  158. * ticket
  159. * @param server the service that this ticket is for
  160. * @param sessionKey the raw bytes for the session key that must be
  161. * used to encrypt the authenticator that will be sent to the server
  162. * @param keyType the key type for the session key as defined by the
  163. * Kerberos protocol specification.
  164. * @param flags the ticket flags. Each element in this array indicates
  165. * the value for the corresponding bit in the ASN.1 BitString that
  166. * represents the ticket flags. If the number of elements in this array
  167. * is less than the number of flags used by the Kerberos protocol,
  168. * then the missing flags will be filled in with false.
  169. * @param authTime the time of initial authentication for the client
  170. * @param startTime the time after which the ticket will be valid. This
  171. * may be null in which case the value of authTime is treated as the
  172. * startTime.
  173. * @param endTime the time after which the ticket will no longer be
  174. * valid
  175. * @param renewTill an absolute expiration time for the ticket,
  176. * including all renewal that might be possible. This field may be null
  177. * for tickets that are not renewable.
  178. * @param clientAddresses the addresses from where the ticket may be
  179. * used by the client. This field may be null when the ticket is usable
  180. * from any address.
  181. */
  182. public KerberosTicket(byte[] asn1Encoding,
  183. KerberosPrincipal client,
  184. KerberosPrincipal server,
  185. byte[] sessionKey,
  186. int keyType,
  187. boolean[] flags,
  188. Date authTime,
  189. Date startTime,
  190. Date endTime,
  191. Date renewTill,
  192. InetAddress[] clientAddresses) {
  193. init(asn1Encoding, client, server, sessionKey, keyType, flags,
  194. authTime, startTime, endTime, renewTill, clientAddresses);
  195. }
  196. private void init(byte[] asn1Encoding,
  197. KerberosPrincipal client,
  198. KerberosPrincipal server,
  199. byte[] sessionKey,
  200. int keyType,
  201. boolean[] flags,
  202. Date authTime,
  203. Date startTime,
  204. Date endTime,
  205. Date renewTill,
  206. InetAddress[] clientAddresses) {
  207. if (asn1Encoding == null)
  208. throw new IllegalArgumentException("ASN.1 encoding of ticket"
  209. + " cannot be null");
  210. this.asn1Encoding = asn1Encoding;
  211. if (client == null)
  212. throw new IllegalArgumentException("Client name in ticket"
  213. + " cannot be null");
  214. this.client = client;
  215. if (server == null)
  216. throw new IllegalArgumentException("Server name in ticket"
  217. + " cannot be null");
  218. this.server = server;
  219. if (sessionKey == null)
  220. throw new IllegalArgumentException("Session key for ticket"
  221. + " cannot be null");
  222. this.sessionKey = new KeyImpl(sessionKey, keyType);
  223. if (flags != null) {
  224. if (flags.length >= NUM_FLAGS)
  225. this.flags = (boolean[]) flags.clone();
  226. else {
  227. this.flags = new boolean[NUM_FLAGS];
  228. // Fill in whatever we have
  229. for (int i = 0; i < flags.length; i++)
  230. this.flags[i] = flags[i];
  231. }
  232. } else
  233. this.flags = new boolean[NUM_FLAGS];
  234. if (flags[RENEWABLE_TICKET_FLAG]) {
  235. if (renewTill == null)
  236. throw new IllegalArgumentException("The renewable period "
  237. + "end time cannot be null for renewable tickets.");
  238. this.renewTill = renewTill;
  239. }
  240. if (authTime == null)
  241. throw new IllegalArgumentException("Authentication time of ticket"
  242. + " cannot be null");
  243. this.authTime = authTime;
  244. this.startTime = (startTime != null? startTime: authTime);
  245. if (endTime == null)
  246. throw new IllegalArgumentException("End time for ticket validity"
  247. + " cannot be null");
  248. this.endTime = endTime;
  249. if (clientAddresses != null)
  250. this.clientAddresses = (InetAddress[]) clientAddresses.clone();
  251. }
  252. /**
  253. * Returns the client principal associated with this ticket.
  254. *
  255. * @return the client principal.
  256. */
  257. public final KerberosPrincipal getClient() {
  258. return client;
  259. }
  260. /**
  261. * Returns the service principal associated with this ticket.
  262. *
  263. * @return the service principal.
  264. */
  265. public final KerberosPrincipal getServer() {
  266. return server;
  267. }
  268. /**
  269. * Returns the session key associated with this ticket.
  270. *
  271. * @return the session key.
  272. */
  273. public final SecretKey getSessionKey() {
  274. if (destroyed)
  275. throw new IllegalStateException("This ticket is no longer valid");
  276. return sessionKey;
  277. }
  278. /**
  279. * Returns the key type of the session key associated with this
  280. * ticket as defined by the Kerberos Protocol Specification.
  281. *
  282. * @return the key type of the session key associated with this
  283. * ticket.
  284. *
  285. * @see #getSessionKey()
  286. */
  287. public final int getSessionKeyType() {
  288. if (destroyed)
  289. throw new IllegalStateException("This ticket is no longer valid");
  290. return sessionKey.getKeyType();
  291. }
  292. /** Determines if this ticket is forwardable.
  293. *
  294. * @return true if this ticket is forwardable, false if not.
  295. */
  296. public final boolean isForwardable() {
  297. return flags[FORWARDABLE_TICKET_FLAG];
  298. }
  299. /**
  300. * Determines if this ticket had been forwarded or was issued based on
  301. * authentication involving a forwarded ticket-granting ticket.
  302. *
  303. * @return true if this ticket had been forwarded or was issued based on
  304. * authentication involving a forwarded ticket-granting ticket,
  305. * false otherwise.
  306. */
  307. public final boolean isForwarded() {
  308. return flags[FORWARDED_TICKET_FLAG];
  309. }
  310. /** Determines if this ticket is proxiable.
  311. *
  312. * @return true if this ticket is proxiable, false if not.
  313. */
  314. public final boolean isProxiable() {
  315. return flags[PROXIABLE_TICKET_FLAG];
  316. }
  317. /** Determines is this ticket is a proxy-ticket.
  318. *
  319. * @return true if this ticket is a proxy-ticket, false if not.
  320. */
  321. public final boolean isProxy() {
  322. return flags[PROXY_TICKET_FLAG];
  323. }
  324. /** Determines is this ticket is post-dated.
  325. *
  326. * @return true if this ticket is post-dated, false if not.
  327. */
  328. public final boolean isPostdated() {
  329. return flags[POSTDATED_TICKET_FLAG];
  330. }
  331. /**
  332. * Determines is this ticket is renewable. If so, the {@link #refresh()
  333. * refresh} method can be called, assuming the validity period for
  334. * renewing is not already over.
  335. *
  336. * @return true if this ticket is renewable, false if not.
  337. */
  338. public final boolean isRenewable() {
  339. return flags[RENEWABLE_TICKET_FLAG];
  340. }
  341. /**
  342. * Determines if this ticket was issued using the Kerberos AS-Exchange
  343. * protocol, and not issued based on some ticket-granting ticket.
  344. *
  345. * @return true if this ticket was issued using the Kerberos AS-Exchange
  346. * protocol, false if not.
  347. */
  348. public final boolean isInitial() {
  349. return flags[INITIAL_TICKET_FLAG];
  350. }
  351. /**
  352. * Returns the flags associated with this ticket. Each element in the
  353. * returned array indicates the value for the corresponding bit in the
  354. * ASN.1 BitString that represents the ticket flags.
  355. *
  356. * @return the flags associated with this ticket.
  357. */
  358. public final boolean[] getFlags() {
  359. return (flags == null? null: (boolean[]) flags.clone());
  360. }
  361. /**
  362. * Returns the time that the client was authenticated.
  363. *
  364. * @return the time that the client was authenticated.
  365. */
  366. public final java.util.Date getAuthTime() {
  367. return authTime;
  368. }
  369. /**
  370. * Returns the start time for this ticket's validity period.
  371. *
  372. * @return the start time for this ticket's validity period.
  373. */
  374. public final java.util.Date getStartTime() {
  375. return startTime;
  376. }
  377. /**
  378. * Returns the expiration time for this ticket's validity period.
  379. *
  380. * @return the expiration time for this ticket's validity period.
  381. */
  382. public final java.util.Date getEndTime() {
  383. return endTime;
  384. }
  385. /**
  386. * Returns the latest expiration time for this ticket, including all
  387. * renewals. This will return a null value for non-renewable tickets.
  388. *
  389. * @return the latest expiration time for this ticket.
  390. */
  391. public final java.util.Date getRenewTill() {
  392. return renewTill;
  393. }
  394. /**
  395. * Returns a list of addresses from where the ticket can be used.
  396. *
  397. * @return ths list of addresses or null, if the field was not
  398. * provided.
  399. */
  400. public final java.net.InetAddress[] getClientAddresses() {
  401. return (clientAddresses == null?
  402. null: (InetAddress[]) clientAddresses.clone());
  403. }
  404. /**
  405. * Returns an ASN.1 encoding of the entire ticket.
  406. *
  407. * @return an ASN.1 encoding of the entire ticket.
  408. */
  409. public final byte[] getEncoded() {
  410. if (destroyed)
  411. throw new IllegalStateException("This ticket is no longer valid");
  412. return (byte[]) asn1Encoding.clone();
  413. }
  414. /** Determines if this ticket is still current. */
  415. public boolean isCurrent() {
  416. return (System.currentTimeMillis() <= getEndTime().getTime());
  417. }
  418. /**
  419. * Extends the validity period of this ticket. The ticket will contain
  420. * a new session key if the refresh operation succeeds. The refresh
  421. * operation will fail if the ticket is not renewable or the latest
  422. * allowable renew time has passed. Any other error returned by the
  423. * KDC will also cause this method to fail.
  424. *
  425. * Note: This method is not synchronized with the the accessor
  426. * methods of this object. Hence callers need to be aware of multiple
  427. * threads that might access this and try to renew it at the same
  428. * time.
  429. *
  430. * @throws RefreshFailedException if the ticket is not renewable, or
  431. * the latest allowable renew time has passed, or the KDC returns some
  432. * error.
  433. *
  434. * @see #isRenewable()
  435. * @see #getRenewTill()
  436. */
  437. public void refresh() throws RefreshFailedException {
  438. if (destroyed)
  439. throw new RefreshFailedException("A destroyed ticket "
  440. + "cannot be renewd.");
  441. if (!isRenewable())
  442. throw new RefreshFailedException("This ticket is not renewable");
  443. if (System.currentTimeMillis() > getRenewTill().getTime())
  444. throw new RefreshFailedException("This ticket is past "
  445. + "its last renewal time.");
  446. Throwable e = null;
  447. sun.security.krb5.Credentials krb5Creds = null;
  448. try {
  449. krb5Creds = new sun.security.krb5.Credentials(asn1Encoding,
  450. client.toString(),
  451. server.toString(),
  452. sessionKey.getEncoded(),
  453. sessionKey.getKeyType(),
  454. flags,
  455. authTime,
  456. startTime,
  457. endTime,
  458. renewTill,
  459. clientAddresses);
  460. krb5Creds = krb5Creds.renew();
  461. } catch (sun.security.krb5.KrbException krbException) {
  462. e = krbException;
  463. } catch (java.io.IOException ioException) {
  464. e = ioException;
  465. }
  466. if (e != null) {
  467. RefreshFailedException rfException
  468. = new RefreshFailedException("Failed to renew Kerberos Ticket "
  469. + "for client " + client
  470. + " and server " + server
  471. + " - " + e.getMessage());
  472. rfException.initCause(e);
  473. throw rfException;
  474. }
  475. /*
  476. * In case multiple threads try to refresh it at the same time.
  477. */
  478. synchronized (this) {
  479. try {
  480. this.destroy();
  481. } catch (DestroyFailedException dfException) {
  482. // Squelch it since we don't care about the old ticket.
  483. }
  484. init(krb5Creds.getEncoded(),
  485. new KerberosPrincipal(krb5Creds.getClient().getName()),
  486. new KerberosPrincipal(krb5Creds.getServer().getName()),
  487. krb5Creds.getSessionKey().getBytes(),
  488. krb5Creds.getSessionKey().getEType(),
  489. krb5Creds.getFlags(),
  490. krb5Creds.getAuthTime(),
  491. krb5Creds.getStartTime(),
  492. krb5Creds.getEndTime(),
  493. krb5Creds.getRenewTill(),
  494. krb5Creds.getClientAddresses());
  495. destroyed = false;
  496. }
  497. }
  498. /**
  499. * Destroys the ticket and destroys any sensitive information stored in
  500. * it.
  501. */
  502. public void destroy() throws DestroyFailedException {
  503. if (!destroyed) {
  504. Arrays.fill(asn1Encoding, (byte) 0);
  505. client = null;
  506. server = null;
  507. sessionKey.destroy();
  508. flags = null;
  509. authTime = null;
  510. startTime = null;
  511. endTime = null;
  512. renewTill = null;
  513. clientAddresses = null;
  514. destroyed = true;
  515. }
  516. }
  517. /** Determines if this ticket has been destroyed.*/
  518. public boolean isDestroyed() {
  519. return destroyed;
  520. }
  521. public String toString() {
  522. if (destroyed)
  523. throw new IllegalStateException("This ticket is no longer valid");
  524. StringBuffer caddrBuf = new StringBuffer();
  525. if (clientAddresses != null) {
  526. for (int i =0;i < clientAddresses.length; i++) {
  527. caddrBuf.append("clientAddresses[" + i + "] = " +
  528. clientAddresses[i].toString());
  529. }
  530. }
  531. return ("Ticket (hex) = " + "\n" +
  532. (new HexDumpEncoder()).encode(asn1Encoding) + "\n" +
  533. "Client Principal = " + client.toString() + "\n" +
  534. "Server Principal = " + server.toString() + "\n" +
  535. "Session Key = " + sessionKey.toString() + "\n" +
  536. "Forwardable Ticket " + flags[FORWARDABLE_TICKET_FLAG] + "\n" +
  537. "Forwarded Ticket " + flags[FORWARDED_TICKET_FLAG] + "\n" +
  538. "Proxiable Ticket " + flags[PROXIABLE_TICKET_FLAG] + "\n" +
  539. "Proxy Ticket " + flags[PROXY_TICKET_FLAG] + "\n" +
  540. "Postdated Ticket " + flags[POSTDATED_TICKET_FLAG] + "\n" +
  541. "Renewable Ticket " + flags[RENEWABLE_TICKET_FLAG] + "\n" +
  542. "Initial Ticket " + flags[RENEWABLE_TICKET_FLAG] + "\n" +
  543. "Auth Time = " + authTime.toString() + "\n" +
  544. "Start Time = " + startTime.toString() + "\n" +
  545. "End Time = " + endTime.toString() + "\n" +
  546. "Renew Till = " +
  547. (renewTill == null ? "Null " :renewTill.toString()) + "\n" +
  548. "Client Addresses " +
  549. (clientAddresses == null ? " Null ":caddrBuf.toString() + "\n")
  550. );
  551. }
  552. }