/* * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.security.jgss; import org.ietf.jgss.*; import sun.security.jgss.spi.*; import java.util.Set; import java.util.HashMap; import java.util.HashSet; import java.util.Arrays; import java.io.IOException; import java.io.UnsupportedEncodingException; import sun.security.util.ObjectIdentifier; import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; /** * This is the implementation class for GSSName. Conceptually the * GSSName is a container with mechanism specific name elements. Each * name element is a representation of how that particular mechanism * would canonicalize this principal. * * Generally a GSSName is created by an application when it supplies * a sequence of bytes and a nametype that helps each mechanism * decide how to interpret those bytes. * * It is not necessary to create name elements for each available * mechanism at the time the application creates the GSSName. This * implementation does this lazily, as and when name elements for * mechanisms are required to be handed out. (Generally, other GSS * classes like GSSContext and GSSCredential request specific * elements depending on the mechanisms that they are dealing with.) * Assume that getting a mechanism to parse the applciation specified * bytes is an expensive call. * * When a GSSName is canonicalized wrt some mechanism, it is supposed * to discard all elements of other mechanisms and retain only the * element for this mechanism. In GSS terminology this is called a * Mechanism Name or MN. This implementation tries to retain the * application provided bytes and name type just in case the MN is * asked to produce an element for a mechanism that is different. * * When a GSSName is to be exported, the name element for the desired * mechanism is converted to a byte representation and written * out. It might happen that a name element for that mechanism cannot * be obtained. This happens when the mechanism is just not supported * in this GSS-API or when the mechanism is supported but bytes * corresponding to the nametypes that it understands are not * available in this GSSName. * * This class is safe for sharing. Each retrieval of a name element * from getElement() might potentially add a new element to the * hashmap of elements, but getElement() is synchronized. * * @author Mayank Upadhyay * @since 1.4 */ public class GSSNameImpl implements GSSName { private GSSManagerImpl gssManager = null; /* * Store whatever the application passed in. We will use this to * get individual mechanisms to create name elements as and when * needed. * Store both the String and the byte[]. Leave I18N to the * mechanism by allowing it to extract bytes from the String! */ private String appNameStr = null; private byte[] appNameBytes = null; private Oid appNameType = null; /* * When we figure out what the printable name would be, we store * both the name and its type. */ private String printableName = null; private Oid printableNameType = null; private HashMap<Oid, GSSNameSpi> elements = null; private GSSNameSpi mechElement = null; static GSSNameImpl wrapElement(GSSManagerImpl gssManager, GSSNameSpi mechElement) throws GSSException { return (mechElement == null ? null : new GSSNameImpl(gssManager, mechElement)); } GSSNameImpl(GSSManagerImpl gssManager, GSSNameSpi mechElement) { this.gssManager = gssManager; appNameStr = printableName = mechElement.toString(); appNameType = printableNameType = mechElement.getStringNameType(); this.mechElement = mechElement; elements = new HashMap<Oid, GSSNameSpi>(1); elements.put(mechElement.getMechanism(), this.mechElement); } GSSNameImpl(GSSManagerImpl gssManager, Object appName, Oid appNameType) throws GSSException { this(gssManager, appName, appNameType, null); } GSSNameImpl(GSSManagerImpl gssManager, Object appName, Oid appNameType, Oid mech) throws GSSException { if (appName == null) throw new GSSExceptionImpl(GSSException.BAD_NAME, "Cannot import null name"); if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; if (NT_EXPORT_NAME.equals(appNameType)) { importName(gssManager, appName); } else { init(gssManager, appName, appNameType, mech); } } private void init(GSSManagerImpl gssManager, Object appName, Oid appNameType, Oid mech) throws GSSException { this.gssManager = gssManager; this.elements = new HashMap<Oid, GSSNameSpi>(gssManager.getMechs().length); if (appName instanceof String) { this.appNameStr = (String) appName; /* * If appNameType is null, then the nametype for this printable * string is determined only by interrogating the * mechanism. Thus, defer the setting of printableName and * printableNameType till later. */ if (appNameType != null) { printableName = appNameStr; printableNameType = appNameType; } } else { this.appNameBytes = (byte[]) appName; } this.appNameType = appNameType; mechElement = getElement(mech); /* * printableName will be null if appName was in a byte[] or if * appName was in a String but appNameType was null. */ if (printableName == null) { printableName = mechElement.toString(); printableNameType = mechElement.getStringNameType(); } /* * At this point the GSSNameImpl has the following set: * appNameStr or appNameBytes * appNameType (could be null) * printableName * printableNameType * mechElement (which also exists in the hashmap of elements) */ } private void importName(GSSManagerImpl gssManager, Object appName) throws GSSException { int pos = 0; byte[] bytes = null; if (appName instanceof String) { try { bytes = ((String) appName).getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // Won't happen } } else bytes = (byte[]) appName; if ((bytes[pos++] != 0x04) || (bytes[pos++] != 0x01)) throw new GSSExceptionImpl(GSSException.BAD_NAME, "Exported name token id is corrupted!"); int oidLen = (((0xFF & bytes[pos++]) << 8) | (0xFF & bytes[pos++])); ObjectIdentifier temp = null; try { DerInputStream din = new DerInputStream(bytes, pos, oidLen); temp = new ObjectIdentifier(din); } catch (IOException e) { throw new GSSExceptionImpl(GSSException.BAD_NAME, "Exported name Object identifier is corrupted!"); } Oid oid = new Oid(temp.toString()); pos += oidLen; int mechPortionLen = (((0xFF & bytes[pos++]) << 24) | ((0xFF & bytes[pos++]) << 16) | ((0xFF & bytes[pos++]) << 8) | (0xFF & bytes[pos++])); byte[] mechPortion = new byte[mechPortionLen]; System.arraycopy(bytes, pos, mechPortion, 0, mechPortionLen); init(gssManager, mechPortion, NT_EXPORT_NAME, oid); } public GSSName canonicalize(Oid mech) throws GSSException { if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; return wrapElement(gssManager, getElement(mech)); } /** * This method may return false negatives. But if it says two * names are equals, then there is some mechanism that * authenticates them as the same principal. */ public boolean equals(GSSName other) throws GSSException { if (this.isAnonymous() || other.isAnonymous()) return false; if (other == this) return true; if (! (other instanceof GSSNameImpl)) return equals(gssManager.createName(other.toString(), other.getStringNameType())); /* * XXX Do a comparison of the appNameStr/appNameBytes if * available. If that fails, then proceed with this test. */ GSSNameImpl that = (GSSNameImpl) other; GSSNameSpi myElement = this.mechElement; GSSNameSpi element = that.mechElement; /* * XXX If they are not of the same mechanism type, convert both to * Kerberos since it is guaranteed to be present. */ if ((myElement == null) && (element != null)) { myElement = this.getElement(element.getMechanism()); } else if ((myElement != null) && (element == null)) { element = that.getElement(myElement.getMechanism()); } if (myElement != null && element != null) { return myElement.equals(element); } if ((this.appNameType != null) && (that.appNameType != null)) { if (!this.appNameType.equals(that.appNameType)) { return false; } byte[] myBytes = null; byte[] bytes = null; try { myBytes = (this.appNameStr != null ? this.appNameStr.getBytes("UTF-8") : this.appNameBytes); bytes = (that.appNameStr != null ? that.appNameStr.getBytes("UTF-8") : that.appNameBytes); } catch (UnsupportedEncodingException e) { // Won't happen } return Arrays.equals(myBytes, bytes); } return false; } /** * Returns a hashcode value for this GSSName. * * @return a hashCode value */ public int hashCode() { /* * XXX * In order to get this to work reliably and properly(!), obtain a * Kerberos name element for the name and then call hashCode on its * string representation. But this cannot be done if the nametype * is not one of those supported by the Kerberos provider and hence * this name cannot be imported by Kerberos. In that case return a * constant value! */ return 1; } public boolean equals(Object another) { try { // XXX This can lead to an infinite loop. Extract info // and create a GSSNameImpl with it. if (another instanceof GSSName) return equals((GSSName) another); } catch (GSSException e) { // Squelch it and return false } return false; } /** * Returns a flat name representation for this object. The name * format is defined in RFC 2743: *<pre> * Length Name Description * 2 TOK_ID Token Identifier * For exported name objects, this * must be hex 04 01. * 2 MECH_OID_LEN Length of the Mechanism OID * MECH_OID_LEN MECH_OID Mechanism OID, in DER * 4 NAME_LEN Length of name * NAME_LEN NAME Exported name; format defined in * applicable mechanism draft. *</pre> * * Note that it is not required to canonicalize a name before * calling export(). i.e., the name need not be an MN. If it is * not an MN, an implementation defined algorithm can be used for * choosing the mechanism which should export this name. * * @return the flat name representation for this object * @exception GSSException with major codes NAME_NOT_MN, BAD_NAME, * BAD_NAME, FAILURE. */ public byte[] export() throws GSSException { if (mechElement == null) { /* Use default mech */ mechElement = getElement(ProviderList.DEFAULT_MECH_OID); } byte[] mechPortion = mechElement.export(); byte[] oidBytes = null; ObjectIdentifier oid = null; try { oid = new ObjectIdentifier (mechElement.getMechanism().toString()); } catch (IOException e) { throw new GSSExceptionImpl(GSSException.FAILURE, "Invalid OID String "); } DerOutputStream dout = new DerOutputStream(); try { dout.putOID(oid); } catch (IOException e) { throw new GSSExceptionImpl(GSSException.FAILURE, "Could not ASN.1 Encode " + oid.toString()); } oidBytes = dout.toByteArray(); byte[] retVal = new byte[2 + 2 + oidBytes.length + 4 + mechPortion.length]; int pos = 0; retVal[pos++] = 0x04; retVal[pos++] = 0x01; retVal[pos++] = (byte) (oidBytes.length>>>8); retVal[pos++] = (byte) oidBytes.length; System.arraycopy(oidBytes, 0, retVal, pos, oidBytes.length); pos += oidBytes.length; retVal[pos++] = (byte) (mechPortion.length>>>24); retVal[pos++] = (byte) (mechPortion.length>>>16); retVal[pos++] = (byte) (mechPortion.length>>>8); retVal[pos++] = (byte) mechPortion.length; System.arraycopy(mechPortion, 0, retVal, pos, mechPortion.length); return retVal; } public String toString() { return printableName; } public Oid getStringNameType() throws GSSException { return printableNameType; } public boolean isAnonymous() { if (printableNameType == null) { return false; } else { return GSSName.NT_ANONYMOUS.equals(printableNameType); } } public boolean isMN() { return true; // Since always canonicalized for some mech } public synchronized GSSNameSpi getElement(Oid mechOid) throws GSSException { GSSNameSpi retVal = elements.get(mechOid); if (retVal == null) { if (appNameStr != null) { retVal = gssManager.getNameElement (appNameStr, appNameType, mechOid); } else { retVal = gssManager.getNameElement (appNameBytes, appNameType, mechOid); } elements.put(mechOid, retVal); } return retVal; } Set<GSSNameSpi> getElements() { return new HashSet<GSSNameSpi>(elements.values()); } private static String getNameTypeStr(Oid nameTypeOid) { if (nameTypeOid == null) return "(NT is null)"; if (nameTypeOid.equals(NT_USER_NAME)) return "NT_USER_NAME"; if (nameTypeOid.equals(NT_HOSTBASED_SERVICE)) return "NT_HOSTBASED_SERVICE"; if (nameTypeOid.equals(NT_EXPORT_NAME)) return "NT_EXPORT_NAME"; if (nameTypeOid.equals(GSSUtil.NT_GSS_KRB5_PRINCIPAL)) return "NT_GSS_KRB5_PRINCIPAL"; else return "Unknown"; } }