/*
* Copyright (c) 2000, 2011, 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.Krb5;
import sun.security.util.*;
import java.io.IOException;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Stack;
import java.util.EmptyStackException;
import sun.security.krb5.internal.util.KerberosString;
Implements the ASN.1 Realm type.
Realm ::= GeneralString
This class is immutable.
/**
* Implements the ASN.1 Realm type.
*
* <xmp>
* Realm ::= GeneralString
* </xmp>
* This class is immutable.
*/
public class Realm implements Cloneable {
public static final boolean AUTODEDUCEREALM =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"sun.security.krb5.autodeducerealm"));
private final String realm; // not null nor empty
private static boolean DEBUG = Krb5.DEBUG;
public Realm(String name) throws RealmException {
realm = parseRealm(name);
}
public static Realm getDefault() throws RealmException {
try {
return new Realm(Config.getInstance().getDefaultRealm());
} catch (RealmException re) {
throw re;
} catch (KrbException ke) {
throw new RealmException(ke);
}
}
// Immutable class, no need to clone
public Object clone() {
return this;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Realm)) {
return false;
}
Realm that = (Realm)obj;
return this.realm.equals(that.realm);
}
public int hashCode() {
return realm.hashCode();
}
Constructs a Realm object.
Params: - encoding – a Der-encoded data.
Throws: - Asn1Exception – if an error occurs while decoding an ASN1 encoded data.
- IOException – if an I/O error occurs while reading encoded data.
- RealmException – if an error occurs while parsing a Realm object.
/**
* Constructs a Realm object.
* @param encoding a Der-encoded data.
* @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
* @exception IOException if an I/O error occurs while reading encoded data.
* @exception RealmException if an error occurs while parsing a Realm object.
*/
public Realm(DerValue encoding)
throws Asn1Exception, RealmException, IOException {
if (encoding == null) {
throw new IllegalArgumentException("encoding can not be null");
}
realm = new KerberosString(encoding).toString();
if (realm == null || realm.length() == 0)
throw new RealmException(Krb5.REALM_NULL);
if (!isValidRealmString(realm))
throw new RealmException(Krb5.REALM_ILLCHAR);
}
public String toString() {
return realm;
}
// Extract realm from a string like dummy@REALM
public static String parseRealmAtSeparator(String name)
throws RealmException {
if (name == null) {
throw new IllegalArgumentException
("null input name is not allowed");
}
String temp = new String(name);
String result = null;
int i = 0;
while (i < temp.length()) {
if (temp.charAt(i) == PrincipalName.NAME_REALM_SEPARATOR) {
if (i == 0 || temp.charAt(i - 1) != '\\') {
if (i + 1 < temp.length()) {
result = temp.substring(i + 1, temp.length());
} else {
throw new IllegalArgumentException
("empty realm part not allowed");
}
break;
}
}
i++;
}
if (result != null) {
if (result.length() == 0)
throw new RealmException(Krb5.REALM_NULL);
if (!isValidRealmString(result))
throw new RealmException(Krb5.REALM_ILLCHAR);
}
return result;
}
public static String parseRealmComponent(String name) {
if (name == null) {
throw new IllegalArgumentException
("null input name is not allowed");
}
String temp = new String(name);
String result = null;
int i = 0;
while (i < temp.length()) {
if (temp.charAt(i) == PrincipalName.REALM_COMPONENT_SEPARATOR) {
if (i == 0 || temp.charAt(i - 1) != '\\') {
if (i + 1 < temp.length())
result = temp.substring(i + 1, temp.length());
break;
}
}
i++;
}
return result;
}
protected static String parseRealm(String name) throws RealmException {
String result = parseRealmAtSeparator(name);
if (result == null)
result = name;
if (result == null || result.length() == 0)
throw new RealmException(Krb5.REALM_NULL);
if (!isValidRealmString(result))
throw new RealmException(Krb5.REALM_ILLCHAR);
return result;
}
// This is protected because the definition of a realm
// string is fixed
protected static boolean isValidRealmString(String name) {
if (name == null)
return false;
if (name.length() == 0)
return false;
for (int i = 0; i < name.length(); i++) {
if (name.charAt(i) == '/' ||
name.charAt(i) == ':' ||
name.charAt(i) == '\0') {
return false;
}
}
return true;
}
Encodes a Realm object.
Throws: - Asn1Exception – if an error occurs while decoding an ASN1 encoded data.
- IOException – if an I/O error occurs while reading encoded data.
Returns: the byte array of encoded KrbCredInfo object.
/**
* Encodes a Realm object.
* @return the byte array of encoded KrbCredInfo 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 out = new DerOutputStream();
out.putDerValue(new KerberosString(this.realm).toDerValue());
return out.toByteArray();
}
Parse (unmarshal) a realm 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.
Params: - data – the Der input stream value, which contains one or more marshaled value.
- explicitTag – tag number.
- optional – indicate if this data field is optional
Throws: - Asn1Exception – on error.
Returns: an instance of Realm.
/**
* Parse (unmarshal) a realm 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 Realm.
*
*/
public static Realm parse(DerInputStream data, byte explicitTag, boolean optional)
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();
return new Realm(subDer);
}
}
/*
* First leg of realms parsing. Used by getRealmsList.
*/
private static String[] doInitialParse(String cRealm, String sRealm)
throws KrbException {
if (cRealm == null || sRealm == null){
throw new KrbException(Krb5.API_INVALID_ARG);
}
if (DEBUG) {
System.out.println(">>> Realm doInitialParse: cRealm=["
+ cRealm + "], sRealm=[" +sRealm + "]");
}
if (cRealm.equals(sRealm)) {
String[] retList = null;
retList = new String[1];
retList[0] = new String(cRealm);
if (DEBUG) {
System.out.println(">>> Realm doInitialParse: "
+ retList[0]);
}
return retList;
}
return null;
}
Returns an array of realms that may be traversed to obtain
a TGT from the initiating realm cRealm to the target realm
sRealm.
There may be an arbitrary number of intermediate realms
between cRealm and sRealm. The realms may be organized
organized hierarchically, or the paths between them may be
specified in the [capaths] stanza of the caller's
Kerberos configuration file. The configuration file is consulted
first. Then a hirarchical organization is assumed if no realms
are found in the configuration file.
The returned list, if not null, contains cRealm as the first
entry. sRealm is not included unless it is mistakenly listed
in the configuration file as an intermediary realm.
Params: - cRealm – the initiating realm
- sRealm – the target realm
@returns array of realms @thows KrbException
/**
* Returns an array of realms that may be traversed to obtain
* a TGT from the initiating realm cRealm to the target realm
* sRealm.
* <br>
* There may be an arbitrary number of intermediate realms
* between cRealm and sRealm. The realms may be organized
* organized hierarchically, or the paths between them may be
* specified in the [capaths] stanza of the caller's
* Kerberos configuration file. The configuration file is consulted
* first. Then a hirarchical organization is assumed if no realms
* are found in the configuration file.
* <br>
* The returned list, if not null, contains cRealm as the first
* entry. sRealm is not included unless it is mistakenly listed
* in the configuration file as an intermediary realm.
*
* @param cRealm the initiating realm
* @param sRealm the target realm
* @returns array of realms
* @thows KrbException
*/
public static String[] getRealmsList(String cRealm, String sRealm)
throws KrbException {
String[] retList = doInitialParse(cRealm, sRealm);
if (retList != null && retList.length != 0) {
return retList;
}
/*
* Try [capaths].
*/
retList = parseCapaths(cRealm, sRealm);
if (retList != null && retList.length != 0) {
return retList;
}
/*
* Now assume the realms are organized hierarchically.
*/
retList = parseHierarchy(cRealm, sRealm);
return retList;
}
Parses the [capaths] stanza of the configuration file for a
list of realms to traverse to obtain credentials from the
initiating realm cRealm to the target realm sRealm.
For a given client realm C there is a tag C in [capaths] whose
subtag S has a value which is a (possibly partial) path from C
to S. When the path is partial, it contains only the tail of the
full path. Values of other subtags will be used to build the full
path. The value "." means a direct path from C to S. If realm S
does not appear as a subtag, there is no path defined here.
The implementation ignores all values which equals to C or S, or
a "." in multiple values, or any duplicated realm names.
When a path value has more than two realms, they can be specified
with multiple key-value pairs each having a single value, but the
order must not change.
For example:
[capaths]
TIVOLI.COM = {
IBM.COM = IBM_LDAPCENTRAL.COM MOONLITE.ORG
IBM_LDAPCENTRAL.COM = LDAPCENTRAL.NET
LDAPCENTRAL.NET = .
}
TIVOLI.COM has a direct path to LDAPCENTRAL.NET, which has a direct
path to IBM_LDAPCENTRAL.COM. It also has a partial path to IBM.COM
being "IBM_LDAPCENTRAL.COM MOONLITE.ORG". Merging these info together,
a full path from TIVOLI.COM to IBM.COM will be
TIVOLI.COM -> LDAPCENTRAL.NET -> IBM_LDAPCENTRAL.COM
-> IBM_LDAPCENTRAL.COM -> MOONLITE.ORG
Please note the sRealm IBM.COM does not appear in the path.
Params: - cRealm – the initiating realm
- sRealm – the target realm, not the same as cRealm
@returns array of realms including at least cRealm as the first
element, or null if the config does not contain a sub-stanza
for cRealm in [capaths] or the sub-stanza does not contain
sRealm as a tag
/**
* Parses the [capaths] stanza of the configuration file for a
* list of realms to traverse to obtain credentials from the
* initiating realm cRealm to the target realm sRealm.
*
* For a given client realm C there is a tag C in [capaths] whose
* subtag S has a value which is a (possibly partial) path from C
* to S. When the path is partial, it contains only the tail of the
* full path. Values of other subtags will be used to build the full
* path. The value "." means a direct path from C to S. If realm S
* does not appear as a subtag, there is no path defined here.
*
* The implementation ignores all values which equals to C or S, or
* a "." in multiple values, or any duplicated realm names.
*
* When a path value has more than two realms, they can be specified
* with multiple key-value pairs each having a single value, but the
* order must not change.
*
* For example:
*
* [capaths]
* TIVOLI.COM = {
* IBM.COM = IBM_LDAPCENTRAL.COM MOONLITE.ORG
* IBM_LDAPCENTRAL.COM = LDAPCENTRAL.NET
* LDAPCENTRAL.NET = .
* }
*
* TIVOLI.COM has a direct path to LDAPCENTRAL.NET, which has a direct
* path to IBM_LDAPCENTRAL.COM. It also has a partial path to IBM.COM
* being "IBM_LDAPCENTRAL.COM MOONLITE.ORG". Merging these info together,
* a full path from TIVOLI.COM to IBM.COM will be
*
* TIVOLI.COM -> LDAPCENTRAL.NET -> IBM_LDAPCENTRAL.COM
* -> IBM_LDAPCENTRAL.COM -> MOONLITE.ORG
*
* Please note the sRealm IBM.COM does not appear in the path.
*
* @param cRealm the initiating realm
* @param sRealm the target realm, not the same as cRealm
* @returns array of realms including at least cRealm as the first
* element, or null if the config does not contain a sub-stanza
* for cRealm in [capaths] or the sub-stanza does not contain
* sRealm as a tag
*/
private static String[] parseCapaths(String cRealm, String sRealm) {
Config cfg = null;
try {
cfg = Config.getInstance();
} catch (Exception exc) {
if (DEBUG) {
System.out.println ("Configuration information can not be " +
"obtained " + exc.getMessage());
}
return null;
}
String intermediaries = cfg.getAll("capaths", cRealm, sRealm);
if (intermediaries == null) {
if (DEBUG) {
System.out.println(">>> Realm parseCapaths: no cfg entry");
}
return null;
}
LinkedList<String> path = new LinkedList<>();
String head = sRealm;
while (true) {
String value = cfg.getAll("capaths", cRealm, head);
if (value == null) {
break;
}
String[] more = value.split("\\s+");
boolean changed = false;
for (int i=more.length-1; i>=0; i--) {
if (path.contains(more[i])
|| more[i].equals(".")
|| more[i].equals(cRealm)
|| more[i].equals(sRealm)
|| more[i].equals(head)) {
// Ignore invalid values
continue;
}
changed = true;
path.addFirst(more[i]);
}
if (!changed) break;
head = path.getFirst();
}
path.addFirst(cRealm);
return path.toArray(new String[path.size()]);
}
Build a list of realm that can be traversed
to obtain credentials from the initiating realm cRealm
for a service in the target realm sRealm.
Params: - cRealm – the initiating realm
- sRealm – the target realm, not the same as cRealm
@returns array of realms including cRealm as the first element
/**
* Build a list of realm that can be traversed
* to obtain credentials from the initiating realm cRealm
* for a service in the target realm sRealm.
* @param cRealm the initiating realm
* @param sRealm the target realm, not the same as cRealm
* @returns array of realms including cRealm as the first element
*/
private static String[] parseHierarchy(String cRealm, String sRealm) {
String[] cComponents = cRealm.split("\\.");
String[] sComponents = sRealm.split("\\.");
int cPos = cComponents.length;
int sPos = sComponents.length;
boolean hasCommon = false;
for (sPos--, cPos--; sPos >=0 && cPos >= 0 &&
sComponents[sPos].equals(cComponents[cPos]);
sPos--, cPos--) {
hasCommon = true;
}
// For those with common components:
// length pos
// SITES1.SALES.EXAMPLE.COM 4 1
// EVERYWHERE.EXAMPLE.COM 3 0
// For those without common components:
// length pos
// DEVEL.EXAMPLE.COM 3 2
// PROD.EXAMPLE.ORG 3 2
LinkedList<String> path = new LinkedList<>();
// Un-common ones for client side
for (int i=0; i<=cPos; i++) {
path.addLast(subStringFrom(cComponents, i));
}
// Common one
if (hasCommon) {
path.addLast(subStringFrom(cComponents, cPos+1));
}
// Un-common ones for server side
for (int i=sPos; i>=0; i--) {
path.addLast(subStringFrom(sComponents, i));
}
// Remove sRealm from path. Note that it might be added at last loop
// or as a common component, if sRealm is a parent of cRealm
path.removeLast();
return path.toArray(new String[path.size()]);
}
Creates a realm name using components from the given position.
For example, subStringFrom({"A", "B", "C"}, 1) is "B.C".
/**
* Creates a realm name using components from the given position.
* For example, subStringFrom({"A", "B", "C"}, 1) is "B.C".
*/
private static String subStringFrom(String[] components, int from) {
StringBuilder sb = new StringBuilder();
for (int i=from; i<components.length; i++) {
if (sb.length() != 0) sb.append('.');
sb.append(components[i]);
}
return sb.toString();
}
}