Skip to content

Commit da3001d

Browse files
committed
8331975: Enable case-insensitive check in ccache and keytab entry lookup
Reviewed-by: mpowers, valeriep
1 parent 424eb60 commit da3001d

File tree

4 files changed

+178
-26
lines changed

4 files changed

+178
-26
lines changed

src/java.base/share/conf/security/java.security

+20
Original file line numberDiff line numberDiff line change
@@ -1515,3 +1515,23 @@ jdk.tls.alpnCharset=ISO_8859_1
15151515
#
15161516
# [1] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/bde93b0e-f3c9-4ddf-9f44-e1453be7af5a
15171517
#jdk.security.krb5.s4u2proxy.acceptNonForwardableServiceTicket=false
1518+
1519+
#
1520+
# Policy for name comparison in keytab and ccache entry lookup
1521+
#
1522+
# When looking up a keytab or credentials cache (ccache) entry for a Kerberos
1523+
# principal, the principal name is compared with the name in the entry.
1524+
# The comparison is by default case-insensitive. However, many Kerberos
1525+
# implementations consider principal names to be case-sensitive. Consequently,
1526+
# if two principals have names that differ only in case, there is a risk that
1527+
# an incorrect keytab or ccache entry might be selected.
1528+
#
1529+
# If this security property is set to "true", the comparison of principal
1530+
# names at keytab and ccache entry lookup is case-sensitive.
1531+
#
1532+
# The default value is "false".
1533+
#
1534+
# If a system property of the same name is also specified, it supersedes the
1535+
# security property value defined here.
1536+
#
1537+
#jdk.security.krb5.name.case.sensitive=false

src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java

+34-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -109,6 +109,12 @@ public class PrincipalName implements Cloneable {
109109
public static final String NAME_REALM_SEPARATOR_STR = "@";
110110
public static final String REALM_COMPONENT_SEPARATOR_STR = ".";
111111

112+
private static final boolean NAME_CASE_SENSITIVE_IN_MATCH
113+
= "true".equalsIgnoreCase(
114+
SecurityProperties.privilegedGetOverridable(
115+
"jdk.security.krb5.name.case.sensitive"));
116+
117+
112118
// Instance fields.
113119

114120
/**
@@ -607,33 +613,47 @@ public byte[] asn1Encode() throws Asn1Exception, IOException {
607613

608614

609615
/**
610-
* Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields.
616+
* Checks if two <code>PrincipalName</code> objects have identical values
617+
* in their corresponding data fields.
618+
* <p>
619+
* If {@systemProperty jdk.security.krb5.name.case.sensitive} is set to true,
620+
* the name comparison is case-sensitive. Otherwise, it's case-insensitive.
621+
* <p>
622+
* It is used in {@link sun.security.krb5.internal.ccache.FileCredentialsCache}
623+
* and {@link sun.security.krb5.internal.ktab.KeyTab} to retrieve ccache
624+
* or keytab entry for a principal.
611625
*
612626
* @param pname the other <code>PrincipalName</code> object.
613627
* @return true if two have identical values, otherwise, return false.
614628
*/
615-
// It is used in <code>sun.security.krb5.internal.ccache</code> package.
616629
public boolean match(PrincipalName pname) {
617-
boolean matched = true;
618-
//name type is just a hint, no two names can be the same ignoring name type.
619-
// if (this.nameType != pname.nameType) {
620-
// matched = false;
621-
// }
622-
if ((this.nameRealm != null) && (pname.nameRealm != null)) {
630+
// No need to check name type. It's just a hint, no two names can be
631+
// the same ignoring name type.
632+
if (NAME_CASE_SENSITIVE_IN_MATCH) {
633+
if (!(this.nameRealm.toString().equals(pname.nameRealm.toString()))) {
634+
return false;
635+
}
636+
} else {
623637
if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) {
624-
matched = false;
638+
return false;
625639
}
626640
}
627641
if (this.nameStrings.length != pname.nameStrings.length) {
628-
matched = false;
642+
return false;
629643
} else {
630644
for (int i = 0; i < this.nameStrings.length; i++) {
631-
if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) {
632-
matched = false;
645+
if (NAME_CASE_SENSITIVE_IN_MATCH) {
646+
if (!(this.nameStrings[i].equals(pname.nameStrings[i]))) {
647+
return false;
648+
}
649+
} else {
650+
if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) {
651+
return false;
652+
}
633653
}
634654
}
635655
}
636-
return matched;
656+
return true;
637657
}
638658

639659
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8331975
27+
* @summary ensure correct name comparison when a system property is set
28+
* @library /test/lib
29+
* @compile -XDignore.symbol.file CaseSensitive.java
30+
* @run main jdk.test.lib.FileInstaller TestHosts TestHosts
31+
* @run main/othervm -Djdk.net.hosts.file=TestHosts CaseSensitive no
32+
* @run main/othervm -Djdk.net.hosts.file=TestHosts
33+
* -Djdk.security.krb5.name.case.sensitive=true CaseSensitive yes
34+
*/
35+
36+
import jdk.test.lib.Asserts;
37+
import org.ietf.jgss.GSSException;
38+
import sun.security.jgss.GSSUtil;
39+
40+
public class CaseSensitive {
41+
42+
public static void main(String[] args) throws Exception {
43+
switch (args[0]) {
44+
case "yes" -> testSensitive();
45+
case "no" -> testInsensitive();
46+
}
47+
}
48+
49+
static void testSensitive() throws Exception {
50+
var kdc = new OneKDC(null).writeJAASConf();
51+
kdc.addPrincipal("hello", "password".toCharArray());
52+
kdc.writeKtab(OneKDC.KTAB);
53+
54+
Context c = Context.fromJAAS("client");
55+
Context s = Context.fromJAAS("com.sun.security.jgss.krb5.accept");
56+
57+
// There is only "hello". Cannot talk to "HELLO"
58+
c.startAsClient("HELLO", GSSUtil.GSS_KRB5_MECH_OID);
59+
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
60+
try {
61+
Context.handshake(c, s);
62+
throw new RuntimeException("Should not succeed");
63+
} catch(GSSException ge) {
64+
System.out.println(ge.getMessage());
65+
System.out.println("No HELLO in db. Expected");
66+
}
67+
68+
// Add "HELLO". Can talk to "HELLO" now.
69+
kdc.addPrincipal("HELLO", "different".toCharArray());
70+
kdc.writeKtab(OneKDC.KTAB);
71+
72+
c.startAsClient("HELLO", GSSUtil.GSS_KRB5_MECH_OID);
73+
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
74+
Context.handshake(c, s);
75+
// Name could be partial without realm, so only compare the beginning
76+
Asserts.assertTrue(c.x().getTargName().toString().startsWith("HELLO"),
77+
c.x().getTargName().toString());
78+
Asserts.assertTrue(s.x().getTargName().toString().startsWith("HELLO"),
79+
s.x().getTargName().toString());
80+
81+
// Can also talk to "hello", which has a different password.
82+
c.startAsClient("hello", GSSUtil.GSS_KRB5_MECH_OID);
83+
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
84+
Context.handshake(c, s);
85+
Asserts.assertTrue(c.x().getTargName().toString().startsWith("hello"),
86+
c.x().getTargName().toString());
87+
Asserts.assertTrue(s.x().getTargName().toString().startsWith("hello"),
88+
s.x().getTargName().toString());
89+
}
90+
91+
static void testInsensitive() throws Exception {
92+
var kdc = new OneKDC(null).writeJAASConf();
93+
kdc.addPrincipal("hello", "password".toCharArray());
94+
kdc.writeKtab(OneKDC.KTAB);
95+
96+
Context c = Context.fromJAAS("client");
97+
Context s = Context.fromJAAS("com.sun.security.jgss.krb5.accept");
98+
99+
// There is only "hello" but we can talk to "HELLO".
100+
c.startAsClient("HELLO", GSSUtil.GSS_KRB5_MECH_OID);
101+
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
102+
Context.handshake(c, s);
103+
Asserts.assertTrue(c.x().getTargName().toString().startsWith("HELLO"),
104+
c.x().getTargName().toString());
105+
Asserts.assertTrue(s.x().getTargName().toString().startsWith("HELLO"),
106+
s.x().getTargName().toString());
107+
}
108+
}

test/jdk/sun/security/krb5/auto/KDC.java

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -143,6 +143,9 @@ public class KDC {
143143
private static final String SUPPORTED_ETYPES
144144
= System.getProperty("kdc.supported.enctypes");
145145

146+
private static final boolean NAME_CASE_SENSITIVE
147+
= Boolean.getBoolean("jdk.security.krb5.name.case.sensitive");
148+
146149
// The native KDC
147150
private final NativeKdc nativeKdc;
148151

@@ -154,27 +157,28 @@ public class KDC {
154157
// Principal db. principal -> pass. A case-insensitive TreeMap is used
155158
// so that even if the client provides a name with different case, the KDC
156159
// can still locate the principal and give back correct salt.
157-
private TreeMap<String,char[]> passwords = new TreeMap<>
158-
(String.CASE_INSENSITIVE_ORDER);
160+
private TreeMap<String,char[]> passwords = newTreeMap();
159161

160162
// Non default salts. Precisely, there should be different salts for
161163
// different etypes, pretend they are the same at the moment.
162-
private TreeMap<String,String> salts = new TreeMap<>
163-
(String.CASE_INSENSITIVE_ORDER);
164+
private TreeMap<String,String> salts = newTreeMap();
164165

165166
// Non default s2kparams for newer etypes. Precisely, there should be
166167
// different s2kparams for different etypes, pretend they are the same
167168
// at the moment.
168-
private TreeMap<String,byte[]> s2kparamses = new TreeMap<>
169-
(String.CASE_INSENSITIVE_ORDER);
169+
private TreeMap<String,byte[]> s2kparamses = newTreeMap();
170170

171171
// Alias for referrals.
172-
private TreeMap<String,KDC> aliasReferrals = new TreeMap<>
173-
(String.CASE_INSENSITIVE_ORDER);
172+
private TreeMap<String,KDC> aliasReferrals = newTreeMap();
174173

175174
// Alias for local resolution.
176-
private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<>
177-
(String.CASE_INSENSITIVE_ORDER);
175+
private TreeMap<String,PrincipalName> alias2Principals = newTreeMap();
176+
177+
private static <T> TreeMap<String,T> newTreeMap() {
178+
return NAME_CASE_SENSITIVE
179+
? new TreeMap<>()
180+
: new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
181+
}
178182

179183
// Realm name
180184
private String realm;
@@ -354,7 +358,7 @@ public void writeKtab(String tab, boolean append, String... names)
354358
}
355359
if (nativeKdc == null) {
356360
char[] pass = passwords.get(name);
357-
int kvno = 0;
361+
int kvno = -1; // always create new keys
358362
if (Character.isDigit(pass[pass.length - 1])) {
359363
kvno = pass[pass.length - 1] - '0';
360364
}

0 commit comments

Comments
 (0)