blob: 885e13cad1efd1a4c8ea3b3d1b1b8bd14b6fc886 [file] [log] [blame]
/*
* Copyright (c) 2000, 2013, 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.util.Locale;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import sun.security.krb5.internal.ccache.CCacheOutputStream;
import sun.security.krb5.internal.util.KerberosString;
/**
* Implements the ASN.1 PrincipalName type and its realm in a single class.
* <xmp>
* Realm ::= KerberosString
*
* PrincipalName ::= SEQUENCE {
* name-type [0] Int32,
* name-string [1] SEQUENCE OF KerberosString
* }
* </xmp>
* This class is immutable.
* @see Realm
*/
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 = ".";
// Instance fields.
/**
* The name type, from PrincipalName's name-type field.
*/
private final int nameType;
/**
* The name strings, from PrincipalName's name-strings field. This field
* must be neither null nor empty. Each entry of it must also be neither
* null nor empty. Make sure to clone the field when it's passed in or out.
*/
private final String[] nameStrings;
/**
* The realm this principal belongs to.
*/
private final Realm nameRealm; // not null
/**
* When constructing a PrincipalName, whether the realm is included in
* the input, or deduced from default realm or domain-realm mapping.
*/
private final boolean realmDeduced;
// cached default salt, not used in clone
private transient String salt = null;
// There are 3 basic constructors. All other constructors must call them.
// All basic constructors must call validateNameStrings.
// 1. From name components
// 2. From name
// 3. From DER encoding
/**
* Creates a PrincipalName.
*/
public PrincipalName(int nameType, String[] nameStrings, Realm nameRealm) {
if (nameRealm == null) {
throw new IllegalArgumentException("Null realm not allowed");
}
validateNameStrings(nameStrings);
this.nameType = nameType;
this.nameStrings = nameStrings.clone();
this.nameRealm = nameRealm;
this.realmDeduced = false;
}
// This method is called by Windows NativeCred.c
public PrincipalName(String[] nameParts, String realm) throws RealmException {
this(KRB_NT_UNKNOWN, nameParts, new Realm(realm));
}
// Validate a nameStrings argument
private static void validateNameStrings(String[] ns) {
if (ns == null) {
throw new IllegalArgumentException("Null nameStrings not allowed");
}
if (ns.length == 0) {
throw new IllegalArgumentException("Empty nameStrings not allowed");
}
for (String s: ns) {
if (s == null) {
throw new IllegalArgumentException("Null nameString not allowed");
}
if (s.isEmpty()) {
throw new IllegalArgumentException("Empty nameString not allowed");
}
}
}
public Object clone() {
try {
PrincipalName pName = (PrincipalName) super.clone();
UNSAFE.putObject(this, NAME_STRINGS_OFFSET, nameStrings.clone());
return pName;
} catch (CloneNotSupportedException ex) {
throw new AssertionError("Should never happen");
}
}
private static final long NAME_STRINGS_OFFSET;
private static final sun.misc.Unsafe UNSAFE;
static {
try {
sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
NAME_STRINGS_OFFSET = unsafe.objectFieldOffset(
PrincipalName.class.getDeclaredField("nameStrings"));
UNSAFE = unsafe;
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof PrincipalName) {
PrincipalName other = (PrincipalName)o;
return nameRealm.equals(other.nameRealm) &&
Arrays.equals(nameStrings, other.nameStrings);
}
return false;
}
/**
* 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 DER-encoded PrincipalName (without Realm)
* @param realm the realm for this name
* @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, Realm realm)
throws Asn1Exception, IOException {
if (realm == null) {
throw new IllegalArgumentException("Null realm not allowed");
}
realmDeduced = false;
nameRealm = realm;
DerValue der;
if (encoding == null) {
throw new IllegalArgumentException("Null encoding 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();
String namePart = new KerberosString(subSubDer).toString();
v.addElement(namePart);
}
nameStrings = new String[v.size()];
v.copyInto(nameStrings);
validateNameStrings(nameStrings);
} 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
* @param realm the realm for the name
* @return an instance of <code>PrincipalName</code>, or null if the
* field is optional and missing.
*/
public static PrincipalName parse(DerInputStream data,
byte explicitTag, boolean
optional,
Realm realm)
throws Asn1Exception, IOException, RealmException {
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();
if (realm == null) {
realm = Realm.getDefault();
}
return new PrincipalName(subDer, realm);
}
}
// XXX Error checkin consistent with MIT krb5_parse_name
// Code repetition, realm parsed again by class Realm
private 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()) {
component = temp.substring(componentStart, i);
tempStrings.addElement(component);
}
String[] result = new String[tempStrings.size()];
tempStrings.copyInto(result);
return result;
}
/**
* Constructs a PrincipalName from a string.
* @param name the name
* @param type the type
* @param realm the realm, null if not known. Note that when realm is not
* null, it will be always used even if there is a realm part in name. When
* realm is null, will read realm part from name, or try to map a realm
* (for KRB_NT_SRV_HST), or use the default realm, or fail
* @throws RealmException
*/
public PrincipalName(String name, int type, String realm)
throws RealmException {
if (name == null) {
throw new IllegalArgumentException("Null name not allowed");
}
String[] nameParts = parseName(name);
validateNameStrings(nameParts);
if (realm == null) {
realm = Realm.parseRealmAtSeparator(name);
}
// No realm info from parameter and string, must deduce later
realmDeduced = realm == null;
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(Locale.ENGLISH).startsWith(
hostName.toLowerCase(Locale.ENGLISH)+".")) {
hostName = canonicalized;
}
} catch (UnknownHostException | SecurityException e) {
// not canonicalized or no permission to do so, use old
}
nameParts[1] = hostName.toLowerCase(Locale.ENGLISH);
}
nameStrings = nameParts;
nameType = type;
if (realm != null) {
nameRealm = new Realm(realm);
} else {
// 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 = Realm.getDefault();
}
}
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;
if (realm != null) {
nameRealm = new Realm(realm);
} else {
nameRealm = Realm.getDefault();
}
break;
default:
throw new IllegalArgumentException("Illegal name type");
}
}
public PrincipalName(String name, int type) throws RealmException {
this(name, type, (String)null);
}
public PrincipalName(String name) throws RealmException {
this(name, KRB_NT_UNKNOWN);
}
public PrincipalName(String name, String realm) throws RealmException {
this(name, KRB_NT_UNKNOWN, realm);
}
public static PrincipalName tgsService(String r1, String r2)
throws KrbException {
return new PrincipalName(PrincipalName.KRB_NT_SRV_INST,
new String[] {PrincipalName.TGS_DEFAULT_SRV_NAME, r1},
new Realm(r2));
}
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.clone();
}
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() {
return nameRealm.toString();
}
public Realm getRealm() {
return nameRealm;
}
public String getSalt() {
if (salt == null) {
StringBuffer salt = new StringBuffer();
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]);
}
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. Note that only the type and
* names are encoded. To encode the realm, call getRealm().asn1Encode().
* @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);
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);
}
}
/**
* 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.get("domain_realm", name)) != 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.get("domain_realm", subname);
if (result != null) {
break;
}
else {
subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM
result = c.get("domain_realm", subname);
if (result != null) {
break;
}
}
}
}
}
} catch (KrbException e) {
}
return result;
}
public boolean isRealmDeduced() {
return realmDeduced;
}
}