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 serviceName = "http" + 57 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + 58 "server.dev.rabbit.hole"; 59 60 // Alias 61 private static final String clientAlias = clientName + 62 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 63 64 // Names + realms 65 private static final String clientKDC1Name = clientAlias.replaceAll( 66 PrincipalName.NAME_REALM_SEPARATOR_STR, "\\\\" + 67 PrincipalName.NAME_REALM_SEPARATOR_STR) + 68 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1; 69 private static final String clientKDC2Name = clientName + 70 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2; 71 private static final String serviceKDC2Name = serviceName + 72 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2; 73 main(String[] args)74 public static void main(String[] args) throws Exception { 75 try { 76 initializeKDCs(); 77 testSubjectCredentials(); 78 testDelegated(); 79 } finally { 80 cleanup(); 81 } 82 } 83 initializeKDCs()84 private static void initializeKDCs() throws Exception { 85 KDC kdc1 = KDC.create(realmKDC1, "localhost", 0, true); 86 kdc1.addPrincipalRandKey(PrincipalName.TGS_DEFAULT_SRV_NAME + 87 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1); 88 kdc1.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME + 89 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1 + 90 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2, 91 password); 92 kdc1.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME + 93 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2, 94 password); 95 96 KDC kdc2 = KDC.create(realmKDC2, "localhost", 0, true); 97 kdc2.addPrincipalRandKey(PrincipalName.TGS_DEFAULT_SRV_NAME + 98 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2); 99 kdc2.addPrincipal(clientKDC2Name, password); 100 kdc2.addPrincipal(serviceName, password); 101 kdc2.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME + 102 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1, 103 password); 104 kdc2.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME + 105 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2 + 106 PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1, 107 password); 108 109 kdc1.registerAlias(clientAlias, kdc2); 110 kdc1.registerAlias(serviceName, kdc2); 111 kdc2.registerAlias(clientAlias, clientKDC2Name); 112 113 Map<String,List<String>> mapKDC2 = new HashMap<>(); 114 mapKDC2.put(serviceName + "@" + realmKDC2, Arrays.asList( 115 new String[]{serviceName + "@" + realmKDC2})); 116 kdc2.setOption(KDC.Option.ALLOW_S4U2PROXY, mapKDC2); 117 118 KDC.saveConfig(krbConfigName, kdc1, kdc2, 119 "forwardable=true"); 120 System.setProperty("java.security.krb5.conf", krbConfigName); 121 } 122 cleanup()123 private static void cleanup() { 124 File f = new File(krbConfigName); 125 if (f.exists()) { 126 f.delete(); 127 } 128 } 129 130 /* 131 * The client subject (whose principal is 132 * test@RABBIT.HOLE@RABBIT.HOLE) will obtain a TGT after 133 * realm referral and name canonicalization (TGT cname 134 * will be test@DEV.RABBIT.HOLE). With this TGT, the client will request 135 * a TGS for service http/server.dev.rabbit.hole@RABBIT.HOLE. After 136 * realm referral, a http/server.dev.rabbit.hole@DEV.RABBIT.HOLE TGS 137 * will be obtained. 138 * 139 * Assert that we get the proper TGT and TGS tickets, and that they are 140 * associated to the client subject. 141 * 142 * Assert that if we request a TGS for the same service again (based on the 143 * original service name), we don't get a new one but the previous, 144 * already in the subject credentials. 145 */ testSubjectCredentials()146 private static void testSubjectCredentials() throws Exception { 147 Subject clientSubject = new Subject(); 148 Context clientContext = Context.fromUserPass(clientSubject, 149 clientKDC1Name, password, false); 150 151 Set<Principal> clientPrincipals = clientSubject.getPrincipals(); 152 if (clientPrincipals.size() != 1) { 153 throw new Exception("Only one client subject principal expected"); 154 } 155 Principal clientPrincipal = clientPrincipals.iterator().next(); 156 if (DEBUG) { 157 System.out.println("Client subject principal: " + 158 clientPrincipal.getName()); 159 } 160 if (!clientPrincipal.getName().equals(clientKDC1Name)) { 161 throw new Exception("Unexpected client subject principal."); 162 } 163 164 clientContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 165 clientContext.take(new byte[0]); 166 Set<KerberosTicket> clientTickets = 167 clientSubject.getPrivateCredentials(KerberosTicket.class); 168 boolean tgtFound = false; 169 boolean tgsFound = false; 170 for (KerberosTicket clientTicket : clientTickets) { 171 String cname = clientTicket.getClient().getName(); 172 String sname = clientTicket.getServer().getName(); 173 if (cname.equals(clientKDC2Name)) { 174 if (sname.equals(PrincipalName.TGS_DEFAULT_SRV_NAME + 175 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + 176 realmKDC2 + PrincipalName.NAME_REALM_SEPARATOR_STR + 177 realmKDC2)) { 178 tgtFound = true; 179 } else if (sname.equals(serviceKDC2Name)) { 180 tgsFound = true; 181 } 182 } 183 if (DEBUG) { 184 System.out.println("Client subject KerberosTicket:"); 185 System.out.println(clientTicket); 186 } 187 } 188 if (!tgtFound || !tgsFound) { 189 throw new Exception("client subject tickets (TGT/TGS) not found."); 190 } 191 int numOfTickets = clientTickets.size(); 192 clientContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 193 clientContext.take(new byte[0]); 194 clientContext.status(); 195 int newNumOfTickets = 196 clientSubject.getPrivateCredentials(KerberosTicket.class).size(); 197 if (DEBUG) { 198 System.out.println("client subject number of tickets: " + 199 numOfTickets); 200 System.out.println("client subject new number of tickets: " + 201 newNumOfTickets); 202 } 203 if (numOfTickets != newNumOfTickets) { 204 throw new Exception("Useless client subject TGS request because" + 205 " TGS was not found in private credentials."); 206 } 207 } 208 209 /* 210 * The server (http/server.dev.rabbit.hole@DEV.RABBIT.HOLE) 211 * will authenticate on itself on behalf of the client 212 * (test@DEV.RABBIT.HOLE). Cross-realm referrals will occur 213 * when requesting different TGTs and TGSs (including the 214 * request for delegated credentials). 215 */ testDelegated()216 private static void testDelegated() throws Exception { 217 Context c = Context.fromUserPass(clientKDC2Name, 218 password, false); 219 c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 220 Context s = Context.fromUserPass(serviceKDC2Name, 221 password, true); 222 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 223 Context.handshake(c, s); 224 Context delegatedContext = s.delegated(); 225 delegatedContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); 226 delegatedContext.x().requestMutualAuth(false); 227 Context s2 = Context.fromUserPass(serviceKDC2Name, 228 password, true); 229 s2.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 230 231 // Test authentication 232 Context.handshake(delegatedContext, s2); 233 if (!delegatedContext.x().isEstablished() || !s2.x().isEstablished()) { 234 throw new Exception("Delegated authentication failed"); 235 } 236 237 // Test identities 238 GSSName contextInitiatorName = delegatedContext.x().getSrcName(); 239 GSSName contextAcceptorName = delegatedContext.x().getTargName(); 240 if (DEBUG) { 241 System.out.println("Context initiator: " + contextInitiatorName); 242 System.out.println("Context acceptor: " + contextAcceptorName); 243 } 244 if (!contextInitiatorName.toString().equals(clientKDC2Name) || 245 !contextAcceptorName.toString().equals(serviceName)) { 246 throw new Exception("Unexpected initiator or acceptor names"); 247 } 248 } 249 } 250