changeset 12932:3975503a71c5

8048030: Expectations should be consistent Reviewed-by: valeriep, mullan, ahgross
author weijun
date Sun, 24 May 2015 16:35:12 +0800
parents e803843a9a36
children 662689223d73
files src/java.base/share/classes/javax/security/auth/AuthPermission.java src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java src/java.security.jgss/share/classes/javax/security/auth/kerberos/ServicePermission.java src/java.security.jgss/share/classes/org/ietf/jgss/GSSName.java src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5NameElement.java src/java.security.jgss/share/classes/sun/security/jgss/wrapper/GSSNameElement.java src/java.security.jgss/share/classes/sun/security/krb5/KrbServiceLocator.java src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java src/java.security.jgss/share/classes/sun/security/krb5/Realm.java src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheInputStream.java test/sun/security/krb5/auto/KDC.java test/sun/security/krb5/auto/SSL.java test/sun/security/krb5/name/Constructors.java
diffstat 13 files changed, 238 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/javax/security/auth/AuthPermission.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.base/share/classes/javax/security/auth/AuthPermission.java	Sun May 24 16:35:12 2015 +0800
@@ -101,6 +101,18 @@
  *                              login Configuration.
  * </pre>
  *
+ * <p>Please note that granting this permission with the "modifyPrincipals",
+ * "modifyPublicCredentials" or "modifyPrivateCredentials" target allows
+ * a JAAS login module to populate principal or credential objects into
+ * the Subject. Although reading information inside the private credentials
+ * set requires a {@link PrivateCredentialPermission} of the credential type to
+ * be granted, reading information inside the principals set and the public
+ * credentials set requires no additional permission. These objects can contain
+ * potentially sensitive information. For example, login modules that read
+ * local user information or perform a Kerberos login are able to add
+ * potentially sensitive information such as user ids, groups and domain names
+ * to the principals set.
+ *
  * <p> The following target name has been deprecated in favor of
  * {@code createLoginContext.{name}}.
  *
--- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java	Sun May 24 16:35:12 2015 +0800
@@ -100,9 +100,16 @@
      * <p>If the input name does not contain a realm, the default realm
      * is used. The default realm can be specified either in a Kerberos
      * configuration file or via the java.security.krb5.realm
-     * system property. For more information,
+     * system property. For more information, see
      * <a href="../../../../../technotes/guides/security/jgss/tutorials/index.html">
-     * Kerberos Requirements </a>
+     * Kerberos Requirements</a>. Additionally, if a security manager is
+     * installed, a {@link ServicePermission} must be granted and the service
+     * principal of the permission must minimally be inside the
+     * {@code KerberosPrincipal}'s realm. For example, if the result of
+     * {@code new KerberosPrincipal("user")} is {@code user@EXAMPLE.COM},
+     * then a {@code ServicePermission} with service principal
+     * {@code host/www.example.com@EXAMPLE.COM} (and any action)
+     * must be granted.
      *
      * @param name the principal name
      * @throws IllegalArgumentException if name is improperly
@@ -110,20 +117,12 @@
      * the realm to use and the default realm is not specified
      * in either a Kerberos configuration file or via the
      * java.security.krb5.realm system property.
+     * @throws SecurityException if a security manager is installed and
+     * {@code name} does not contain the realm to use, and a proper
+     * {@link ServicePermission} as described above is not granted.
      */
     public KerberosPrincipal(String name) {
-
-        PrincipalName krb5Principal = null;
-
-        try {
-            // Appends the default realm if it is missing
-            krb5Principal = new PrincipalName(name, KRB_NT_PRINCIPAL);
-        } catch (KrbException e) {
-            throw new IllegalArgumentException(e.getMessage());
-        }
-        nameType = KRB_NT_PRINCIPAL;  // default name type
-        fullName = krb5Principal.toString();
-        realm = krb5Principal.getRealmString();
+        this(name, KRB_NT_PRINCIPAL);
     }
 
     /**
@@ -138,12 +137,19 @@
      * name type, KRB_NT_PRINCIPAL where <i>duke</i>
      * represents a principal, and <i>FOO.COM</i> represents a realm).
      *
-     * <p> If the input name does not contain a realm, the default realm
+     * <p>If the input name does not contain a realm, the default realm
      * is used. The default realm can be specified either in a Kerberos
      * configuration file or via the java.security.krb5.realm
      * system property. For more information, see
      * <a href="../../../../../technotes/guides/security/jgss/tutorials/index.html">
-     * Kerberos Requirements</a>.
+     * Kerberos Requirements</a>. Additionally, if a security manager is
+     * installed, a {@link ServicePermission} must be granted and the service
+     * principal of the permission must minimally be inside the
+     * {@code KerberosPrincipal}'s realm. For example, if the result of
+     * {@code new KerberosPrincipal("user")} is {@code user@EXAMPLE.COM},
+     * then a {@code ServicePermission} with service principal
+     * {@code host/www.example.com@EXAMPLE.COM} (and any action)
+     * must be granted.
      *
      * @param name the principal name
      * @param nameType the name type of the principal
@@ -152,6 +158,9 @@
      * or if name does not contain the realm to use and the default
      * realm is not specified in either a Kerberos configuration
      * file or via the java.security.krb5.realm system property.
+     * @throws SecurityException if a security manager is installed and
+     * {@code name} does not contain the realm to use, and a proper
+     * {@link ServicePermission} as described above is not granted.
      */
 
     public KerberosPrincipal(String name, int nameType) {
@@ -165,6 +174,18 @@
             throw new IllegalArgumentException(e.getMessage());
         }
 
+        if (krb5Principal.isRealmDeduced() && !Realm.AUTODEDUCEREALM) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                try {
+                    sm.checkPermission(new ServicePermission(
+                            "@" + krb5Principal.getRealmAsString(), "-"));
+                } catch (SecurityException se) {
+                    // Swallow the actual exception to hide info
+                    throw new SecurityException("Cannot read realm info");
+                }
+            }
+        }
         this.nameType = nameType;
         fullName = krb5Principal.toString();
         realm = krb5Principal.getRealmString();
--- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/ServicePermission.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/ServicePermission.java	Sun May 24 16:35:12 2015 +0800
@@ -51,7 +51,7 @@
  * used within.
  * <p>
  * The service principal name is the canonical name of the
- * {@code KereberosPrincipal} supplying the service, that is
+ * {@code KerberosPrincipal} supplying the service, that is
  * the KerberosPrincipal represents a Kerberos service
  * principal. This name is treated in a case sensitive manner.
  * An asterisk may appear by itself, to signify any service principal.
@@ -62,6 +62,10 @@
  * permission also implies that the TGT can be obtained by an
  * Authentication Service exchange.
  * <p>
+ * Granting this permission also implies creating {@link KerberosPrincipal}
+ * or {@link org.ietf.jgss.GSSName GSSName} without providing a Kerberos
+ * realm, as long as the permission's service principal is in this realm.
+ * <p>
  * The possible actions are:
  *
  * <pre>
@@ -146,6 +150,9 @@
      * @param action the action string
      */
     public ServicePermission(String servicePrincipal, String action) {
+        // Note: servicePrincipal can be "@REALM" which means any principal in
+        // this realm implies it. action can be "-" which means any
+        // action implies it.
         super(servicePrincipal);
         init(servicePrincipal, getMask(action));
     }
@@ -208,7 +215,9 @@
 
     boolean impliesIgnoreMask(ServicePermission p) {
         return ((this.getName().equals("*")) ||
-                this.getName().equals(p.getName()));
+                this.getName().equals(p.getName()) ||
+                (p.getName().startsWith("@") &&
+                        this.getName().endsWith(p.getName())));
     }
 
     /**
@@ -318,7 +327,10 @@
     /**
      * Convert an action string to an integer actions mask.
      *
-     * @param action the action string
+     * Note: if action is "-", action will be NONE, which means any
+     * action implies it.
+     *
+     * @param action the action string.
      * @return the action mask
      */
     private static int getMask(String action) {
@@ -335,9 +347,11 @@
 
         char[] a = action.toCharArray();
 
+        if (a.length == 1 && a[0] == '-') {
+            return mask;
+        }
+
         int i = a.length - 1;
-        if (i < 0)
-            return mask;
 
         while (i != -1) {
             char c;
@@ -501,6 +515,17 @@
         ServicePermission np = (ServicePermission) permission;
         int desired = np.getMask();
 
+        if (desired == 0) {
+            for (Permission p: perms.values()) {
+                ServicePermission sp = (ServicePermission)p;
+                if (sp.impliesIgnoreMask(np)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+
         // first, check for wildcard principal
         ServicePermission x = (ServicePermission)perms.get("*");
         if (x != null) {
--- a/src/java.security.jgss/share/classes/org/ietf/jgss/GSSName.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/org/ietf/jgss/GSSName.java	Sun May 24 16:35:12 2015 +0800
@@ -81,6 +81,18 @@
  *                      GSSName.NT_EXPORT_NAME);
  *
  * </pre>
+ * If a security manager is installed, in order to create a {@code GSSName}
+ * that contains a Kerberos name element without providing its realm,
+ * a {@link javax.security.auth.kerberos.ServicePermission ServicePermission}
+ * must be granted and the service principal of the permission must minimally
+ * be inside the Kerberos name element's realm. For example, if the result of
+ * {@link GSSManager#createName(String, Oid) createName("user", NT_USER_NAME)}
+ * contains a Kerberos name element {@code user@EXAMPLE.COM}, then
+ * a {@code ServicePermission} with service principal
+ * {@code host/www.example.com@EXAMPLE.COM} (and any action) must be granted.
+ * Otherwise, the creation will throw a {@link GSSException} containing the
+ * {@code GSSException.FAILURE} error code.
+ *
  * @see #export()
  * @see #equals(GSSName)
  * @see GSSManager#createName(String, Oid)
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5NameElement.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5NameElement.java	Sun May 24 16:35:12 2015 +0800
@@ -28,7 +28,10 @@
 import org.ietf.jgss.*;
 import sun.security.jgss.spi.*;
 import sun.security.krb5.PrincipalName;
+import sun.security.krb5.Realm;
 import sun.security.krb5.KrbException;
+
+import javax.security.auth.kerberos.ServicePermission;
 import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -126,6 +129,18 @@
             throw new GSSException(GSSException.BAD_NAME, -1, e.getMessage());
         }
 
+        if (principalName.isRealmDeduced() && !Realm.AUTODEDUCEREALM) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                try {
+                    sm.checkPermission(new ServicePermission(
+                            "@" + principalName.getRealmAsString(), "-"));
+                } catch (SecurityException se) {
+                    // Do not chain the actual exception to hide info
+                    throw new GSSException(GSSException.FAILURE);
+                }
+            }
+        }
         return new Krb5NameElement(principalName, gssNameStr, gssNameType);
     }
 
--- a/src/java.security.jgss/share/classes/sun/security/jgss/wrapper/GSSNameElement.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/wrapper/GSSNameElement.java	Sun May 24 16:35:12 2015 +0800
@@ -30,6 +30,7 @@
 import java.security.Security;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import sun.security.krb5.Realm;
 import sun.security.jgss.GSSUtil;
 import sun.security.util.ObjectIdentifier;
 import sun.security.util.DerInputStream;
@@ -38,6 +39,8 @@
 import sun.security.jgss.GSSExceptionImpl;
 import sun.security.jgss.spi.GSSNameSpi;
 
+import javax.security.auth.kerberos.ServicePermission;
+
 /**
  * This class is essentially a wrapper class for the gss_name_t
  * structure of the native GSS library.
@@ -150,6 +153,26 @@
         pName = cStub.importName(name, nameType);
         setPrintables();
 
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null && !Realm.AUTODEDUCEREALM) {
+            String krbName = getKrbName();
+            int atPos = krbName.lastIndexOf('@');
+            if (atPos != -1) {
+                String atRealm = krbName.substring(atPos);
+                if (nameType.equals(GSSUtil.NT_GSS_KRB5_PRINCIPAL)
+                        && new String(nameBytes).endsWith(atRealm)) {
+                    // Created from Kerberos name with realm, no need to check
+                } else {
+                    try {
+                        sm.checkPermission(new ServicePermission(atRealm, "-"));
+                    } catch (SecurityException se) {
+                        // Do not chain the actual exception to hide info
+                        throw new GSSException(GSSException.FAILURE);
+                    }
+                }
+            }
+        }
+
         SunNativeProvider.debug("Imported " + printableName + " w/ type " +
                                 printableType);
     }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbServiceLocator.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbServiceLocator.java	Sun May 24 16:35:12 2015 +0800
@@ -25,6 +25,11 @@
 
 package sun.security.krb5;
 
+import sun.security.krb5.internal.Krb5;
+
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.Random;
@@ -52,6 +57,8 @@
 
     private static final Random random = new Random();
 
+    private static final boolean DEBUG = Krb5.DEBUG;
+
     private KrbServiceLocator() {
     }
 
@@ -62,8 +69,7 @@
      * Information on the mapping of DNS hostnames and domain names
      * to Kerberos realms is stored using DNS TXT records
      *
-     * @param domainName A string domain name.
-     * @param environment The possibly null environment of the context.
+     * @param realmName A string realm name.
      * @return An ordered list of hostports for the Kerberos service or null if
      *          the service has not been located.
      */
@@ -81,8 +87,18 @@
             if (!(ctx instanceof DirContext)) {
                 return null; // cannot create a DNS context
             }
-            Attributes attrs =
-                ((DirContext)ctx).getAttributes(dnsUrl, SRV_TXT_ATTR);
+            Attributes attrs = null;
+            try {
+                // both connect and accept are needed since DNS is thru UDP
+                attrs = AccessController.doPrivileged(
+                        (PrivilegedExceptionAction<Attributes>)
+                                () -> ((DirContext)ctx).getAttributes(
+                                        dnsUrl, SRV_TXT_ATTR),
+                        null,
+                        new java.net.SocketPermission("*", "connect,accept"));
+            } catch (PrivilegedActionException e) {
+                throw (NamingException)e.getCause();
+            }
             Attribute attr;
 
             if (attrs != null && ((attr = attrs.get(SRV_TXT)) != null)) {
@@ -124,7 +140,8 @@
      * Queries DNS for a list of KERBEROS Service Location Records (SRV) for a
      * given domain name.
      *
-     * @param domainName A string domain name.
+     * @param realmName A string realm name.
+     * @param protocol the protocol string, can be "_udp" or "_tcp"
      * @return An ordered list of hostports for the Kerberos service or null if
      *          the service has not been located.
      */
@@ -142,8 +159,20 @@
             if (!(ctx instanceof DirContext)) {
                 return null; // cannot create a DNS context
             }
-            Attributes attrs =
-                ((DirContext)ctx).getAttributes(dnsUrl, SRV_RR_ATTR);
+
+            Attributes attrs = null;
+            try {
+                // both connect and accept are needed since DNS is thru UDP
+                attrs = AccessController.doPrivileged(
+                        (PrivilegedExceptionAction<Attributes>)
+                                () -> ((DirContext)ctx).getAttributes(
+                                        dnsUrl, SRV_RR_ATTR),
+                        null,
+                        new java.net.SocketPermission("*", "connect,accept"));
+            } catch (PrivilegedActionException e) {
+                throw (NamingException)e.getCause();
+            }
+
             Attribute attr;
 
             if (attrs != null && ((attr = attrs.get(SRV_RR)) != null)) {
--- a/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java	Sun May 24 16:35:12 2015 +0800
@@ -123,6 +123,13 @@
      */
     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;
 
@@ -143,6 +150,7 @@
         this.nameType = nameType;
         this.nameStrings = nameStrings.clone();
         this.nameRealm = nameRealm;
+        this.realmDeduced = false;
     }
 
     // This method is called by Windows NativeCred.c
@@ -150,11 +158,6 @@
         this(KRB_NT_UNKNOWN, nameParts, new Realm(realm));
     }
 
-    public PrincipalName(String[] nameParts, int type)
-            throws IllegalArgumentException, RealmException {
-        this(type, nameParts, Realm.getDefault());
-    }
-
     // Validate a nameStrings argument
     private static void validateNameStrings(String[] ns) {
         if (ns == null) {
@@ -226,7 +229,7 @@
      * <a href="http://www.ietf.org/rfc/rfc4120.txt">
      * http://www.ietf.org/rfc/rfc4120.txt</a>.
      *
-     * @param encoding a Der-encoded data.
+     * @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.
@@ -240,6 +243,7 @@
         if (realm == null) {
             throw new IllegalArgumentException("Null realm not allowed");
         }
+        realmDeduced = false;
         nameRealm = realm;
         DerValue der;
         if (encoding == null) {
@@ -394,6 +398,10 @@
         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) {
@@ -413,8 +421,8 @@
                                 hostName.toLowerCase(Locale.ENGLISH)+".")) {
                         hostName = canonicalized;
                     }
-                } catch (UnknownHostException e) {
-                    // no canonicalization, use old
+                } catch (UnknownHostException | SecurityException e) {
+                    // not canonicalized or no permission to do so, use old
                 }
                 nameParts[1] = hostName.toLowerCase(Locale.ENGLISH);
             }
@@ -681,4 +689,7 @@
         return result;
     }
 
+    public boolean isRealmDeduced() {
+        return realmDeduced;
+    }
 }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/Realm.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/Realm.java	Sun May 24 16:35:12 2015 +0800
@@ -46,6 +46,12 @@
  * 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
 
     public Realm(String name) throws RealmException {
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheInputStream.java	Sat May 23 02:49:50 2015 +0300
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheInputStream.java	Sun May 24 16:35:12 2015 +0800
@@ -146,8 +146,9 @@
         }
         try {
             return new PrincipalName(
+                    type,
                     result.toArray(new String[result.size()]),
-                    type);
+                    Realm.getDefault());
         } catch (RealmException re) {
             return null;
         }
--- a/test/sun/security/krb5/auto/KDC.java	Sat May 23 02:49:50 2015 +0300
+++ b/test/sun/security/krb5/auto/KDC.java	Sun May 24 16:35:12 2015 +0800
@@ -889,8 +889,9 @@
 
         PrincipalName service = asReq.reqBody.sname;
         if (options.containsKey(KDC.Option.RESP_NT)) {
-            service = new PrincipalName(service.getNameStrings(),
-                    (int)options.get(KDC.Option.RESP_NT));
+            service = new PrincipalName((int)options.get(KDC.Option.RESP_NT),
+                    service.getNameStrings(),
+                    Realm.getDefault());
         }
         try {
             System.out.println(realm + "> " + asReq.reqBody.cname +
--- a/test/sun/security/krb5/auto/SSL.java	Sat May 23 02:49:50 2015 +0300
+++ b/test/sun/security/krb5/auto/SSL.java	Sun May 24 16:35:12 2015 +0800
@@ -76,7 +76,10 @@
             return;
         }
         ServicePermission p = (ServicePermission)perm;
-        permChecks = permChecks + p.getActions().toUpperCase().charAt(0);
+        // ServicePermissions required to create GSSName are ignored
+        if (!p.getActions().isEmpty()) {
+            permChecks = permChecks + p.getActions().toUpperCase().charAt(0);
+        }
     }
 
     public static void main(String[] args) throws Exception {
--- a/test/sun/security/krb5/name/Constructors.java	Sat May 23 02:49:50 2015 +0300
+++ b/test/sun/security/krb5/name/Constructors.java	Sun May 24 16:35:12 2015 +0800
@@ -41,22 +41,22 @@
 
         // Good ones
         type = PrincipalName.KRB_NT_UNKNOWN;
-        checkName("a", type, "R", "R", "a");
-        checkName("a@R2", type, "R", "R", "a");
-        checkName("a/b", type, "R", "R", "a", "b");
-        checkName("a/b@R2", type, "R", "R", "a", "b");
-        checkName("a/b/c", type, "R", "R", "a", "b", "c");
-        checkName("a/b/c@R2", type, "R", "R", "a", "b", "c");
+        checkName("a", type, "R", "R", false, "a");
+        checkName("a@R2", type, "R", "R", false, "a");
+        checkName("a/b", type, "R", "R", false, "a", "b");
+        checkName("a/b@R2", type, "R", "R", false, "a", "b");
+        checkName("a/b/c", type, "R", "R", false, "a", "b", "c");
+        checkName("a/b/c@R2", type, "R", "R", false, "a", "b", "c");
         // Weird ones
-        checkName("a\\/b", type, "R", "R", "a/b");
-        checkName("a\\/b\\/c", type, "R", "R", "a/b/c");
-        checkName("a\\/b\\@R2", type, "R", "R", "a/b@R2");
+        checkName("a\\/b", type, "R", "R", false, "a/b");
+        checkName("a\\/b\\/c", type, "R", "R", false, "a/b/c");
+        checkName("a\\/b\\@R2", type, "R", "R", false, "a/b@R2");
         // Bad ones
-        checkName("a", type, "", null);
-        checkName("a/", type, "R", null);
-        checkName("/a", type, "R", null);
-        checkName("a//b", type, "R", null);
-        checkName("a@", type, null, null);
+        checkName("a", type, "", null, false);
+        checkName("a/", type, "R", null, false);
+        checkName("/a", type, "R", null, false);
+        checkName("a//b", type, "R", null, false);
+        checkName("a@", type, null, null, false);
         type = PrincipalName.KRB_NT_SRV_HST;
 
         // Part 2: on realm choices
@@ -78,17 +78,17 @@
 
         if (testNoDefaultDomain) {
             type = PrincipalName.KRB_NT_UNKNOWN;
-            checkName("a", type, "R1", "R1", "a");      // arg
-            checkName("a@R1", type, null, "R1", "a");   // or r in name
-            checkName("a@R2", type, "R1", "R1", "a");   // arg over r
-            checkName("a", type, null, null);      // fail if none
-            checkName("a/b@R1", type, null, "R1", "a", "b");
+            checkName("a", type, "R1", "R1", false, "a");      // arg
+            checkName("a@R1", type, null, "R1", false, "a");   // or r in name
+            checkName("a@R2", type, "R1", "R1", false, "a");   // arg over r
+            checkName("a", type, null, null, false);      // fail if none
+            checkName("a/b@R1", type, null, "R1", false, "a", "b");
             type = PrincipalName.KRB_NT_SRV_HST;
             // Let's pray "b.h" won't be canonicalized
-            checkName("a/b.h", type, "R1", "R1", "a", "b.h");    // arg
-            checkName("a/b.h@R1", type, null, "R1", "a", "b.h"); // or r in name
-            checkName("a/b.h@R1", type, "R2", "R2", "a", "b.h"); // arg over r
-            checkName("a/b.h", type, null, null);    // fail if none
+            checkName("a/b.h", type, "R1", "R1", false, "a", "b.h");    // arg
+            checkName("a/b.h@R1", type, null, "R1", false, "a", "b.h"); // or r in name
+            checkName("a/b.h@R1", type, "R2", "R2", false, "a", "b.h"); // arg over r
+            checkName("a/b.h", type, null, null, false);    // fail if none
         }
 
         // When there is default realm
@@ -97,25 +97,25 @@
         Config.refresh();
 
         type = PrincipalName.KRB_NT_UNKNOWN;
-        checkName("a", type, "R1", "R1", "a");      // arg
-        checkName("a@R1", type, null, "R1", "a");   // or r in name
-        checkName("a@R2", type, "R1", "R1", "a");   // arg over r
-        checkName("a", type, null, "R", "a");       // default
-        checkName("a/b", type, null, "R", "a", "b");
+        checkName("a", type, "R1", "R1", false, "a");      // arg
+        checkName("a@R1", type, null, "R1", false, "a");   // or r in name
+        checkName("a@R2", type, "R1", "R1", false, "a");   // arg over r
+        checkName("a", type, null, "R", true, "a");       // default
+        checkName("a/b", type, null, "R", true, "a", "b");
         type = PrincipalName.KRB_NT_SRV_HST;
-        checkName("a/b.h3", type, "R1", "R1", "a", "b.h3");     // arg
-        checkName("a/b.h@R1", type, null, "R1", "a", "b.h");    // or r in name
-        checkName("a/b.h3@R2", type, "R1", "R1", "a", "b.h3");  // arg over r
-        checkName("a/b.h2", type, "R1", "R1", "a", "b.h2");     // arg over map
-        checkName("a/b.h2@R1", type, null, "R1", "a", "b.h2");  // r over map
-        checkName("a/b.h2", type, null, "R2", "a", "b.h2");     // map
-        checkName("a/b.h", type, null, "R", "a", "b.h");        // default
+        checkName("a/b.h3", type, "R1", "R1", false, "a", "b.h3");     // arg
+        checkName("a/b.h@R1", type, null, "R1", false, "a", "b.h");    // or r in name
+        checkName("a/b.h3@R2", type, "R1", "R1", false, "a", "b.h3");  // arg over r
+        checkName("a/b.h2", type, "R1", "R1", false, "a", "b.h2");     // arg over map
+        checkName("a/b.h2@R1", type, null, "R1", false, "a", "b.h2");  // r over map
+        checkName("a/b.h2", type, null, "R2", true, "a", "b.h2");     // map
+        checkName("a/b.h", type, null, "R", true, "a", "b.h");        // default
     }
 
     // Check if the creation matches the expected output.
     // Note: realm == null means creation failure
     static void checkName(String n, int t, String s,
-            String realm, String... parts)
+            String realm, boolean deduced, String... parts)
             throws Exception {
         PrincipalName pn = null;
         try {
@@ -132,5 +132,8 @@
             throw new Exception(pn.toString() + " vs "
                     + Arrays.toString(parts) + "@" + realm);
         }
+        if (deduced != pn.isRealmDeduced()) {
+            throw new Exception("pn.realmDeduced is " + pn.isRealmDeduced());
+        }
     }
 }