1 /* 2 * Copyright (c) 2013, 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 7152176 27 * @summary More krb5 tests 28 * @library ../../../../java/security/testlibrary/ 29 * @modules java.base/sun.misc 30 * java.base/sun.net.spi.nameservice 31 * java.base/sun.security.util 32 * java.security.jgss/sun.security.jgss 33 * java.security.jgss/sun.security.krb5 34 * java.security.jgss/sun.security.krb5.internal 35 * java.security.jgss/sun.security.krb5.internal.ccache 36 * java.security.jgss/sun.security.krb5.internal.crypto 37 * java.security.jgss/sun.security.krb5.internal.ktab 38 * java.security.jgss/sun.security.krb5.internal.rcache 39 * @compile -XDignore.symbol.file ReplayCacheTestProc.java 40 * @run main/othervm/timeout=100 ReplayCacheTestProc 41 */ 42 43 import java.io.*; 44 import java.nio.BufferUnderflowException; 45 import java.nio.channels.SeekableByteChannel; 46 import java.nio.file.Files; 47 import java.nio.file.Paths; 48 import java.nio.file.StandardCopyOption; 49 import java.nio.file.StandardOpenOption; 50 import java.security.MessageDigest; 51 import java.util.*; 52 53 import sun.security.jgss.GSSUtil; 54 import sun.security.krb5.internal.APReq; 55 import sun.security.krb5.internal.rcache.AuthTime; 56 57 // This test runs multiple acceptor Procs to mimin AP-REQ replays. 58 public class ReplayCacheTestProc { 59 60 private static Proc[] ps; 61 private static Proc pc; 62 private static List<Req> reqs = new ArrayList<>(); 63 private static String HOST = "localhost"; 64 65 // Where should the rcache be saved. It seems KRB5RCACHEDIR is not 66 // recognized on Solaris. Maybe version too low? I see 1.6. 67 private static String cwd = 68 System.getProperty("os.name").startsWith("SunOS") ? 69 "/var/krb5/rcache/" : 70 System.getProperty("user.dir"); 71 72 73 private static long uid; 74 75 public static void main0(String[] args) throws Exception { 76 System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF); 77 if (args.length == 0) { // The controller 78 int ns = 5; // number of servers 79 int nu = 5; // number of users 80 int nx = 50; // number of experiments 81 int np = 5; // number of peers (services) 82 int mode = 0; // native(1), random(0), java(-1) 83 boolean random = true; // random experiments choreograph 84 85 // Do not test interop with native GSS on some platforms 86 String os = System.getProperty("os.name", "???"); 87 if (!os.startsWith("SunOS") && !os.startsWith("Linux")) { 88 mode = -1; 89 } 90 91 uid = sun.misc.VM.geteuid(); 92 93 KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true); 94 for (int i=0; i<nu; i++) { 95 kdc.addPrincipal(user(i), OneKDC.PASS); 96 } 97 kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM); 98 for (int i=0; i<np; i++) { 99 kdc.addPrincipalRandKey(peer(i)); 100 } 101 102 kdc.writeKtab(OneKDC.KTAB); 103 KDC.saveConfig(OneKDC.KRB5_CONF, kdc); 104 105 if (mode != -1) { 106 // A special native server to check basic sanity 107 if (ns(-1).waitFor() != 0) { 108 Proc.d("Native mode sanity check failed, revert to java"); 109 mode = -1; 110 } 111 } 112 113 pc = Proc.create("ReplayCacheTestProc").debug("C") 114 .args("client") 115 .start(); 116 ps = new Proc[ns]; 117 Ex[] result = new Ex[nx]; 118 119 if (!random) { 120 // 2 experiments, 2 server, 1 peer, 1 user 121 nx = 2; ns = 2; np = 1; nu = 1; 122 123 // Creates reqs from user# to peer# 124 req(0, 0); 125 126 // Creates server# 127 ps[0] = ns(0); 128 ps[1] = js(1); 129 130 // Runs ex# using req# to server# with expected result 131 result[0] = round(0, 0, 0, true); 132 result[1] = round(1, 0, 1, false); 133 } else { 134 Random r = new Random(); 135 for (int i=0; i<ns; i++) { 136 boolean useNative = (mode == 1) ? true 137 : (mode == -1 ? false : r.nextBoolean()); 138 ps[i] = useNative?ns(i):js(i); 139 } 140 for (int i=0; i<nx; i++) { 141 result[i] = new Ex(); 142 int old; // which req to send 143 boolean expected; 144 if (reqs.isEmpty() || r.nextBoolean()) { 145 Proc.d("Console get new AP-REQ"); 146 old = req(r.nextInt(nu), r.nextInt(np)); 147 expected = true; 148 } else { 149 Proc.d("Console resue old"); 150 old = r.nextInt(reqs.size()); 151 expected = false; 152 } 153 int s = r.nextInt(ns); 154 Proc.d("Console send to " + s); 155 result[i] = round(i, old, s, expected); 156 Proc.d("Console sees " + result[i].actual); 157 } 158 } 159 160 pc.println("END"); 161 for (int i=0; i<ns; i++) { 162 ps[i].println("END"); 163 } 164 System.out.println("Result\n======"); 165 boolean finalOut = true; 166 for (int i=0; i<nx; i++) { 167 boolean out = result[i].expected==result[i].actual; 168 finalOut &= out; 169 System.out.printf("%3d: %s (%2d): u%d h%d %s %s %s %2d\n", 170 i, 171 result[i].expected?"----":" ", 172 result[i].old, 173 result[i].user, result[i].peer, result[i].server, 174 result[i].actual?"Good":"Bad ", 175 out?" ":"xxx", 176 result[i].csize); 177 } 178 if (!finalOut) throw new Exception(); 179 } else if (args[0].equals("N-1")) { 180 // Native mode sanity check 181 Proc.d("Detect start"); 182 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 183 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 184 } else if (args[0].equals("client")) { 185 while (true) { 186 String title = Proc.textIn(); 187 Proc.d("Client see " + title); 188 if (title.equals("END")) break; 189 String[] cas = title.split(" "); 190 Context c = Context.fromUserPass(cas[0], OneKDC.PASS, false); 191 c.startAsClient(cas[1], GSSUtil.GSS_KRB5_MECH_OID); 192 c.x().requestCredDeleg(true); 193 byte[] token = c.take(new byte[0]); 194 Proc.d("Client AP-REQ generated"); 195 Proc.binOut(token); 196 } 197 } else { 198 Proc.d("Server start"); 199 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 200 Proc.d("Server login"); 201 while (true) { 202 String title = Proc.textIn(); 203 Proc.d("Server " + args[0] + " sees " + title); 204 if (title.equals("END")) break; 205 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 206 byte[] token = Proc.binIn(); 207 try { 208 s.take(token); 209 Proc.textOut("true"); 210 Proc.d(args[0] + " Good"); 211 } catch (Exception e) { 212 Proc.textOut("false"); 213 Proc.d(args[0] + " Bad"); 214 } 215 } 216 } 217 } 218 219 public static void main(String[] args) throws Exception { 220 try { 221 main0(args); 222 } catch (Exception e) { 223 Proc.d(e); 224 throw e; 225 } 226 } 227 228 // returns the user name 229 private static String user(int p) { 230 return "USER" + p; 231 } 232 // returns the peer name 233 private static String peer(int p) { 234 return "host" + p + "/" + HOST; 235 } 236 // returns the dfl name for a host 237 private static String dfl(int p) { 238 return cwd + "host" + p + (uid == -1 ? "" : ("_"+uid)); 239 } 240 // generates an ap-req and save into reqs, returns the index 241 private static int req(int user, int peer) throws Exception { 242 pc.println(user(user) + " " + peer(peer)); 243 Req req = new Req(user, peer, pc.readData()); 244 reqs.add(req); 245 return reqs.size() - 1; 246 } 247 // carries out a round of experiment 248 // i: ex#, old: which req, server: which server, expected: result? 249 private static Ex round(int i, int old, int server, boolean expected) 250 throws Exception { 251 ps[server].println("TEST"); 252 ps[server].println(reqs.get(old).msg); 253 String reply = ps[server].readData(); 254 Ex result = new Ex(); 255 result.i = i; 256 result.expected = expected; 257 result.server = ps[server].debug(); 258 result.actual = Boolean.valueOf(reply); 259 result.user = reqs.get(old).user; 260 result.peer = reqs.get(old).peer; 261 result.old = old; 262 result.csize = csize(result.peer); 263 result.hash = hash(reqs.get(old).msg); 264 if (new File(dfl(result.peer)).exists()) { 265 Files.copy(Paths.get(dfl(result.peer)), Paths.get( 266 String.format("%03d-USER%d-host%d-%s-%s", 267 i, result.user, result.peer, result.server, 268 result.actual) 269 + "-" + result.hash), 270 StandardCopyOption.COPY_ATTRIBUTES); 271 } 272 return result; 273 } 274 // create a native server 275 private static Proc ns(int i) throws Exception { 276 return Proc.create("ReplayCacheTestProc") 277 .args("N"+i) 278 .env("KRB5_CONFIG", OneKDC.KRB5_CONF) 279 .env("KRB5_KTNAME", OneKDC.KTAB) 280 .env("KRB5RCACHEDIR", cwd) 281 .prop("sun.security.jgss.native", "true") 282 .prop("javax.security.auth.useSubjectCredsOnly", "false") 283 .prop("sun.security.nativegss.debug", "true") 284 .debug("N"+i) 285 .start(); 286 } 287 // creates a java server 288 private static Proc js(int i) throws Exception { 289 return Proc.create("ReplayCacheTestProc") 290 .debug("S"+i) 291 .args("S"+i) 292 .prop("sun.security.krb5.rcache", "dfl") 293 .prop("java.io.tmpdir", cwd) 294 .start(); 295 } 296 // generates hash of authenticator inside ap-req inside initsectoken 297 private static String hash(String req) throws Exception { 298 byte[] data = Base64.getDecoder().decode(req); 299 data = Arrays.copyOfRange(data, 17, data.length); 300 byte[] hash = MessageDigest.getInstance("MD5").digest(new APReq(data).authenticator.getBytes()); 301 char[] h = new char[hash.length * 2]; 302 char[] hexConst = "0123456789ABCDEF".toCharArray(); 303 for (int i=0; i<hash.length; i++) { 304 h[2*i] = hexConst[(hash[i]&0xff)>>4]; 305 h[2*i+1] = hexConst[hash[i]&0xf]; 306 } 307 return new String(h); 308 } 309 // return size of dfl file, excluding the null hash ones 310 private static int csize(int p) throws Exception { 311 try (SeekableByteChannel chan = Files.newByteChannel( 312 Paths.get(dfl(p)), StandardOpenOption.READ)) { 313 chan.position(6); 314 int cc = 0; 315 while (true) { 316 try { 317 if (AuthTime.readFrom(chan) != null) cc++; 318 } catch (BufferUnderflowException e) { 319 break; 320 } 321 } 322 return cc; 323 } catch (IOException ioe) { 324 return 0; 325 } 326 } 327 // models an experiement 328 private static class Ex { 329 int i; // # 330 boolean expected; // expected result 331 boolean actual; // actual output 332 int old; // which ap-req to send 333 String server; // which server to send to 334 String hash; // the hash of req 335 int user; // which initiator 336 int peer; // which acceptor 337 int csize; // size of rcache after test 338 } 339 // models a saved ap-req msg 340 private static class Req { 341 String msg; // based64-ed req 342 int user; // which initiator 343 int peer; // which accceptor 344 Req(int user, int peer, String msg) { 345 this.msg = msg; 346 this.user= user; 347 this.peer = peer; 348 } 349 } 350 }