diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security
index 4c0f4cda13d..5188362033d 100644
--- a/src/java.base/share/conf/security/java.security
+++ b/src/java.base/share/conf/security/java.security
@@ -1515,3 +1515,23 @@ jdk.tls.alpnCharset=ISO_8859_1
#
# [1] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/bde93b0e-f3c9-4ddf-9f44-e1453be7af5a
#jdk.security.krb5.s4u2proxy.acceptNonForwardableServiceTicket=false
+
+#
+# Policy for name comparison in keytab and ccache entry lookup
+#
+# When looking up a keytab or credentials cache (ccache) entry for a Kerberos
+# principal, the principal name is compared with the name in the entry.
+# The comparison is by default case-insensitive. However, many Kerberos
+# implementations consider principal names to be case-sensitive. Consequently,
+# if two principals have names that differ only in case, there is a risk that
+# an incorrect keytab or ccache entry might be selected.
+#
+# If this security property is set to "true", the comparison of principal
+# names at keytab and ccache entry lookup is case-sensitive.
+#
+# The default value is "false".
+#
+# If a system property of the same name is also specified, it supersedes the
+# security property value defined here.
+#
+#jdk.security.krb5.name.case.sensitive=false
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java b/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java
index d11c96a6613..130f2c23f86 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2024, 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
@@ -109,6 +109,12 @@ public class PrincipalName implements Cloneable {
public static final String NAME_REALM_SEPARATOR_STR = "@";
public static final String REALM_COMPONENT_SEPARATOR_STR = ".";
+ private static final boolean NAME_CASE_SENSITIVE_IN_MATCH
+ = "true".equalsIgnoreCase(
+ SecurityProperties.privilegedGetOverridable(
+ "jdk.security.krb5.name.case.sensitive"));
+
+
// Instance fields.
/**
@@ -607,33 +613,47 @@ public class PrincipalName implements Cloneable {
/**
- * Checks if two PrincipalName
objects have identical values in their corresponding data fields.
+ * Checks if two PrincipalName
objects have identical values
+ * in their corresponding data fields.
+ *
+ * If {@systemProperty jdk.security.krb5.name.case.sensitive} is set to true, + * the name comparison is case-sensitive. Otherwise, it's case-insensitive. + *
+ * It is used in {@link sun.security.krb5.internal.ccache.FileCredentialsCache}
+ * and {@link sun.security.krb5.internal.ktab.KeyTab} to retrieve ccache
+ * or keytab entry for a principal.
*
* @param pname the other PrincipalName
object.
* @return true if two have identical values, otherwise, return false.
*/
- // It is used in sun.security.krb5.internal.ccache
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)) {
+ // No need to check name type. It's just a hint, no two names can be
+ // the same ignoring name type.
+ if (NAME_CASE_SENSITIVE_IN_MATCH) {
+ if (!(this.nameRealm.toString().equals(pname.nameRealm.toString()))) {
+ return false;
+ }
+ } else {
if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) {
- matched = false;
+ return false;
}
}
if (this.nameStrings.length != pname.nameStrings.length) {
- matched = false;
+ return false;
} else {
for (int i = 0; i < this.nameStrings.length; i++) {
- if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) {
- matched = false;
+ if (NAME_CASE_SENSITIVE_IN_MATCH) {
+ if (!(this.nameStrings[i].equals(pname.nameStrings[i]))) {
+ return false;
+ }
+ } else {
+ if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) {
+ return false;
+ }
}
}
}
- return matched;
+ return true;
}
/**
diff --git a/test/jdk/sun/security/krb5/auto/CaseSensitive.java b/test/jdk/sun/security/krb5/auto/CaseSensitive.java
new file mode 100644
index 00000000000..365d8c6c6f0
--- /dev/null
+++ b/test/jdk/sun/security/krb5/auto/CaseSensitive.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2024, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8331975
+ * @summary ensure correct name comparison when a system property is set
+ * @library /test/lib
+ * @compile -XDignore.symbol.file CaseSensitive.java
+ * @run main jdk.test.lib.FileInstaller TestHosts TestHosts
+ * @run main/othervm -Djdk.net.hosts.file=TestHosts CaseSensitive no
+ * @run main/othervm -Djdk.net.hosts.file=TestHosts
+ * -Djdk.security.krb5.name.case.sensitive=true CaseSensitive yes
+ */
+
+import jdk.test.lib.Asserts;
+import org.ietf.jgss.GSSException;
+import sun.security.jgss.GSSUtil;
+
+public class CaseSensitive {
+
+ public static void main(String[] args) throws Exception {
+ switch (args[0]) {
+ case "yes" -> testSensitive();
+ case "no" -> testInsensitive();
+ }
+ }
+
+ static void testSensitive() throws Exception {
+ var kdc = new OneKDC(null).writeJAASConf();
+ kdc.addPrincipal("hello", "password".toCharArray());
+ kdc.writeKtab(OneKDC.KTAB);
+
+ Context c = Context.fromJAAS("client");
+ Context s = Context.fromJAAS("com.sun.security.jgss.krb5.accept");
+
+ // There is only "hello". Cannot talk to "HELLO"
+ c.startAsClient("HELLO", GSSUtil.GSS_KRB5_MECH_OID);
+ s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+ try {
+ Context.handshake(c, s);
+ throw new RuntimeException("Should not succeed");
+ } catch(GSSException ge) {
+ System.out.println(ge.getMessage());
+ System.out.println("No HELLO in db. Expected");
+ }
+
+ // Add "HELLO". Can talk to "HELLO" now.
+ kdc.addPrincipal("HELLO", "different".toCharArray());
+ kdc.writeKtab(OneKDC.KTAB);
+
+ c.startAsClient("HELLO", GSSUtil.GSS_KRB5_MECH_OID);
+ s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+ Context.handshake(c, s);
+ // Name could be partial without realm, so only compare the beginning
+ Asserts.assertTrue(c.x().getTargName().toString().startsWith("HELLO"),
+ c.x().getTargName().toString());
+ Asserts.assertTrue(s.x().getTargName().toString().startsWith("HELLO"),
+ s.x().getTargName().toString());
+
+ // Can also talk to "hello", which has a different password.
+ c.startAsClient("hello", GSSUtil.GSS_KRB5_MECH_OID);
+ s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+ Context.handshake(c, s);
+ Asserts.assertTrue(c.x().getTargName().toString().startsWith("hello"),
+ c.x().getTargName().toString());
+ Asserts.assertTrue(s.x().getTargName().toString().startsWith("hello"),
+ s.x().getTargName().toString());
+ }
+
+ static void testInsensitive() throws Exception {
+ var kdc = new OneKDC(null).writeJAASConf();
+ kdc.addPrincipal("hello", "password".toCharArray());
+ kdc.writeKtab(OneKDC.KTAB);
+
+ Context c = Context.fromJAAS("client");
+ Context s = Context.fromJAAS("com.sun.security.jgss.krb5.accept");
+
+ // There is only "hello" but we can talk to "HELLO".
+ c.startAsClient("HELLO", GSSUtil.GSS_KRB5_MECH_OID);
+ s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+ Context.handshake(c, s);
+ Asserts.assertTrue(c.x().getTargName().toString().startsWith("HELLO"),
+ c.x().getTargName().toString());
+ Asserts.assertTrue(s.x().getTargName().toString().startsWith("HELLO"),
+ s.x().getTargName().toString());
+ }
+}
diff --git a/test/jdk/sun/security/krb5/auto/KDC.java b/test/jdk/sun/security/krb5/auto/KDC.java
index 6e4bfba61f4..bb744281eb9 100644
--- a/test/jdk/sun/security/krb5/auto/KDC.java
+++ b/test/jdk/sun/security/krb5/auto/KDC.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2024, 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
@@ -143,6 +143,9 @@ public class KDC {
private static final String SUPPORTED_ETYPES
= System.getProperty("kdc.supported.enctypes");
+ private static final boolean NAME_CASE_SENSITIVE
+ = Boolean.getBoolean("jdk.security.krb5.name.case.sensitive");
+
// The native KDC
private final NativeKdc nativeKdc;
@@ -154,27 +157,28 @@ public class KDC {
// Principal db. principal -> pass. A case-insensitive TreeMap is used
// so that even if the client provides a name with different case, the KDC
// can still locate the principal and give back correct salt.
- private TreeMap