1 /* 2 * Copyright (c) 2019, Red Hat, Inc. 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 8215032 27 * @library /test/lib 28 * @run main/othervm/timeout=120 -Dsun.security.krb5.debug=true ReferralsTest 29 * @summary Test Kerberos cross-realm referrals (RFC 6806) 30 */ 31 32 import java.io.File; 33 import java.security.Principal; 34 import java.util.Arrays; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import javax.security.auth.kerberos.KerberosTicket; 40 import javax.security.auth.Subject; 41 42 import org.ietf.jgss.GSSName; 43 44 import sun.security.jgss.GSSUtil; 45 import sun.security.krb5.PrincipalName; 46 47 public class ReferralsTest { 48 private static final boolean DEBUG = true; 49 private static final String krbConfigName = "krb5-localkdc.conf"; 50 private static final String realmKDC1 = "RABBIT.HOLE"; 51 private static final String realmKDC2 = "DEV.RABBIT.HOLE"; 52 private static final char[] password = "123qwe@Z".toCharArray(); 53 54 // Names 55 private static final String clientName = "test"; 56 private static final String userName = "user"; 57 private static final String serviceName = "http" + 58 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + 59 "server.dev.rabbit.hole"; 60 private static final String backendServiceName = "cifs" + 61 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + 62 "backend.rabbit.hole"; 63 64 // Alias 65 private static final String clientAlias = clientName + 66 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 67 68 // Names + realms 69 private static final String clientKDC1Name = clientAlias.replaceAll( 70 PrincipalName.NAME_REALM_SEPARATOR_STR, "\\\\" + 71 PrincipalName.NAME_REALM_SEPARATOR_STR) + 72 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 73 private static final String clientKDC2Name = clientName + 74 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2; 75 private static final String userKDC1Name = userName + 76 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 77 private static final String serviceKDC2Name = serviceName + 78 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2; 79 private static final String backendKDC1Name = backendServiceName + 80 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 81 private static final String krbtgtKDC1 = 82 PrincipalName.TGS_DEFAULT_SRV_NAME + 83 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1; 84 private static final String krbtgtKDC2 = 85 PrincipalName.TGS_DEFAULT_SRV_NAME + 86 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2; 87 private static final String krbtgtKDC1toKDC2 = 88 PrincipalName.TGS_DEFAULT_SRV_NAME + 89 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2 + 90 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 91 private static final String krbtgtKDC2toKDC1 = 92 PrincipalName.TGS_DEFAULT_SRV_NAME + 93 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1 + 94 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2; 95 main(String[] args)96 public static void main(String[] args) throws Exception { 97 try { 98 initializeKDCs(); 99 testSubjectCredentials(); 100 testDelegation(); 101 testImpersonation(); 102 testDelegationWithReferrals(); 103 } finally { 104 cleanup(); 105 } 106 } 107 initializeKDCs()108 private static void initializeKDCs() throws Exception { 109 KDC kdc1 = KDC.create(realmKDC1, "localhost", 0, true); 110 kdc1.addPrincipalRandKey(krbtgtKDC1); 111 kdc1.addPrincipal(krbtgtKDC2toKDC1, password); 112 kdc1.addPrincipal(krbtgtKDC2, password); 113 kdc1.addPrincipal(userKDC1Name, password); 114 kdc1.addPrincipal(backendServiceName, password); 115 116 KDC kdc2 = KDC.create(realmKDC2, "localhost", 0, true); 117 kdc2.addPrincipalRandKey(krbtgtKDC2); 118 kdc2.addPrincipal(clientKDC2Name, password); 119 kdc2.addPrincipal(serviceName, password); 120 kdc2.addPrincipal(krbtgtKDC1, password); 121 kdc2.addPrincipal(krbtgtKDC1toKDC2, password); 122 123 kdc1.registerAlias(clientAlias, kdc2); 124 kdc1.registerAlias(serviceName, kdc2); 125 kdc2.registerAlias(clientAlias, clientKDC2Name); 126 kdc2.registerAlias(backendServiceName, kdc1); 127 128 kdc1.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList( 129 new String[]{serviceName + "@" + realmKDC2})); 130 Map<String,List<String>> mapKDC1 = new HashMap<>(); 131 mapKDC1.put(serviceName + "@" + realmKDC2, Arrays.asList( 132 new String[]{backendKDC1Name})); 133 kdc1.setOption(KDC.Option.ALLOW_S4U2PROXY, mapKDC1); 134 135 Map<String,List<String>> mapKDC2 = new HashMap<>(); 136 mapKDC2.put(serviceName + "@" + realmKDC2, Arrays.asList( 137 new String[]{serviceName + "@" + realmKDC2, 138 krbtgtKDC2toKDC1})); 139 kdc2.setOption(KDC.Option.ALLOW_S4U2PROXY, mapKDC2); 140 141 KDC.saveConfig(krbConfigName, kdc1, kdc2, 142 "forwardable=true"); 143 System.setProperty("java.security.krb5.conf", krbConfigName); 144 } 145 cleanup()146 private static void cleanup() { 147 File f = new File(krbConfigName); 148 if (f.exists()) { 149 f.delete(); 150 } 151 } 152 153 /* 154 * The client subject (whose principal is 155 * test@RABBIT.HOLE@RABBIT.HOLE) will obtain a TGT after 156 * realm referral and name canonicalization (TGT cname 157 * will be test@DEV.RABBIT.HOLE). With this TGT, the client will request 158 * a TGS for service http/server.dev.rabbit.hole@RABBIT.HOLE. After 159 * realm referral, a http/server.dev.rabbit.hole@DEV.RABBIT.HOLE TGS 160 * will be obtained. 161 * 162 * Assert that we get the proper TGT and TGS tickets, and that they are 163 * associated to the client subject. 164 * 165 * Assert that if we request a TGS for the same service again (based on the 166 * original service name), we don't get a new one but the previous, 167 * already in the subject credentials. 168 */ testSubjectCredentials()169 private static void testSubjectCredentials() throws Exception { 170 Subject clientSubject = new Subject(); 171 Context clientContext = Context.fromUserPass(clientSubject, 172 clientKDC1Name, password, false); 173 174 Set<Principal> clientPrincipals = clientSubject.getPrincipals(); 175 if (clientPrincipals.size() != 1) { 176 throw new Exception("Only one client subject principal expected"); 177 } 178 Principal clientPrincipal = clientPrincipals.iterator().next(); 179 if (DEBUG) { 180 System.out.println("Client subject principal: " + 181 clientPrincipal.getName()); 182 } 183 if (!clientPrincipal.getName().equals(clientKDC1Name)) { 184 throw new Exception("Unexpected client subject principal."); 185 } 186 187 clientContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 188 clientContext.take(new byte[0]); 189 Set<KerberosTicket> clientTickets = 190 clientSubject.getPrivateCredentials(KerberosTicket.class); 191 boolean tgtFound = false; 192 boolean tgsFound = false; 193 for (KerberosTicket clientTicket : clientTickets) { 194 String cname = clientTicket.getClient().getName(); 195 String sname = clientTicket.getServer().getName(); 196 if (cname.equals(clientKDC2Name)) { 197 if (sname.equals(krbtgtKDC2 + 198 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2)) { 199 tgtFound = true; 200 } else if (sname.equals(serviceKDC2Name)) { 201 tgsFound = true; 202 } 203 } 204 if (DEBUG) { 205 System.out.println("Client subject KerberosTicket:"); 206 System.out.println(clientTicket); 207 } 208 } 209 if (!tgtFound || !tgsFound) { 210 throw new Exception("client subject tickets (TGT/TGS) not found."); 211 } 212 int numOfTickets = clientTickets.size(); 213 clientContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 214 clientContext.take(new byte[0]); 215 clientContext.status(); 216 int newNumOfTickets = 217 clientSubject.getPrivateCredentials(KerberosTicket.class).size(); 218 if (DEBUG) { 219 System.out.println("client subject number of tickets: " + 220 numOfTickets); 221 System.out.println("client subject new number of tickets: " + 222 newNumOfTickets); 223 } 224 if (numOfTickets != newNumOfTickets) { 225 throw new Exception("Useless client subject TGS request because" + 226 " TGS was not found in private credentials."); 227 } 228 } 229 230 /* 231 * The server (http/server.dev.rabbit.hole@DEV.RABBIT.HOLE) 232 * will authenticate on itself on behalf of the client 233 * (test@DEV.RABBIT.HOLE). Cross-realm referrals will occur 234 * when requesting different TGTs and TGSs (including the 235 * request for delegated credentials). 236 */ testDelegation()237 private static void testDelegation() throws Exception { 238 Context c = Context.fromUserPass(clientKDC2Name, 239 password, false); 240 c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 241 Context s = Context.fromUserPass(serviceKDC2Name, 242 password, true); 243 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 244 Context.handshake(c, s); 245 Context delegatedContext = s.delegated(); 246 delegatedContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 247 delegatedContext.x().requestMutualAuth(false); 248 Context s2 = Context.fromUserPass(serviceKDC2Name, 249 password, true); 250 s2.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 251 252 // Test authentication 253 Context.handshake(delegatedContext, s2); 254 if (!delegatedContext.x().isEstablished() || !s2.x().isEstablished()) { 255 throw new Exception("Delegated authentication failed"); 256 } 257 258 // Test identities 259 GSSName contextInitiatorName = delegatedContext.x().getSrcName(); 260 GSSName contextAcceptorName = delegatedContext.x().getTargName(); 261 if (DEBUG) { 262 System.out.println("Context initiator: " + contextInitiatorName); 263 System.out.println("Context acceptor: " + contextAcceptorName); 264 } 265 if (!contextInitiatorName.toString().equals(clientKDC2Name) || 266 !contextAcceptorName.toString().equals(serviceName)) { 267 throw new Exception("Unexpected initiator or acceptor names"); 268 } 269 } 270 271 /* 272 * The server (http/server.dev.rabbit.hole@DEV.RABBIT.HOLE) 273 * will get a TGS ticket for itself on behalf of the client 274 * (user@RABBIT.HOLE). Cross-realm referrals will be handled 275 * in S4U2Self requests because the user and the server are 276 * on different realms. 277 */ testImpersonation()278 private static void testImpersonation() throws Exception { 279 Context s = Context.fromUserPass(serviceKDC2Name, password, true); 280 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 281 GSSName impName = s.impersonate(userKDC1Name).cred().getName(); 282 if (DEBUG) { 283 System.out.println("Impersonated name: " + impName); 284 } 285 if (!impName.toString().equals(userKDC1Name)) { 286 throw new Exception("Unexpected impersonated name"); 287 } 288 } 289 290 /* 291 * The server (http/server.dev.rabbit.hole@DEV.RABBIT.HOLE) 292 * will use delegated credentials (user@RABBIT.HOLE) to 293 * authenticate in the backend (cifs/backend.rabbit.hole@RABBIT.HOLE). 294 * Cross-realm referrals will be handled in S4U2Proxy requests 295 * because the server and the backend are on different realms. 296 */ testDelegationWithReferrals()297 private static void testDelegationWithReferrals() throws Exception { 298 Context c = Context.fromUserPass(userKDC1Name, password, false); 299 c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 300 Context s = Context.fromUserPass(serviceKDC2Name, password, true); 301 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 302 Context.handshake(c, s); 303 Context delegatedContext = s.delegated(); 304 delegatedContext.startAsClient(backendServiceName, 305 GSSUtil.GSS_KRB5_MECH_OID); 306 delegatedContext.x().requestMutualAuth(false); 307 Context b = Context.fromUserPass(backendKDC1Name, password, true); 308 b.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 309 310 // Test authentication 311 Context.handshake(delegatedContext, b); 312 if (!delegatedContext.x().isEstablished() || !b.x().isEstablished()) { 313 throw new Exception("Delegated authentication failed"); 314 } 315 316 // Test identities 317 GSSName contextInitiatorName = delegatedContext.x().getSrcName(); 318 GSSName contextAcceptorName = delegatedContext.x().getTargName(); 319 if (DEBUG) { 320 System.out.println("Context initiator: " + contextInitiatorName); 321 System.out.println("Context acceptor: " + contextAcceptorName); 322 } 323 if (!contextInitiatorName.toString().equals(userKDC1Name) || 324 !contextAcceptorName.toString().equals(backendServiceName)) { 325 throw new Exception("Unexpected initiator or acceptor names"); 326 } 327 } 328 } 329