Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/javax/security/auth/kerberos/KerberosTicket.java
38918 views
/*1* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.security.auth.kerberos;2627import java.io.*;28import java.util.Date;29import java.util.Arrays;30import java.net.InetAddress;31import java.util.Objects;32import javax.crypto.SecretKey;33import javax.security.auth.Refreshable;34import javax.security.auth.Destroyable;35import javax.security.auth.RefreshFailedException;36import javax.security.auth.DestroyFailedException;3738import sun.misc.HexDumpEncoder;39import sun.security.krb5.EncryptionKey;40import sun.security.krb5.Asn1Exception;41import sun.security.util.*;4243/**44* This class encapsulates a Kerberos ticket and associated45* information as viewed from the client's point of view. It captures all46* information that the Key Distribution Center (KDC) sends to the client47* in the reply message KDC-REP defined in the Kerberos Protocol48* Specification (<a href=http://www.ietf.org/rfc/rfc4120.txt>RFC 4120</a>).49* <p>50* All Kerberos JAAS login modules that authenticate a user to a KDC should51* use this class. Where available, the login module might even read this52* information from a ticket cache in the operating system instead of53* directly communicating with the KDC. During the commit phase of the JAAS54* authentication process, the JAAS login module should instantiate this55* class and store the instance in the private credential set of a56* {@link javax.security.auth.Subject Subject}.<p>57*58* It might be necessary for the application to be granted a59* {@link javax.security.auth.PrivateCredentialPermission60* PrivateCredentialPermission} if it needs to access a KerberosTicket61* instance from a Subject. This permission is not needed when the62* application depends on the default JGSS Kerberos mechanism to access the63* KerberosTicket. In that case, however, the application will need an64* appropriate65* {@link javax.security.auth.kerberos.ServicePermission ServicePermission}.66* <p>67* Note that this class is applicable to both ticket granting tickets and68* other regular service tickets. A ticket granting ticket is just a69* special case of a more generalized service ticket.70*71* @see javax.security.auth.Subject72* @see javax.security.auth.PrivateCredentialPermission73* @see javax.security.auth.login.LoginContext74* @see org.ietf.jgss.GSSCredential75* @see org.ietf.jgss.GSSManager76*77* @author Mayank Upadhyay78* @since 1.479*/80public class KerberosTicket implements Destroyable, Refreshable,81java.io.Serializable {8283private static final long serialVersionUID = 7395334370157380539L;8485// XXX Make these flag indices public86private static final int FORWARDABLE_TICKET_FLAG = 1;87private static final int FORWARDED_TICKET_FLAG = 2;88private static final int PROXIABLE_TICKET_FLAG = 3;89private static final int PROXY_TICKET_FLAG = 4;90private static final int POSTDATED_TICKET_FLAG = 6;91private static final int RENEWABLE_TICKET_FLAG = 8;92private static final int INITIAL_TICKET_FLAG = 9;9394private static final int NUM_FLAGS = 32;9596/**97*98* ASN.1 DER Encoding of the Ticket as defined in the99* Kerberos Protocol Specification RFC4120.100*101* @serial102*/103104private byte[] asn1Encoding;105106/**107*{@code KeyImpl} is serialized by writing out the ASN1 Encoded bytes108* of the encryption key. The ASN1 encoding is defined in RFC4120 and as109* follows:110* <pre>111* EncryptionKey ::= SEQUENCE {112* keytype [0] Int32 -- actually encryption type --,113* keyvalue [1] OCTET STRING114* }115* </pre>116*117* @serial118*/119120private KeyImpl sessionKey;121122/**123*124* Ticket Flags as defined in the Kerberos Protocol Specification RFC4120.125*126* @serial127*/128129private boolean[] flags;130131/**132*133* Time of initial authentication134*135* @serial136*/137138private Date authTime;139140/**141*142* Time after which the ticket is valid.143* @serial144*/145private Date startTime;146147/**148*149* Time after which the ticket will not be honored. (its expiration time).150*151* @serial152*/153154private Date endTime;155156/**157*158* For renewable Tickets it indicates the maximum endtime that may be159* included in a renewal. It can be thought of as the absolute expiration160* time for the ticket, including all renewals. This field may be null161* for tickets that are not renewable.162*163* @serial164*/165166private Date renewTill;167168/**169*170* Client that owns the service ticket171*172* @serial173*/174175private KerberosPrincipal client;176177/**178*179* The service for which the ticket was issued.180*181* @serial182*/183184private KerberosPrincipal server;185186/**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* @serial192*/193194private InetAddress[] clientAddresses;195196transient KerberosPrincipal clientAlias = null;197198transient KerberosPrincipal serverAlias = null;199200/**201* Evidence ticket if proxy_impersonator. This field can be accessed202* by KerberosSecrets. It's serialized.203*/204KerberosTicket proxy = null;205206private transient boolean destroyed = false;207208/**209* Constructs a KerberosTicket using credentials information that a210* client either receives from a KDC or reads from a cache.211*212* @param asn1Encoding the ASN.1 encoding of the ticket as defined by213* the Kerberos protocol specification.214* @param client the client that owns this service215* ticket216* @param server the service that this ticket is for217* @param sessionKey the raw bytes for the session key that must be218* used to encrypt the authenticator that will be sent to the server219* @param keyType the key type for the session key as defined by the220* Kerberos protocol specification.221* @param flags the ticket flags. Each element in this array indicates222* the value for the corresponding bit in the ASN.1 BitString that223* represents the ticket flags. If the number of elements in this array224* is less than the number of flags used by the Kerberos protocol,225* then the missing flags will be filled in with false.226* @param authTime the time of initial authentication for the client227* @param startTime the time after which the ticket will be valid. This228* may be null in which case the value of authTime is treated as the229* startTime.230* @param endTime the time after which the ticket will no longer be231* valid232* @param renewTill an absolute expiration time for the ticket,233* including all renewal that might be possible. This field may be null234* for tickets that are not renewable.235* @param clientAddresses the addresses from where the ticket may be236* used by the client. This field may be null when the ticket is usable237* from any address.238*/239public KerberosTicket(byte[] asn1Encoding,240KerberosPrincipal client,241KerberosPrincipal server,242byte[] sessionKey,243int keyType,244boolean[] flags,245Date authTime,246Date startTime,247Date endTime,248Date renewTill,249InetAddress[] clientAddresses) {250251init(asn1Encoding, client, server, sessionKey, keyType, flags,252authTime, startTime, endTime, renewTill, clientAddresses);253}254255private void init(byte[] asn1Encoding,256KerberosPrincipal client,257KerberosPrincipal server,258byte[] sessionKey,259int keyType,260boolean[] flags,261Date authTime,262Date startTime,263Date endTime,264Date renewTill,265InetAddress[] clientAddresses) {266if (sessionKey == null)267throw new IllegalArgumentException("Session key for ticket"268+ " cannot be null");269init(asn1Encoding, client, server,270new KeyImpl(sessionKey, keyType), flags, authTime,271startTime, endTime, renewTill, clientAddresses);272}273274private void init(byte[] asn1Encoding,275KerberosPrincipal client,276KerberosPrincipal server,277KeyImpl sessionKey,278boolean[] flags,279Date authTime,280Date startTime,281Date endTime,282Date renewTill,283InetAddress[] clientAddresses) {284if (asn1Encoding == null)285throw new IllegalArgumentException("ASN.1 encoding of ticket"286+ " cannot be null");287this.asn1Encoding = asn1Encoding.clone();288289if (client == null)290throw new IllegalArgumentException("Client name in ticket"291+ " cannot be null");292this.client = client;293294if (server == null)295throw new IllegalArgumentException("Server name in ticket"296+ " cannot be null");297this.server = server;298299// Caller needs to make sure `sessionKey` will not be null300this.sessionKey = sessionKey;301302if (flags != null) {303if (flags.length >= NUM_FLAGS)304this.flags = flags.clone();305else {306this.flags = new boolean[NUM_FLAGS];307// Fill in whatever we have308for (int i = 0; i < flags.length; i++)309this.flags[i] = flags[i];310}311} else312this.flags = new boolean[NUM_FLAGS];313314if (this.flags[RENEWABLE_TICKET_FLAG] && renewTill != null) {315this.renewTill = new Date(renewTill.getTime());316}317318if (authTime != null) {319this.authTime = new Date(authTime.getTime());320}321if (startTime != null) {322this.startTime = new Date(startTime.getTime());323} else {324this.startTime = this.authTime;325}326327if (endTime == null)328throw new IllegalArgumentException("End time for ticket validity"329+ " cannot be null");330this.endTime = new Date(endTime.getTime());331332if (clientAddresses != null)333this.clientAddresses = clientAddresses.clone();334}335336/**337* Returns the client principal associated with this ticket.338*339* @return the client principal.340*/341public final KerberosPrincipal getClient() {342return client;343}344345/**346* Returns the service principal associated with this ticket.347*348* @return the service principal.349*/350public final KerberosPrincipal getServer() {351return server;352}353354/**355* Returns the session key associated with this ticket.356*357* @return the session key.358*/359public final SecretKey getSessionKey() {360if (destroyed)361throw new IllegalStateException("This ticket is no longer valid");362return sessionKey;363}364365/**366* Returns the key type of the session key associated with this367* ticket as defined by the Kerberos Protocol Specification.368*369* @return the key type of the session key associated with this370* ticket.371*372* @see #getSessionKey()373*/374public final int getSessionKeyType() {375if (destroyed)376throw new IllegalStateException("This ticket is no longer valid");377return sessionKey.getKeyType();378}379380/**381* Determines if this ticket is forwardable.382*383* @return true if this ticket is forwardable, false if not.384*/385public final boolean isForwardable() {386return flags == null? false: flags[FORWARDABLE_TICKET_FLAG];387}388389/**390* Determines if this ticket had been forwarded or was issued based on391* authentication involving a forwarded ticket-granting ticket.392*393* @return true if this ticket had been forwarded or was issued based on394* authentication involving a forwarded ticket-granting ticket,395* false otherwise.396*/397public final boolean isForwarded() {398return flags == null? false: flags[FORWARDED_TICKET_FLAG];399}400401/**402* Determines if this ticket is proxiable.403*404* @return true if this ticket is proxiable, false if not.405*/406public final boolean isProxiable() {407return flags == null? false: flags[PROXIABLE_TICKET_FLAG];408}409410/**411* Determines is this ticket is a proxy-ticket.412*413* @return true if this ticket is a proxy-ticket, false if not.414*/415public final boolean isProxy() {416return flags == null? false: flags[PROXY_TICKET_FLAG];417}418419420/**421* Determines is this ticket is post-dated.422*423* @return true if this ticket is post-dated, false if not.424*/425public final boolean isPostdated() {426return flags == null? false: flags[POSTDATED_TICKET_FLAG];427}428429/**430* Determines is this ticket is renewable. If so, the {@link #refresh()431* refresh} method can be called, assuming the validity period for432* renewing is not already over.433*434* @return true if this ticket is renewable, false if not.435*/436public final boolean isRenewable() {437return flags == null? false: flags[RENEWABLE_TICKET_FLAG];438}439440/**441* Determines if this ticket was issued using the Kerberos AS-Exchange442* protocol, and not issued based on some ticket-granting ticket.443*444* @return true if this ticket was issued using the Kerberos AS-Exchange445* protocol, false if not.446*/447public final boolean isInitial() {448return flags == null? false: flags[INITIAL_TICKET_FLAG];449}450451/**452* Returns the flags associated with this ticket. Each element in the453* returned array indicates the value for the corresponding bit in the454* ASN.1 BitString that represents the ticket flags.455*456* @return the flags associated with this ticket.457*/458public final boolean[] getFlags() {459return (flags == null? null: flags.clone());460}461462/**463* Returns the time that the client was authenticated.464*465* @return the time that the client was authenticated466* or null if not set.467*/468public final java.util.Date getAuthTime() {469return (authTime == null) ? null : (Date)authTime.clone();470}471472/**473* Returns the start time for this ticket's validity period.474*475* @return the start time for this ticket's validity period476* or null if not set.477*/478public final java.util.Date getStartTime() {479return (startTime == null) ? null : (Date)startTime.clone();480}481482/**483* Returns the expiration time for this ticket's validity period.484*485* @return the expiration time for this ticket's validity period.486*/487public final java.util.Date getEndTime() {488return (endTime == null) ? null : (Date) endTime.clone();489}490491/**492* Returns the latest expiration time for this ticket, including all493* renewals. This will return a null value for non-renewable tickets.494*495* @return the latest expiration time for this ticket.496*/497public final java.util.Date getRenewTill() {498return (renewTill == null) ? null: (Date)renewTill.clone();499}500501/**502* Returns a list of addresses from where the ticket can be used.503*504* @return ths list of addresses or null, if the field was not505* provided.506*/507public final java.net.InetAddress[] getClientAddresses() {508return (clientAddresses == null) ? null: clientAddresses.clone();509}510511/**512* Returns an ASN.1 encoding of the entire ticket.513*514* @return an ASN.1 encoding of the entire ticket.515*/516public final byte[] getEncoded() {517if (destroyed)518throw new IllegalStateException("This ticket is no longer valid");519return asn1Encoding.clone();520}521522/** Determines if this ticket is still current. */523public boolean isCurrent() {524return endTime == null? false: (System.currentTimeMillis() <= endTime.getTime());525}526527/**528* Extends the validity period of this ticket. The ticket will contain529* a new session key if the refresh operation succeeds. The refresh530* operation will fail if the ticket is not renewable or the latest531* allowable renew time has passed. Any other error returned by the532* KDC will also cause this method to fail.533*534* Note: This method is not synchronized with the the accessor535* methods of this object. Hence callers need to be aware of multiple536* threads that might access this and try to renew it at the same537* time.538*539* @throws RefreshFailedException if the ticket is not renewable, or540* the latest allowable renew time has passed, or the KDC returns some541* error.542*543* @see #isRenewable()544* @see #getRenewTill()545*/546public void refresh() throws RefreshFailedException {547548if (destroyed)549throw new RefreshFailedException("A destroyed ticket "550+ "cannot be renewd.");551552if (!isRenewable())553throw new RefreshFailedException("This ticket is not renewable");554555if (getRenewTill() == null) {556// Renewable ticket without renew-till. Illegal and ignored.557return;558}559560if (System.currentTimeMillis() > getRenewTill().getTime())561throw new RefreshFailedException("This ticket is past "562+ "its last renewal time.");563Throwable e = null;564sun.security.krb5.Credentials krb5Creds = null;565566try {567krb5Creds = new sun.security.krb5.Credentials(asn1Encoding,568client.toString(),569(clientAlias != null ?570clientAlias.getName() : null),571server.toString(),572(serverAlias != null ?573serverAlias.getName() : null),574sessionKey.getEncoded(),575sessionKey.getKeyType(),576flags,577authTime,578startTime,579endTime,580renewTill,581clientAddresses);582krb5Creds = krb5Creds.renew();583} catch (sun.security.krb5.KrbException krbException) {584e = krbException;585} catch (java.io.IOException ioException) {586e = ioException;587}588589if (e != null) {590RefreshFailedException rfException591= new RefreshFailedException("Failed to renew Kerberos Ticket "592+ "for client " + client593+ " and server " + server594+ " - " + e.getMessage());595rfException.initCause(e);596throw rfException;597}598599/*600* In case multiple threads try to refresh it at the same time.601*/602synchronized (this) {603try {604this.destroy();605} catch (DestroyFailedException dfException) {606// Squelch it since we don't care about the old ticket.607}608init(krb5Creds.getEncoded(),609new KerberosPrincipal(krb5Creds.getClient().getName()),610new KerberosPrincipal(krb5Creds.getServer().getName(),611KerberosPrincipal.KRB_NT_SRV_INST),612krb5Creds.getSessionKey().getBytes(),613krb5Creds.getSessionKey().getEType(),614krb5Creds.getFlags(),615krb5Creds.getAuthTime(),616krb5Creds.getStartTime(),617krb5Creds.getEndTime(),618krb5Creds.getRenewTill(),619krb5Creds.getClientAddresses());620destroyed = false;621}622}623624/**625* Destroys the ticket and destroys any sensitive information stored in626* it.627*/628public void destroy() throws DestroyFailedException {629if (!destroyed) {630Arrays.fill(asn1Encoding, (byte) 0);631client = null;632server = null;633sessionKey.destroy();634flags = null;635authTime = null;636startTime = null;637endTime = null;638renewTill = null;639clientAddresses = null;640destroyed = true;641}642}643644/**645* Determines if this ticket has been destroyed.646*/647public boolean isDestroyed() {648return destroyed;649}650651public String toString() {652if (destroyed) {653return "Destroyed KerberosTicket";654}655StringBuffer caddrBuf = new StringBuffer();656if (clientAddresses != null) {657for (int i = 0; i < clientAddresses.length; i++) {658caddrBuf.append("clientAddresses[" + i + "] = " +659clientAddresses[i].toString());660}661}662return ("Ticket (hex) = " + "\n" +663(new HexDumpEncoder()).encodeBuffer(asn1Encoding) + "\n" +664"Client Principal = " + client.toString() + "\n" +665"Server Principal = " + server.toString() + "\n" +666"Session Key = " + sessionKey.toString() + "\n" +667"Forwardable Ticket " + flags[FORWARDABLE_TICKET_FLAG] + "\n" +668"Forwarded Ticket " + flags[FORWARDED_TICKET_FLAG] + "\n" +669"Proxiable Ticket " + flags[PROXIABLE_TICKET_FLAG] + "\n" +670"Proxy Ticket " + flags[PROXY_TICKET_FLAG] + "\n" +671"Postdated Ticket " + flags[POSTDATED_TICKET_FLAG] + "\n" +672"Renewable Ticket " + flags[RENEWABLE_TICKET_FLAG] + "\n" +673"Initial Ticket " + flags[RENEWABLE_TICKET_FLAG] + "\n" +674"Auth Time = " + String.valueOf(authTime) + "\n" +675"Start Time = " + String.valueOf(startTime) + "\n" +676"End Time = " + endTime.toString() + "\n" +677"Renew Till = " + String.valueOf(renewTill) + "\n" +678"Client Addresses " +679(clientAddresses == null ? " Null " : caddrBuf.toString() +680(proxy == null ? "" : "\nwith a proxy ticket") +681"\n"));682}683684/**685* Returns a hashcode for this KerberosTicket.686*687* @return a hashCode() for the {@code KerberosTicket}688* @since 1.6689*/690public int hashCode() {691int result = 17;692if (isDestroyed()) {693return result;694}695result = result * 37 + Arrays.hashCode(getEncoded());696result = result * 37 + endTime.hashCode();697result = result * 37 + client.hashCode();698result = result * 37 + server.hashCode();699result = result * 37 + sessionKey.hashCode();700701// authTime may be null702if (authTime != null) {703result = result * 37 + authTime.hashCode();704}705706// startTime may be null707if (startTime != null) {708result = result * 37 + startTime.hashCode();709}710711// renewTill may be null712if (renewTill != null) {713result = result * 37 + renewTill.hashCode();714}715716// clientAddress may be null, the array's hashCode is 0717result = result * 37 + Arrays.hashCode(clientAddresses);718719if (proxy != null) {720result = result * 37 + proxy.hashCode();721}722return result * 37 + Arrays.hashCode(flags);723}724725/**726* Compares the specified Object with this KerberosTicket for equality.727* Returns true if the given object is also a728* {@code KerberosTicket} and the two729* {@code KerberosTicket} instances are equivalent.730*731* @param other the Object to compare to732* @return true if the specified object is equal to this KerberosTicket,733* false otherwise. NOTE: Returns false if either of the KerberosTicket734* objects has been destroyed.735* @since 1.6736*/737public boolean equals(Object other) {738739if (other == this)740return true;741742if (! (other instanceof KerberosTicket)) {743return false;744}745746KerberosTicket otherTicket = ((KerberosTicket) other);747if (isDestroyed() || otherTicket.isDestroyed()) {748return false;749}750751if (!Arrays.equals(getEncoded(), otherTicket.getEncoded()) ||752!endTime.equals(otherTicket.getEndTime()) ||753!server.equals(otherTicket.getServer()) ||754!client.equals(otherTicket.getClient()) ||755!sessionKey.equals(otherTicket.getSessionKey()) ||756!Arrays.equals(clientAddresses, otherTicket.getClientAddresses()) ||757!Arrays.equals(flags, otherTicket.getFlags())) {758return false;759}760761// authTime may be null762if (authTime == null) {763if (otherTicket.getAuthTime() != null)764return false;765} else {766if (!authTime.equals(otherTicket.getAuthTime()))767return false;768}769770// startTime may be null771if (startTime == null) {772if (otherTicket.getStartTime() != null)773return false;774} else {775if (!startTime.equals(otherTicket.getStartTime()))776return false;777}778779if (renewTill == null) {780if (otherTicket.getRenewTill() != null)781return false;782} else {783if (!renewTill.equals(otherTicket.getRenewTill()))784return false;785}786787if (!Objects.equals(proxy, otherTicket.proxy)) {788return false;789}790791return true;792}793794private void readObject(ObjectInputStream s)795throws IOException, ClassNotFoundException {796s.defaultReadObject();797if (sessionKey == null) {798throw new InvalidObjectException("Session key cannot be null");799}800try {801init(asn1Encoding, client, server, sessionKey,802flags, authTime, startTime, endTime,803renewTill, clientAddresses);804} catch (IllegalArgumentException iae) {805throw (InvalidObjectException)806new InvalidObjectException(iae.getMessage()).initCause(iae);807}808}809}810811812