jdk/src/share/classes/sun/security/krb5/PrincipalName.java
changeset 2 90ce3da70b43
child 2918 395b9ffa7cc6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/krb5/PrincipalName.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,689 @@
+/*
+ * Portions 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.
+ */
+
+/*
+ *
+ *  (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.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import sun.security.krb5.internal.ccache.CCacheOutputStream;
+
+
+/**
+ * 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
+
+    // salt for principal
+    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() {
+        PrincipalName pName = new PrincipalName();
+        pName.nameType = nameType;
+        if (nameStrings != null) {
+            pName.nameStrings =
+                new String[nameStrings.length];
+                System.arraycopy(nameStrings,0,pName.nameStrings,0,
+                                nameStrings.length);
+        }
+        if (nameRealm != null) {
+            pName.nameRealm = (Realm)nameRealm.clone();
+        }
+        return pName;
+    }
+
+    /*
+     * 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<String> ();
+            DerValue subSubDer;
+            while(subDer.getData().available() > 0) {
+                subSubDer = subDer.getData().getDerValue();
+                v.addElement(subSubDer.getGeneralString());
+            }
+            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> ();
+        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) {
+                try {
+                    // Canonicalize the hostname as per the
+                    // RFC4120 Section 6.2.1 and
+                    // RFC1964 Section 2.1.2
+                    // we assume internet domain names
+                    String hostName =
+                        (InetAddress.getByName(nameParts[1])).
+                        getCanonicalHostName();
+                    nameParts[1] = hostName.toLowerCase();
+                } catch (UnknownHostException e) {
+                    // no canonicalization, just convert to lowercase
+                    nameParts[1] = nameParts[1].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 void setSalt(String salt) {
+        this.salt = 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 DerValue(DerValue.tag_GeneralString, nameStrings[i]);
+        }
+        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;
+    }
+
+}