001: /*
002: * Copyright 2000-2006 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 sun.security.jgss;
027:
028: import org.ietf.jgss.*;
029: import sun.security.jgss.spi.*;
030: import java.util.Set;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.Arrays;
034: import java.io.IOException;
035: import java.io.UnsupportedEncodingException;
036: import sun.security.util.ObjectIdentifier;
037: import sun.security.util.DerInputStream;
038: import sun.security.util.DerOutputStream;
039:
040: /**
041: * This is the implementation class for GSSName. Conceptually the
042: * GSSName is a container with mechanism specific name elements. Each
043: * name element is a representation of how that particular mechanism
044: * would canonicalize this principal.
045: *
046: * Generally a GSSName is created by an application when it supplies
047: * a sequence of bytes and a nametype that helps each mechanism
048: * decide how to interpret those bytes.
049: *
050: * It is not necessary to create name elements for each available
051: * mechanism at the time the application creates the GSSName. This
052: * implementation does this lazily, as and when name elements for
053: * mechanisms are required to be handed out. (Generally, other GSS
054: * classes like GSSContext and GSSCredential request specific
055: * elements depending on the mechanisms that they are dealing with.)
056: * Assume that getting a mechanism to parse the applciation specified
057: * bytes is an expensive call.
058: *
059: * When a GSSName is canonicalized wrt some mechanism, it is supposed
060: * to discard all elements of other mechanisms and retain only the
061: * element for this mechanism. In GSS terminology this is called a
062: * Mechanism Name or MN. This implementation tries to retain the
063: * application provided bytes and name type just in case the MN is
064: * asked to produce an element for a mechanism that is different.
065: *
066: * When a GSSName is to be exported, the name element for the desired
067: * mechanism is converted to a byte representation and written
068: * out. It might happen that a name element for that mechanism cannot
069: * be obtained. This happens when the mechanism is just not supported
070: * in this GSS-API or when the mechanism is supported but bytes
071: * corresponding to the nametypes that it understands are not
072: * available in this GSSName.
073: *
074: * This class is safe for sharing. Each retrieval of a name element
075: * from getElement() might potentially add a new element to the
076: * hashmap of elements, but getElement() is synchronized.
077: *
078: * @author Mayank Upadhyay
079: * @version 1.24, 05/05/07
080: * @since 1.4
081: */
082:
083: public class GSSNameImpl implements GSSName {
084:
085: private GSSManagerImpl gssManager = null;
086:
087: /*
088: * Store whatever the application passed in. We will use this to
089: * get individual mechanisms to create name elements as and when
090: * needed.
091: * Store both the String and the byte[]. Leave I18N to the
092: * mechanism by allowing it to extract bytes from the String!
093: */
094:
095: private String appNameStr = null;
096: private byte[] appNameBytes = null;
097: private Oid appNameType = null;
098:
099: /*
100: * When we figure out what the printable name would be, we store
101: * both the name and its type.
102: */
103:
104: private String printableName = null;
105: private Oid printableNameType = null;
106:
107: private HashMap<Oid, GSSNameSpi> elements = null;
108: private GSSNameSpi mechElement = null;
109:
110: static GSSNameImpl wrapElement(GSSManagerImpl gssManager,
111: GSSNameSpi mechElement) throws GSSException {
112: return (mechElement == null ? null : new GSSNameImpl(
113: gssManager, mechElement));
114: }
115:
116: GSSNameImpl(GSSManagerImpl gssManager, GSSNameSpi mechElement) {
117: this .gssManager = gssManager;
118: appNameStr = printableName = mechElement.toString();
119: appNameType = printableNameType = mechElement
120: .getStringNameType();
121: this .mechElement = mechElement;
122: elements = new HashMap<Oid, GSSNameSpi>(1);
123: elements.put(mechElement.getMechanism(), this .mechElement);
124: }
125:
126: GSSNameImpl(GSSManagerImpl gssManager, Object appName,
127: Oid appNameType) throws GSSException {
128: this (gssManager, appName, appNameType, null);
129: }
130:
131: GSSNameImpl(GSSManagerImpl gssManager, Object appName,
132: Oid appNameType, Oid mech) throws GSSException {
133:
134: if (appName == null)
135: throw new GSSExceptionImpl(GSSException.BAD_NAME,
136: "Cannot import null name");
137: if (mech == null)
138: mech = ProviderList.DEFAULT_MECH_OID;
139: if (NT_EXPORT_NAME.equals(appNameType)) {
140: importName(gssManager, appName);
141: } else {
142: init(gssManager, appName, appNameType, mech);
143: }
144: }
145:
146: private void init(GSSManagerImpl gssManager, Object appName,
147: Oid appNameType, Oid mech) throws GSSException {
148:
149: this .gssManager = gssManager;
150: this .elements = new HashMap<Oid, GSSNameSpi>(gssManager
151: .getMechs().length);
152:
153: if (appName instanceof String) {
154: this .appNameStr = (String) appName;
155: /*
156: * If appNameType is null, then the nametype for this printable
157: * string is determined only by interrogating the
158: * mechanism. Thus, defer the setting of printableName and
159: * printableNameType till later.
160: */
161: if (appNameType != null) {
162: printableName = appNameStr;
163: printableNameType = appNameType;
164: }
165: } else {
166: this .appNameBytes = (byte[]) appName;
167: }
168:
169: this .appNameType = appNameType;
170:
171: mechElement = getElement(mech);
172:
173: /*
174: * printableName will be null if appName was in a byte[] or if
175: * appName was in a String but appNameType was null.
176: */
177: if (printableName == null) {
178: printableName = mechElement.toString();
179: printableNameType = mechElement.getStringNameType();
180: }
181:
182: /*
183: * At this point the GSSNameImpl has the following set:
184: * appNameStr or appNameBytes
185: * appNameType (could be null)
186: * printableName
187: * printableNameType
188: * mechElement (which also exists in the hashmap of elements)
189: */
190: }
191:
192: private void importName(GSSManagerImpl gssManager, Object appName)
193: throws GSSException {
194:
195: int pos = 0;
196: byte[] bytes = null;
197:
198: if (appName instanceof String) {
199: try {
200: bytes = ((String) appName).getBytes("UTF-8");
201: } catch (UnsupportedEncodingException e) {
202: // Won't happen
203: }
204: } else
205: bytes = (byte[]) appName;
206:
207: if ((bytes[pos++] != 0x04) || (bytes[pos++] != 0x01))
208: throw new GSSExceptionImpl(GSSException.BAD_NAME,
209: "Exported name token id is corrupted!");
210:
211: int oidLen = (((0xFF & bytes[pos++]) << 8) | (0xFF & bytes[pos++]));
212: ObjectIdentifier temp = null;
213: try {
214: DerInputStream din = new DerInputStream(bytes, pos, oidLen);
215: temp = new ObjectIdentifier(din);
216: } catch (IOException e) {
217: throw new GSSExceptionImpl(GSSException.BAD_NAME,
218: "Exported name Object identifier is corrupted!");
219: }
220: Oid oid = new Oid(temp.toString());
221: pos += oidLen;
222: int mechPortionLen = (((0xFF & bytes[pos++]) << 24)
223: | ((0xFF & bytes[pos++]) << 16)
224: | ((0xFF & bytes[pos++]) << 8) | (0xFF & bytes[pos++]));
225: byte[] mechPortion = new byte[mechPortionLen];
226: System.arraycopy(bytes, pos, mechPortion, 0, mechPortionLen);
227:
228: init(gssManager, mechPortion, NT_EXPORT_NAME, oid);
229: }
230:
231: public GSSName canonicalize(Oid mech) throws GSSException {
232: if (mech == null)
233: mech = ProviderList.DEFAULT_MECH_OID;
234:
235: return wrapElement(gssManager, getElement(mech));
236: }
237:
238: /**
239: * This method may return false negatives. But if it says two
240: * names are equals, then there is some mechanism that
241: * authenticates them as the same principal.
242: */
243: public boolean equals(GSSName other) throws GSSException {
244:
245: if (this .isAnonymous() || other.isAnonymous())
246: return false;
247:
248: if (other == this )
249: return true;
250:
251: if (!(other instanceof GSSNameImpl))
252: return equals(gssManager.createName(other.toString(), other
253: .getStringNameType()));
254:
255: /*
256: * XXX Do a comparison of the appNameStr/appNameBytes if
257: * available. If that fails, then proceed with this test.
258: */
259:
260: GSSNameImpl that = (GSSNameImpl) other;
261:
262: GSSNameSpi myElement = this .mechElement;
263: GSSNameSpi element = that.mechElement;
264:
265: /*
266: * XXX If they are not of the same mechanism type, convert both to
267: * Kerberos since it is guaranteed to be present.
268: */
269: if ((myElement == null) && (element != null)) {
270: myElement = this .getElement(element.getMechanism());
271: } else if ((myElement != null) && (element == null)) {
272: element = that.getElement(myElement.getMechanism());
273: }
274:
275: if (myElement != null && element != null) {
276: return myElement.equals(element);
277: }
278:
279: if ((this .appNameType != null) && (that.appNameType != null)) {
280: if (!this .appNameType.equals(that.appNameType)) {
281: return false;
282: }
283: byte[] myBytes = null;
284: byte[] bytes = null;
285: try {
286: myBytes = (this .appNameStr != null ? this .appNameStr
287: .getBytes("UTF-8") : this .appNameBytes);
288: bytes = (that.appNameStr != null ? that.appNameStr
289: .getBytes("UTF-8") : that.appNameBytes);
290: } catch (UnsupportedEncodingException e) {
291: // Won't happen
292: }
293:
294: return Arrays.equals(myBytes, bytes);
295: }
296:
297: return false;
298:
299: }
300:
301: /**
302: * Returns a hashcode value for this GSSName.
303: *
304: * @return a hashCode value
305: */
306: public int hashCode() {
307: /*
308: * XXX
309: * In order to get this to work reliably and properly(!), obtain a
310: * Kerberos name element for the name and then call hashCode on its
311: * string representation. But this cannot be done if the nametype
312: * is not one of those supported by the Kerberos provider and hence
313: * this name cannot be imported by Kerberos. In that case return a
314: * constant value!
315: */
316:
317: return 1;
318: }
319:
320: public boolean equals(Object another) {
321:
322: try {
323: // XXX This can lead to an infinite loop. Extract info
324: // and create a GSSNameImpl with it.
325:
326: if (another instanceof GSSName)
327: return equals((GSSName) another);
328: } catch (GSSException e) {
329: // Squelch it and return false
330: }
331:
332: return false;
333: }
334:
335: /**
336: * Returns a flat name representation for this object. The name
337: * format is defined in RFC 2743:
338: *<pre>
339: * Length Name Description
340: * 2 TOK_ID Token Identifier
341: * For exported name objects, this
342: * must be hex 04 01.
343: * 2 MECH_OID_LEN Length of the Mechanism OID
344: * MECH_OID_LEN MECH_OID Mechanism OID, in DER
345: * 4 NAME_LEN Length of name
346: * NAME_LEN NAME Exported name; format defined in
347: * applicable mechanism draft.
348: *</pre>
349: *
350: * Note that it is not required to canonicalize a name before
351: * calling export(). i.e., the name need not be an MN. If it is
352: * not an MN, an implementation defined algorithm can be used for
353: * choosing the mechanism which should export this name.
354: *
355: * @return the flat name representation for this object
356: * @exception GSSException with major codes NAME_NOT_MN, BAD_NAME,
357: * BAD_NAME, FAILURE.
358: */
359: public byte[] export() throws GSSException {
360:
361: if (mechElement == null) {
362: /* Use default mech */
363: mechElement = getElement(ProviderList.DEFAULT_MECH_OID);
364: }
365:
366: byte[] mechPortion = mechElement.export();
367: byte[] oidBytes = null;
368: ObjectIdentifier oid = null;
369:
370: try {
371: oid = new ObjectIdentifier(mechElement.getMechanism()
372: .toString());
373: } catch (IOException e) {
374: throw new GSSExceptionImpl(GSSException.FAILURE,
375: "Invalid OID String ");
376: }
377: DerOutputStream dout = new DerOutputStream();
378: try {
379: dout.putOID(oid);
380: } catch (IOException e) {
381: throw new GSSExceptionImpl(GSSException.FAILURE,
382: "Could not ASN.1 Encode " + oid.toString());
383: }
384: oidBytes = dout.toByteArray();
385:
386: byte[] retVal = new byte[2 + 2 + oidBytes.length + 4
387: + mechPortion.length];
388: int pos = 0;
389: retVal[pos++] = 0x04;
390: retVal[pos++] = 0x01;
391: retVal[pos++] = (byte) (oidBytes.length >>> 8);
392: retVal[pos++] = (byte) oidBytes.length;
393: System.arraycopy(oidBytes, 0, retVal, pos, oidBytes.length);
394: pos += oidBytes.length;
395: retVal[pos++] = (byte) (mechPortion.length >>> 24);
396: retVal[pos++] = (byte) (mechPortion.length >>> 16);
397: retVal[pos++] = (byte) (mechPortion.length >>> 8);
398: retVal[pos++] = (byte) mechPortion.length;
399: System.arraycopy(mechPortion, 0, retVal, pos,
400: mechPortion.length);
401: return retVal;
402: }
403:
404: public String toString() {
405: return printableName;
406:
407: }
408:
409: public Oid getStringNameType() throws GSSException {
410: return printableNameType;
411: }
412:
413: public boolean isAnonymous() {
414: if (printableNameType == null) {
415: return false;
416: } else {
417: return GSSName.NT_ANONYMOUS.equals(printableNameType);
418: }
419: }
420:
421: public boolean isMN() {
422: return true; // Since always canonicalized for some mech
423: }
424:
425: public synchronized GSSNameSpi getElement(Oid mechOid)
426: throws GSSException {
427:
428: GSSNameSpi retVal = elements.get(mechOid);
429:
430: if (retVal == null) {
431: if (appNameStr != null) {
432: retVal = gssManager.getNameElement(appNameStr,
433: appNameType, mechOid);
434: } else {
435: retVal = gssManager.getNameElement(appNameBytes,
436: appNameType, mechOid);
437: }
438: elements.put(mechOid, retVal);
439: }
440: return retVal;
441: }
442:
443: Set<GSSNameSpi> getElements() {
444: return new HashSet<GSSNameSpi>(elements.values());
445: }
446:
447: private static String getNameTypeStr(Oid nameTypeOid) {
448:
449: if (nameTypeOid == null)
450: return "(NT is null)";
451:
452: if (nameTypeOid.equals(NT_USER_NAME))
453: return "NT_USER_NAME";
454: if (nameTypeOid.equals(NT_HOSTBASED_SERVICE))
455: return "NT_HOSTBASED_SERVICE";
456: if (nameTypeOid.equals(NT_EXPORT_NAME))
457: return "NT_EXPORT_NAME";
458: if (nameTypeOid.equals(GSSUtil.NT_GSS_KRB5_PRINCIPAL))
459: return "NT_GSS_KRB5_PRINCIPAL";
460: else
461: return "Unknown";
462: }
463: }
|