| /* |
| * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * |
| * (C) Copyright IBM Corp. 1999 All Rights Reserved. |
| * Copyright 1997 The Open Group Research Institute. All rights reserved. |
| */ |
| |
| package sun.security.krb5; |
| |
| import sun.security.krb5.internal.*; |
| import sun.security.util.*; |
| import java.net.*; |
| import java.util.Vector; |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import sun.security.krb5.internal.ccache.CCacheOutputStream; |
| import sun.security.krb5.internal.util.KerberosString; |
| |
| |
| /** |
| * This class encapsulates a Kerberos principal. |
| */ |
| public class PrincipalName |
| implements Cloneable { |
| |
| //name types |
| |
| /** |
| * Name type not known |
| */ |
| public static final int KRB_NT_UNKNOWN = 0; |
| |
| /** |
| * Just the name of the principal as in DCE, or for users |
| */ |
| public static final int KRB_NT_PRINCIPAL = 1; |
| |
| /** |
| * Service and other unique instance (krbtgt) |
| */ |
| public static final int KRB_NT_SRV_INST = 2; |
| |
| /** |
| * Service with host name as instance (telnet, rcommands) |
| */ |
| public static final int KRB_NT_SRV_HST = 3; |
| |
| /** |
| * Service with host as remaining components |
| */ |
| public static final int KRB_NT_SRV_XHST = 4; |
| |
| /** |
| * Unique ID |
| */ |
| public static final int KRB_NT_UID = 5; |
| |
| |
| |
| /** |
| * TGS Name |
| */ |
| public static final String TGS_DEFAULT_SRV_NAME = "krbtgt"; |
| public static final int TGS_DEFAULT_NT = KRB_NT_SRV_INST; |
| |
| public static final char NAME_COMPONENT_SEPARATOR = '/'; |
| public static final char NAME_REALM_SEPARATOR = '@'; |
| public static final char REALM_COMPONENT_SEPARATOR = '.'; |
| |
| public static final String NAME_COMPONENT_SEPARATOR_STR = "/"; |
| public static final String NAME_REALM_SEPARATOR_STR = "@"; |
| public static final String REALM_COMPONENT_SEPARATOR_STR = "."; |
| |
| private int nameType; |
| private String[] nameStrings; // Principal names don't mutate often |
| |
| private Realm nameRealm; // optional; a null realm means use default |
| // Note: the nameRealm is not included in the default ASN.1 encoding |
| |
| // cached salt, might be changed by KDC info, not used in clone |
| private String salt = null; |
| |
| protected PrincipalName() { |
| } |
| |
| public PrincipalName(String[] nameParts, int type) |
| throws IllegalArgumentException, IOException { |
| if (nameParts == null) { |
| throw new IllegalArgumentException("Null input not allowed"); |
| } |
| nameStrings = new String[nameParts.length]; |
| System.arraycopy(nameParts, 0, nameStrings, 0, nameParts.length); |
| nameType = type; |
| nameRealm = null; |
| } |
| |
| public PrincipalName(String[] nameParts) throws IOException { |
| this(nameParts, KRB_NT_UNKNOWN); |
| } |
| |
| public Object clone() { |
| try { |
| PrincipalName pName = (PrincipalName) super.clone(); |
| // Re-assign mutable fields |
| if (nameStrings != null) { |
| pName.nameStrings = nameStrings.clone(); |
| } |
| if (nameRealm != null) { |
| pName.nameRealm = (Realm)nameRealm.clone(); |
| } |
| return pName; |
| } catch (CloneNotSupportedException ex) { |
| throw new AssertionError("Should never happen"); |
| } |
| } |
| |
| /* |
| * Added to workaround a bug where the equals method that takes a |
| * PrincipalName is not being called but Object.equals(Object) is |
| * being called. |
| */ |
| public boolean equals(Object o) { |
| if (o instanceof PrincipalName) |
| return equals((PrincipalName)o); |
| else |
| return false; |
| } |
| |
| public boolean equals(PrincipalName other) { |
| |
| |
| if (!equalsWithoutRealm(other)) { |
| return false; |
| } |
| |
| if ((nameRealm != null && other.nameRealm == null) || |
| (nameRealm == null && other.nameRealm != null)) { |
| return false; |
| } |
| |
| if (nameRealm != null && other.nameRealm != null) { |
| if (!nameRealm.equals(other.nameRealm)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| boolean equalsWithoutRealm(PrincipalName other) { |
| |
| |
| if (nameType != KRB_NT_UNKNOWN && |
| other.nameType != KRB_NT_UNKNOWN && |
| nameType != other.nameType) |
| return false; |
| |
| if ((nameStrings != null && other.nameStrings == null) || |
| (nameStrings == null && other.nameStrings != null)) |
| return false; |
| |
| if (nameStrings != null && other.nameStrings != null) { |
| if (nameStrings.length != other.nameStrings.length) |
| return false; |
| for (int i = 0; i < nameStrings.length; i++) |
| if (!nameStrings[i].equals(other.nameStrings[i])) |
| return false; |
| } |
| |
| return true; |
| |
| } |
| |
| /** |
| * Returns the ASN.1 encoding of the |
| * <xmp> |
| * PrincipalName ::= SEQUENCE { |
| * name-type [0] Int32, |
| * name-string [1] SEQUENCE OF KerberosString |
| * } |
| * |
| * KerberosString ::= GeneralString (IA5String) |
| * </xmp> |
| * |
| * <p> |
| * This definition reflects the Network Working Group RFC 4120 |
| * specification available at |
| * <a href="http://www.ietf.org/rfc/rfc4120.txt"> |
| * http://www.ietf.org/rfc/rfc4120.txt</a>. |
| * |
| * @param encoding a Der-encoded data. |
| * @exception Asn1Exception if an error occurs while decoding |
| * an ASN1 encoded data. |
| * @exception Asn1Exception if there is an ASN1 encoding error |
| * @exception IOException if an I/O error occurs |
| * @exception IllegalArgumentException if encoding is null |
| * reading encoded data. |
| * |
| */ |
| public PrincipalName(DerValue encoding) |
| throws Asn1Exception, IOException { |
| nameRealm = null; |
| DerValue der; |
| if (encoding == null) { |
| throw new IllegalArgumentException("Null input not allowed"); |
| } |
| if (encoding.getTag() != DerValue.tag_Sequence) { |
| throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
| } |
| der = encoding.getData().getDerValue(); |
| if ((der.getTag() & 0x1F) == 0x00) { |
| BigInteger bint = der.getData().getBigInteger(); |
| nameType = bint.intValue(); |
| } else { |
| throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
| } |
| der = encoding.getData().getDerValue(); |
| if ((der.getTag() & 0x01F) == 0x01) { |
| DerValue subDer = der.getData().getDerValue(); |
| if (subDer.getTag() != DerValue.tag_SequenceOf) { |
| throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
| } |
| Vector<String> v = new Vector<>(); |
| DerValue subSubDer; |
| while(subDer.getData().available() > 0) { |
| subSubDer = subDer.getData().getDerValue(); |
| v.addElement(new KerberosString(subSubDer).toString()); |
| } |
| if (v.size() > 0) { |
| nameStrings = new String[v.size()]; |
| v.copyInto(nameStrings); |
| } else { |
| nameStrings = new String[] {""}; |
| } |
| } else { |
| throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
| } |
| } |
| |
| /** |
| * Parse (unmarshal) a <code>PrincipalName</code> from a DER |
| * input stream. This form |
| * parsing might be used when expanding a value which is part of |
| * a constructed sequence and uses explicitly tagged type. |
| * |
| * @exception Asn1Exception on error. |
| * @param data the Der input stream value, which contains one or |
| * more marshaled value. |
| * @param explicitTag tag number. |
| * @param optional indicate if this data field is optional |
| * @return an instance of <code>PrincipalName</code>. |
| * |
| */ |
| public static PrincipalName parse(DerInputStream data, |
| byte explicitTag, boolean |
| optional) |
| throws Asn1Exception, IOException { |
| |
| if ((optional) && (((byte)data.peekByte() & (byte)0x1F) != |
| explicitTag)) |
| return null; |
| DerValue der = data.getDerValue(); |
| if (explicitTag != (der.getTag() & (byte)0x1F)) |
| throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
| else { |
| DerValue subDer = der.getData().getDerValue(); |
| return new PrincipalName(subDer); |
| } |
| } |
| |
| |
| // This is protected because the definition of a principal |
| // string is fixed |
| // XXX Error checkin consistent with MIT krb5_parse_name |
| // Code repetition, realm parsed again by class Realm |
| protected static String[] parseName(String name) { |
| |
| Vector<String> tempStrings = new Vector<>(); |
| String temp = name; |
| int i = 0; |
| int componentStart = 0; |
| String component; |
| |
| while (i < temp.length()) { |
| if (temp.charAt(i) == NAME_COMPONENT_SEPARATOR) { |
| /* |
| * If this separator is escaped then don't treat it |
| * as a separator |
| */ |
| if (i > 0 && temp.charAt(i - 1) == '\\') { |
| temp = temp.substring(0, i - 1) + |
| temp.substring(i, temp.length()); |
| continue; |
| } |
| else { |
| if (componentStart < i) { |
| component = temp.substring(componentStart, i); |
| tempStrings.addElement(component); |
| } |
| componentStart = i + 1; |
| } |
| } else |
| if (temp.charAt(i) == NAME_REALM_SEPARATOR) { |
| /* |
| * If this separator is escaped then don't treat it |
| * as a separator |
| */ |
| if (i > 0 && temp.charAt(i - 1) == '\\') { |
| temp = temp.substring(0, i - 1) + |
| temp.substring(i, temp.length()); |
| continue; |
| } else { |
| if (componentStart < i) { |
| component = temp.substring(componentStart, i); |
| tempStrings.addElement(component); |
| } |
| componentStart = i + 1; |
| break; |
| } |
| } |
| i++; |
| } |
| |
| if (i == temp.length()) |
| if (componentStart < i) { |
| component = temp.substring(componentStart, i); |
| tempStrings.addElement(component); |
| } |
| |
| String[] result = new String[tempStrings.size()]; |
| tempStrings.copyInto(result); |
| return result; |
| } |
| |
| public PrincipalName(String name, int type) |
| throws RealmException { |
| if (name == null) { |
| throw new IllegalArgumentException("Null name not allowed"); |
| } |
| String[] nameParts = parseName(name); |
| Realm tempRealm = null; |
| String realmString = Realm.parseRealmAtSeparator(name); |
| |
| if (realmString == null) { |
| try { |
| Config config = Config.getInstance(); |
| realmString = config.getDefaultRealm(); |
| } catch (KrbException e) { |
| RealmException re = |
| new RealmException(e.getMessage()); |
| re.initCause(e); |
| throw re; |
| } |
| } |
| |
| if (realmString != null) |
| tempRealm = new Realm(realmString); |
| |
| switch (type) { |
| case KRB_NT_SRV_HST: |
| if (nameParts.length >= 2) { |
| String hostName = nameParts[1]; |
| try { |
| // RFC4120 does not recommend canonicalizing a hostname. |
| // However, for compatibility reason, we will try |
| // canonicalize it and see if the output looks better. |
| |
| String canonicalized = (InetAddress.getByName(hostName)). |
| getCanonicalHostName(); |
| |
| // Looks if canonicalized is a longer format of hostName, |
| // we accept cases like |
| // bunny -> bunny.rabbit.hole |
| if (canonicalized.toLowerCase() |
| .startsWith(hostName.toLowerCase()+".")) { |
| hostName = canonicalized; |
| } |
| } catch (UnknownHostException e) { |
| // no canonicalization, use old |
| } |
| nameParts[1] = hostName.toLowerCase(); |
| } |
| nameStrings = nameParts; |
| nameType = type; |
| // We will try to get realm name from the mapping in |
| // the configuration. If it is not specified |
| // we will use the default realm. This nametype does |
| // not allow a realm to be specified. The name string must of |
| // the form service@host and this is internally changed into |
| // service/host by Kerberos |
| |
| String mapRealm = mapHostToRealm(nameParts[1]); |
| if (mapRealm != null) { |
| nameRealm = new Realm(mapRealm); |
| } else { |
| nameRealm = tempRealm; |
| } |
| break; |
| case KRB_NT_UNKNOWN: |
| case KRB_NT_PRINCIPAL: |
| case KRB_NT_SRV_INST: |
| case KRB_NT_SRV_XHST: |
| case KRB_NT_UID: |
| nameStrings = nameParts; |
| nameType = type; |
| nameRealm = tempRealm; |
| break; |
| default: |
| throw new IllegalArgumentException("Illegal name type"); |
| } |
| } |
| |
| public PrincipalName(String name) throws RealmException { |
| this(name, KRB_NT_UNKNOWN); |
| } |
| |
| public PrincipalName(String name, String realm) throws RealmException { |
| this(name, KRB_NT_UNKNOWN); |
| nameRealm = new Realm(realm); |
| } |
| |
| public String getRealmAsString() { |
| return getRealmString(); |
| } |
| |
| public String getPrincipalNameAsString() { |
| StringBuffer temp = new StringBuffer(nameStrings[0]); |
| for (int i = 1; i < nameStrings.length; i++) |
| temp.append(nameStrings[i]); |
| return temp.toString(); |
| } |
| |
| public int hashCode() { |
| return toString().hashCode(); |
| } |
| |
| public String getName() { |
| return toString(); |
| } |
| |
| public int getNameType() { |
| return nameType; |
| } |
| |
| public String[] getNameStrings() { |
| return nameStrings; |
| } |
| |
| public byte[][] toByteArray() { |
| byte[][] result = new byte[nameStrings.length][]; |
| for (int i = 0; i < nameStrings.length; i++) { |
| result[i] = new byte[nameStrings[i].length()]; |
| result[i] = nameStrings[i].getBytes(); |
| } |
| return result; |
| } |
| |
| public String getRealmString() { |
| if (nameRealm != null) |
| return nameRealm.toString(); |
| return null; |
| } |
| |
| public Realm getRealm() { |
| return nameRealm; |
| } |
| |
| public void setRealm(Realm new_nameRealm) throws RealmException { |
| nameRealm = new_nameRealm; |
| } |
| |
| public void setRealm(String realmsString) throws RealmException { |
| nameRealm = new Realm(realmsString); |
| } |
| |
| public String getSalt() { |
| if (salt == null) { |
| StringBuffer salt = new StringBuffer(); |
| if (nameRealm != null) { |
| salt.append(nameRealm.toString()); |
| } |
| for (int i = 0; i < nameStrings.length; i++) { |
| salt.append(nameStrings[i]); |
| } |
| return salt.toString(); |
| } |
| return salt; |
| } |
| |
| public String toString() { |
| StringBuffer str = new StringBuffer(); |
| for (int i = 0; i < nameStrings.length; i++) { |
| if (i > 0) |
| str.append("/"); |
| str.append(nameStrings[i]); |
| } |
| if (nameRealm != null) { |
| str.append("@"); |
| str.append(nameRealm.toString()); |
| } |
| |
| return str.toString(); |
| } |
| |
| public String getNameString() { |
| StringBuffer str = new StringBuffer(); |
| for (int i = 0; i < nameStrings.length; i++) { |
| if (i > 0) |
| str.append("/"); |
| str.append(nameStrings[i]); |
| } |
| return str.toString(); |
| } |
| |
| /** |
| * Encodes a <code>PrincipalName</code> object. |
| * @return the byte array of the encoded PrncipalName object. |
| * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. |
| * @exception IOException if an I/O error occurs while reading encoded data. |
| * |
| */ |
| public byte[] asn1Encode() throws Asn1Exception, IOException { |
| DerOutputStream bytes = new DerOutputStream(); |
| DerOutputStream temp = new DerOutputStream(); |
| BigInteger bint = BigInteger.valueOf(this.nameType); |
| temp.putInteger(bint); |
| bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp); |
| temp = new DerOutputStream(); |
| DerValue der[] = new DerValue[nameStrings.length]; |
| for (int i = 0; i < nameStrings.length; i++) { |
| der[i] = new KerberosString(nameStrings[i]).toDerValue(); |
| } |
| temp.putSequence(der); |
| bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp); |
| temp = new DerOutputStream(); |
| temp.write(DerValue.tag_Sequence, bytes); |
| return temp.toByteArray(); |
| } |
| |
| |
| /** |
| * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields. |
| * |
| * @param pname the other <code>PrincipalName</code> object. |
| * @return true if two have identical values, otherwise, return false. |
| */ |
| // It is used in <code>sun.security.krb5.internal.ccache</code> package. |
| public boolean match(PrincipalName pname) { |
| boolean matched = true; |
| //name type is just a hint, no two names can be the same ignoring name type. |
| // if (this.nameType != pname.nameType) { |
| // matched = false; |
| // } |
| if ((this.nameRealm != null) && (pname.nameRealm != null)) { |
| if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) { |
| matched = false; |
| } |
| } |
| if (this.nameStrings.length != pname.nameStrings.length) { |
| matched = false; |
| } else { |
| for (int i = 0; i < this.nameStrings.length; i++) { |
| if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) { |
| matched = false; |
| } |
| } |
| } |
| return matched; |
| } |
| |
| /** |
| * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream. |
| * |
| * @param cos a <code>CCacheOutputStream</code> for writing data. |
| * @exception IOException if an I/O exception occurs. |
| * @see sun.security.krb5.internal.ccache.CCacheOutputStream |
| */ |
| public void writePrincipal(CCacheOutputStream cos) throws IOException { |
| cos.write32(nameType); |
| cos.write32(nameStrings.length); |
| if (nameRealm != null) { |
| byte[] realmBytes = null; |
| realmBytes = nameRealm.toString().getBytes(); |
| cos.write32(realmBytes.length); |
| cos.write(realmBytes, 0, realmBytes.length); |
| } |
| byte[] bytes = null; |
| for (int i = 0; i < nameStrings.length; i++) { |
| bytes = nameStrings[i].getBytes(); |
| cos.write32(bytes.length); |
| cos.write(bytes, 0, bytes.length); |
| } |
| } |
| |
| /** |
| * Creates a KRB_NT_SRV_INST name from the supplied |
| * name components and realm. |
| * @param primary the primary component of the name |
| * @param instance the instance component of the name |
| * @param realm the realm |
| * @throws KrbException |
| */ |
| protected PrincipalName(String primary, String instance, String realm, |
| int type) |
| throws KrbException { |
| |
| if (type != KRB_NT_SRV_INST) { |
| throw new KrbException(Krb5.KRB_ERR_GENERIC, "Bad name type"); |
| } |
| |
| String[] nParts = new String[2]; |
| nParts[0] = primary; |
| nParts[1] = instance; |
| |
| this.nameStrings = nParts; |
| this.nameRealm = new Realm(realm); |
| this.nameType = type; |
| } |
| |
| /** |
| * Returns the instance component of a name. |
| * In a multi-component name such as a KRB_NT_SRV_INST |
| * name, the second component is returned. |
| * Null is returned if there are not two or more |
| * components in the name. |
| * @returns instance component of a multi-component name. |
| */ |
| public String getInstanceComponent() |
| { |
| if (nameStrings != null && nameStrings.length >= 2) |
| { |
| return new String(nameStrings[1]); |
| } |
| |
| return null; |
| } |
| |
| static String mapHostToRealm(String name) { |
| String result = null; |
| try { |
| String subname = null; |
| Config c = Config.getInstance(); |
| if ((result = c.getDefault(name, "domain_realm")) != null) |
| return result; |
| else { |
| for (int i = 1; i < name.length(); i++) { |
| if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM |
| subname = name.substring(i); |
| result = c.getDefault(subname, "domain_realm"); |
| if (result != null) { |
| break; |
| } |
| else { |
| subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM |
| result = c.getDefault(subname, "domain_realm"); |
| if (result != null) { |
| break; |
| } |
| } |
| } |
| } |
| } |
| } catch (KrbException e) { |
| } |
| return result; |
| } |
| |
| } |