001: /*
002: * Portions 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: /*
027: * @(#)PrincipalName.java 1.31 07/05/05
028: *
029: * (C) Copyright IBM Corp. 1999 All Rights Reserved.
030: * Copyright 1997 The Open Group Research Institute. All rights reserved.
031: */
032:
033: package sun.security.krb5;
034:
035: import sun.security.krb5.internal.*;
036: import sun.security.util.*;
037: import java.net.*;
038: import java.util.Vector;
039: import java.io.IOException;
040: import java.io.OutputStream;
041: import java.io.UnsupportedEncodingException;
042: import java.math.BigInteger;
043: import sun.security.krb5.internal.ccache.CCacheOutputStream;
044:
045: /**
046: * This class encapsulates a Kerberos principal.
047: */
048: public class PrincipalName implements Cloneable {
049:
050: //name types
051:
052: /**
053: * Name type not known
054: */
055: public static final int KRB_NT_UNKNOWN = 0;
056:
057: /**
058: * Just the name of the principal as in DCE, or for users
059: */
060: public static final int KRB_NT_PRINCIPAL = 1;
061:
062: /**
063: * Service and other unique instance (krbtgt)
064: */
065: public static final int KRB_NT_SRV_INST = 2;
066:
067: /**
068: * Service with host name as instance (telnet, rcommands)
069: */
070: public static final int KRB_NT_SRV_HST = 3;
071:
072: /**
073: * Service with host as remaining components
074: */
075: public static final int KRB_NT_SRV_XHST = 4;
076:
077: /**
078: * Unique ID
079: */
080: public static final int KRB_NT_UID = 5;
081:
082: /**
083: * TGS Name
084: */
085: public static final String TGS_DEFAULT_SRV_NAME = "krbtgt";
086: public static final int TGS_DEFAULT_NT = KRB_NT_SRV_INST;
087:
088: public static final char NAME_COMPONENT_SEPARATOR = '/';
089: public static final char NAME_REALM_SEPARATOR = '@';
090: public static final char REALM_COMPONENT_SEPARATOR = '.';
091:
092: public static final String NAME_COMPONENT_SEPARATOR_STR = "/";
093: public static final String NAME_REALM_SEPARATOR_STR = "@";
094: public static final String REALM_COMPONENT_SEPARATOR_STR = ".";
095:
096: private int nameType;
097: private String[] nameStrings; // Principal names don't mutate often
098:
099: private Realm nameRealm; // optional; a null realm means use default
100: // Note: the nameRealm is not included in the default ASN.1 encoding
101:
102: // salt for principal
103: private String salt = null;
104:
105: protected PrincipalName() {
106: }
107:
108: public PrincipalName(String[] nameParts, int type)
109: throws IllegalArgumentException, IOException {
110: if (nameParts == null) {
111: throw new IllegalArgumentException("Null input not allowed");
112: }
113: nameStrings = new String[nameParts.length];
114: System
115: .arraycopy(nameParts, 0, nameStrings, 0,
116: nameParts.length);
117: nameType = type;
118: nameRealm = null;
119: }
120:
121: public PrincipalName(String[] nameParts) throws IOException {
122: this (nameParts, KRB_NT_UNKNOWN);
123: }
124:
125: public Object clone() {
126: PrincipalName pName = new PrincipalName();
127: pName.nameType = nameType;
128: if (nameStrings != null) {
129: pName.nameStrings = new String[nameStrings.length];
130: System.arraycopy(nameStrings, 0, pName.nameStrings, 0,
131: nameStrings.length);
132: }
133: if (nameRealm != null) {
134: pName.nameRealm = (Realm) nameRealm.clone();
135: }
136: return pName;
137: }
138:
139: /*
140: * Added to workaround a bug where the equals method that takes a
141: * PrincipalName is not being called but Object.equals(Object) is
142: * being called.
143: */
144: public boolean equals(Object o) {
145: if (o instanceof PrincipalName)
146: return equals((PrincipalName) o);
147: else
148: return false;
149: }
150:
151: public boolean equals(PrincipalName other) {
152:
153: if (!equalsWithoutRealm(other)) {
154: return false;
155: }
156:
157: if ((nameRealm != null && other.nameRealm == null)
158: || (nameRealm == null && other.nameRealm != null)) {
159: return false;
160: }
161:
162: if (nameRealm != null && other.nameRealm != null) {
163: if (!nameRealm.equals(other.nameRealm)) {
164: return false;
165: }
166: }
167:
168: return true;
169: }
170:
171: boolean equalsWithoutRealm(PrincipalName other) {
172:
173: if (nameType != KRB_NT_UNKNOWN
174: && other.nameType != KRB_NT_UNKNOWN
175: && nameType != other.nameType)
176: return false;
177:
178: if ((nameStrings != null && other.nameStrings == null)
179: || (nameStrings == null && other.nameStrings != null))
180: return false;
181:
182: if (nameStrings != null && other.nameStrings != null) {
183: if (nameStrings.length != other.nameStrings.length)
184: return false;
185: for (int i = 0; i < nameStrings.length; i++)
186: if (!nameStrings[i].equals(other.nameStrings[i]))
187: return false;
188: }
189:
190: return true;
191:
192: }
193:
194: /**
195: * Returns the ASN.1 encoding of the
196: * <xmp>
197: * PrincipalName ::= SEQUENCE {
198: * name-type [0] Int32,
199: * name-string [1] SEQUENCE OF KerberosString
200: * }
201: *
202: * KerberosString ::= GeneralString (IA5String)
203: * </xmp>
204: *
205: * <p>
206: * This definition reflects the Network Working Group RFC 4120
207: * specification available at
208: * <a href="http://www.ietf.org/rfc/rfc4120.txt">
209: * http://www.ietf.org/rfc/rfc4120.txt</a>.
210: *
211: * @param encoding a Der-encoded data.
212: * @exception Asn1Exception if an error occurs while decoding
213: * an ASN1 encoded data.
214: * @exception Asn1Exception if there is an ASN1 encoding error
215: * @exception IOException if an I/O error occurs
216: * @exception IllegalArgumentException if encoding is null
217: * reading encoded data.
218: *
219: */
220: public PrincipalName(DerValue encoding) throws Asn1Exception,
221: IOException {
222: nameRealm = null;
223: DerValue der;
224: if (encoding == null) {
225: throw new IllegalArgumentException("Null input not allowed");
226: }
227: if (encoding.getTag() != DerValue.tag_Sequence) {
228: throw new Asn1Exception(Krb5.ASN1_BAD_ID);
229: }
230: der = encoding.getData().getDerValue();
231: if ((der.getTag() & 0x1F) == 0x00) {
232: BigInteger bint = der.getData().getBigInteger();
233: nameType = bint.intValue();
234: } else {
235: throw new Asn1Exception(Krb5.ASN1_BAD_ID);
236: }
237: der = encoding.getData().getDerValue();
238: if ((der.getTag() & 0x01F) == 0x01) {
239: DerValue subDer = der.getData().getDerValue();
240: if (subDer.getTag() != DerValue.tag_SequenceOf) {
241: throw new Asn1Exception(Krb5.ASN1_BAD_ID);
242: }
243: Vector<String> v = new Vector<String>();
244: DerValue subSubDer;
245: while (subDer.getData().available() > 0) {
246: subSubDer = subDer.getData().getDerValue();
247: v.addElement(subSubDer.getGeneralString());
248: }
249: if (v.size() > 0) {
250: nameStrings = new String[v.size()];
251: v.copyInto(nameStrings);
252: } else {
253: nameStrings = new String[] { "" };
254: }
255: } else {
256: throw new Asn1Exception(Krb5.ASN1_BAD_ID);
257: }
258: }
259:
260: /**
261: * Parse (unmarshal) a <code>PrincipalName</code> from a DER
262: * input stream. This form
263: * parsing might be used when expanding a value which is part of
264: * a constructed sequence and uses explicitly tagged type.
265: *
266: * @exception Asn1Exception on error.
267: * @param data the Der input stream value, which contains one or
268: * more marshaled value.
269: * @param explicitTag tag number.
270: * @param optional indicate if this data field is optional
271: * @return an instance of <code>PrincipalName</code>.
272: *
273: */
274: public static PrincipalName parse(DerInputStream data,
275: byte explicitTag, boolean optional) throws Asn1Exception,
276: IOException {
277:
278: if ((optional)
279: && (((byte) data.peekByte() & (byte) 0x1F) != explicitTag))
280: return null;
281: DerValue der = data.getDerValue();
282: if (explicitTag != (der.getTag() & (byte) 0x1F))
283: throw new Asn1Exception(Krb5.ASN1_BAD_ID);
284: else {
285: DerValue subDer = der.getData().getDerValue();
286: return new PrincipalName(subDer);
287: }
288: }
289:
290: // This is protected because the definition of a principal
291: // string is fixed
292: // XXX Error checkin consistent with MIT krb5_parse_name
293: // Code repetition, realm parsed again by class Realm
294: protected static String[] parseName(String name) {
295:
296: Vector<String> tempStrings = new Vector<String>();
297: String temp = name;
298: int i = 0;
299: int componentStart = 0;
300: String component;
301:
302: while (i < temp.length()) {
303: if (temp.charAt(i) == NAME_COMPONENT_SEPARATOR) {
304: /*
305: * If this separator is escaped then don't treat it
306: * as a separator
307: */
308: if (i > 0 && temp.charAt(i - 1) == '\\') {
309: temp = temp.substring(0, i - 1)
310: + temp.substring(i, temp.length());
311: continue;
312: } else {
313: if (componentStart < i) {
314: component = temp.substring(componentStart, i);
315: tempStrings.addElement(component);
316: }
317: componentStart = i + 1;
318: }
319: } else if (temp.charAt(i) == NAME_REALM_SEPARATOR) {
320: /*
321: * If this separator is escaped then don't treat it
322: * as a separator
323: */
324: if (i > 0 && temp.charAt(i - 1) == '\\') {
325: temp = temp.substring(0, i - 1)
326: + temp.substring(i, temp.length());
327: continue;
328: } else {
329: if (componentStart < i) {
330: component = temp.substring(componentStart, i);
331: tempStrings.addElement(component);
332: }
333: componentStart = i + 1;
334: break;
335: }
336: }
337: i++;
338: }
339:
340: if (i == temp.length())
341: if (componentStart < i) {
342: component = temp.substring(componentStart, i);
343: tempStrings.addElement(component);
344: }
345:
346: String[] result = new String[tempStrings.size()];
347: tempStrings.copyInto(result);
348: return result;
349: }
350:
351: public PrincipalName(String name, int type) throws RealmException {
352: if (name == null) {
353: throw new IllegalArgumentException("Null name not allowed");
354: }
355: String[] nameParts = parseName(name);
356: Realm tempRealm = null;
357: String realmString = Realm.parseRealmAtSeparator(name);
358:
359: if (realmString == null) {
360: try {
361: Config config = Config.getInstance();
362: realmString = config.getDefaultRealm();
363: } catch (KrbException e) {
364: RealmException re = new RealmException(e.getMessage());
365: re.initCause(e);
366: throw re;
367: }
368: }
369:
370: if (realmString != null)
371: tempRealm = new Realm(realmString);
372:
373: switch (type) {
374: case KRB_NT_SRV_HST:
375: if (nameParts.length >= 2) {
376: try {
377: // Canonicalize the hostname as per the
378: // RFC4120 Section 6.2.1 and
379: // RFC1964 Section 2.1.2
380: // we assume internet domain names
381: String hostName = (InetAddress
382: .getByName(nameParts[1]))
383: .getCanonicalHostName();
384: nameParts[1] = hostName.toLowerCase();
385: } catch (UnknownHostException e) {
386: // no canonicalization, just convert to lowercase
387: nameParts[1] = nameParts[1].toLowerCase();
388: }
389: }
390: nameStrings = nameParts;
391: nameType = type;
392: // We will try to get realm name from the mapping in
393: // the configuration. If it is not specified
394: // we will use the default realm. This nametype does
395: // not allow a realm to be specified. The name string must of
396: // the form service@host and this is internally changed into
397: // service/host by Kerberos
398:
399: String mapRealm = mapHostToRealm(nameParts[1]);
400: if (mapRealm != null) {
401: nameRealm = new Realm(mapRealm);
402: } else {
403: nameRealm = tempRealm;
404: }
405: break;
406: case KRB_NT_UNKNOWN:
407: case KRB_NT_PRINCIPAL:
408: case KRB_NT_SRV_INST:
409: case KRB_NT_SRV_XHST:
410: case KRB_NT_UID:
411: nameStrings = nameParts;
412: nameType = type;
413: nameRealm = tempRealm;
414: break;
415: default:
416: throw new IllegalArgumentException("Illegal name type");
417: }
418: }
419:
420: public PrincipalName(String name) throws RealmException {
421: this (name, KRB_NT_UNKNOWN);
422: }
423:
424: public PrincipalName(String name, String realm)
425: throws RealmException {
426: this (name, KRB_NT_UNKNOWN);
427: nameRealm = new Realm(realm);
428: }
429:
430: public String getRealmAsString() {
431: return getRealmString();
432: }
433:
434: public String getPrincipalNameAsString() {
435: StringBuffer temp = new StringBuffer(nameStrings[0]);
436: for (int i = 1; i < nameStrings.length; i++)
437: temp.append(nameStrings[i]);
438: return temp.toString();
439: }
440:
441: public int hashCode() {
442: return toString().hashCode();
443: }
444:
445: public String getName() {
446: return toString();
447: }
448:
449: public int getNameType() {
450: return nameType;
451: }
452:
453: public String[] getNameStrings() {
454: return nameStrings;
455: }
456:
457: public byte[][] toByteArray() {
458: byte[][] result = new byte[nameStrings.length][];
459: for (int i = 0; i < nameStrings.length; i++) {
460: result[i] = new byte[nameStrings[i].length()];
461: result[i] = nameStrings[i].getBytes();
462: }
463: return result;
464: }
465:
466: public String getRealmString() {
467: if (nameRealm != null)
468: return nameRealm.toString();
469: return null;
470: }
471:
472: public Realm getRealm() {
473: return nameRealm;
474: }
475:
476: public void setRealm(Realm new_nameRealm) throws RealmException {
477: nameRealm = new_nameRealm;
478: }
479:
480: public void setRealm(String realmsString) throws RealmException {
481: nameRealm = new Realm(realmsString);
482: }
483:
484: public String getSalt() {
485: if (salt == null) {
486: StringBuffer salt = new StringBuffer();
487: if (nameRealm != null) {
488: salt.append(nameRealm.toString());
489: }
490: for (int i = 0; i < nameStrings.length; i++) {
491: salt.append(nameStrings[i]);
492: }
493: return salt.toString();
494: }
495: return salt;
496: }
497:
498: public void setSalt(String salt) {
499: this .salt = salt;
500: }
501:
502: public String toString() {
503: StringBuffer str = new StringBuffer();
504: for (int i = 0; i < nameStrings.length; i++) {
505: if (i > 0)
506: str.append("/");
507: str.append(nameStrings[i]);
508: }
509: if (nameRealm != null) {
510: str.append("@");
511: str.append(nameRealm.toString());
512: }
513:
514: return str.toString();
515: }
516:
517: public String getNameString() {
518: StringBuffer str = new StringBuffer();
519: for (int i = 0; i < nameStrings.length; i++) {
520: if (i > 0)
521: str.append("/");
522: str.append(nameStrings[i]);
523: }
524: return str.toString();
525: }
526:
527: /**
528: * Encodes a <code>PrincipalName</code> object.
529: * @return the byte array of the encoded PrncipalName object.
530: * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
531: * @exception IOException if an I/O error occurs while reading encoded data.
532: *
533: */
534: public byte[] asn1Encode() throws Asn1Exception, IOException {
535: DerOutputStream bytes = new DerOutputStream();
536: DerOutputStream temp = new DerOutputStream();
537: BigInteger bint = BigInteger.valueOf(this .nameType);
538: temp.putInteger(bint);
539: bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,
540: (byte) 0x00), temp);
541: temp = new DerOutputStream();
542: DerValue der[] = new DerValue[nameStrings.length];
543: for (int i = 0; i < nameStrings.length; i++) {
544: der[i] = new DerValue(DerValue.tag_GeneralString,
545: nameStrings[i]);
546: }
547: temp.putSequence(der);
548: bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,
549: (byte) 0x01), temp);
550: temp = new DerOutputStream();
551: temp.write(DerValue.tag_Sequence, bytes);
552: return temp.toByteArray();
553: }
554:
555: /**
556: * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields.
557: *
558: * @param pname the other <code>PrincipalName</code> object.
559: * @return true if two have identical values, otherwise, return false.
560: */
561: // It is used in <code>sun.security.krb5.internal.ccache</code> package.
562: public boolean match(PrincipalName pname) {
563: boolean matched = true;
564: //name type is just a hint, no two names can be the same ignoring name type.
565: // if (this.nameType != pname.nameType) {
566: // matched = false;
567: // }
568: if ((this .nameRealm != null) && (pname.nameRealm != null)) {
569: if (!(this .nameRealm.toString()
570: .equalsIgnoreCase(pname.nameRealm.toString()))) {
571: matched = false;
572: }
573: }
574: if (this .nameStrings.length != pname.nameStrings.length) {
575: matched = false;
576: } else {
577: for (int i = 0; i < this .nameStrings.length; i++) {
578: if (!(this .nameStrings[i]
579: .equalsIgnoreCase(pname.nameStrings[i]))) {
580: matched = false;
581: }
582: }
583: }
584: return matched;
585: }
586:
587: /**
588: * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream.
589: *
590: * @param cos a <code>CCacheOutputStream</code> for writing data.
591: * @exception IOException if an I/O exception occurs.
592: * @see sun.security.krb5.internal.ccache.CCacheOutputStream
593: */
594: public void writePrincipal(CCacheOutputStream cos)
595: throws IOException {
596: cos.write32(nameType);
597: cos.write32(nameStrings.length);
598: if (nameRealm != null) {
599: byte[] realmBytes = null;
600: realmBytes = nameRealm.toString().getBytes();
601: cos.write32(realmBytes.length);
602: cos.write(realmBytes, 0, realmBytes.length);
603: }
604: byte[] bytes = null;
605: for (int i = 0; i < nameStrings.length; i++) {
606: bytes = nameStrings[i].getBytes();
607: cos.write32(bytes.length);
608: cos.write(bytes, 0, bytes.length);
609: }
610: }
611:
612: /**
613: * Creates a KRB_NT_SRV_INST name from the supplied
614: * name components and realm.
615: * @param primary the primary component of the name
616: * @param instance the instance component of the name
617: * @param realm the realm
618: * @throws KrbException
619: */
620: protected PrincipalName(String primary, String instance,
621: String realm, int type) throws KrbException {
622:
623: if (type != KRB_NT_SRV_INST) {
624: throw new KrbException(Krb5.KRB_ERR_GENERIC,
625: "Bad name type");
626: }
627:
628: String[] nParts = new String[2];
629: nParts[0] = primary;
630: nParts[1] = instance;
631:
632: this .nameStrings = nParts;
633: this .nameRealm = new Realm(realm);
634: this .nameType = type;
635: }
636:
637: /**
638: * Returns the instance component of a name.
639: * In a multi-component name such as a KRB_NT_SRV_INST
640: * name, the second component is returned.
641: * Null is returned if there are not two or more
642: * components in the name.
643: * @returns instance component of a multi-component name.
644: */
645: public String getInstanceComponent() {
646: if (nameStrings != null && nameStrings.length >= 2) {
647: return new String(nameStrings[1]);
648: }
649:
650: return null;
651: }
652:
653: static String mapHostToRealm(String name) {
654: String result = null;
655: try {
656: String subname = null;
657: Config c = Config.getInstance();
658: if ((result = c.getDefault(name, "domain_realm")) != null)
659: return result;
660: else {
661: for (int i = 1; i < name.length(); i++) {
662: if ((name.charAt(i) == '.')
663: && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM
664: subname = name.substring(i);
665: result = c.getDefault(subname, "domain_realm");
666: if (result != null) {
667: break;
668: } else {
669: subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM
670: result = c.getDefault(subname,
671: "domain_realm");
672: if (result != null) {
673: break;
674: }
675: }
676: }
677: }
678: }
679: } catch (KrbException e) {
680: }
681: return result;
682: }
683:
684: }
|