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 8168518 8172017 27 * @summary More krb5 tests 28 * @library ../../../../java/security/testlibrary/ /test/lib 29 * @run main/othervm/timeout=300 ReplayCacheTestProc 30 */ 31 32import java.io.*; 33import java.nio.BufferUnderflowException; 34import java.nio.channels.SeekableByteChannel; 35import java.nio.file.Files; 36import java.nio.file.Paths; 37import java.nio.file.StandardCopyOption; 38import java.nio.file.StandardOpenOption; 39import java.security.MessageDigest; 40import java.security.NoSuchAlgorithmException; 41import java.util.*; 42import java.util.regex.Matcher; 43import java.util.regex.Pattern; 44 45import jdk.test.lib.Platform; 46import sun.security.jgss.GSSUtil; 47import sun.security.krb5.internal.rcache.AuthTime; 48 49/** 50 * This test runs multiple acceptor Procs to mimic AP-REQ replays. 51 * These system properties are supported: 52 * 53 * - test.libs on what types of acceptors to use 54 * Format: CSV of (J|N|N<suffix>=<libname>|J<suffix>=<launcher>) 55 * Default: J,N on Solaris and Linux where N is available, or J 56 * Example: J,N,N14=/krb5-1.14/lib/libgssapi_krb5.so,J8=/java8/bin/java 57 * 58 * - test.runs on manual runs. If empty, a iterate through all pattern 59 * Format: (req# | client# service#) acceptor# expected, ... 60 * Default: null 61 * Example: c0s0Jav,c1s1N14av,r0Jbx means 0th req is new c0->s0 sent to Ja, 62 * 1st req is new c1 to s1 sent to N14a, 63 * 2nd req is old (0th replayed) sent to Jb. 64 * a/b at the end of acceptor is different acceptors of the same lib 65 * 66 * - test.autoruns on number of automatic runs 67 * Format: number 68 * Default: 100 69 */ 70public class ReplayCacheTestProc { 71 72 private static Proc[] pa; // all acceptors 73 private static Proc pi; // the single initiator 74 private static List<Req> reqs = new ArrayList<>(); 75 private static String HOST = "localhost"; 76 77 private static final String SERVICE; 78 private static long uid; 79 private static String cwd; 80 81 static { 82 String tmp = System.getProperty("test.service"); 83 SERVICE = (tmp == null) ? "service" : tmp; 84 uid = jdk.internal.misc.VM.geteuid(); 85 // Where should the rcache be saved. KRB5RCACHEDIR is not 86 // recognized on Solaris (might be supported on Solaris 12), 87 // and directory name is different when launched by root. 88 // See manpage krb5envvar(5) on KRB5RCNAME. 89 if (System.getProperty("os.name").startsWith("SunOS")) { 90 if (uid == 0) { 91 cwd = "/var/krb5/rcache/root/"; 92 } else { 93 cwd = "/var/krb5/rcache/"; 94 } 95 } else { 96 cwd = System.getProperty("user.dir"); 97 } 98 } 99 100 private static MessageDigest md5, sha256; 101 102 static { 103 try { 104 md5 = MessageDigest.getInstance("MD5"); 105 sha256 = MessageDigest.getInstance("SHA-256"); 106 } catch (NoSuchAlgorithmException nsae) { 107 throw new AssertionError("Impossible", nsae); 108 } 109 } 110 111 112 public static void main0(String[] args) throws Exception { 113 System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF); 114 if (args.length == 0) { // The controller 115 int nc = 5; // number of clients 116 int ns = 5; // number of services 117 String[] libs; // available acceptor types: 118 // J: java 119 // J<suffix>=<java launcher>: another java 120 // N: default native lib 121 // N<suffix>=<libname>: another native lib 122 Ex[] result; 123 int numPerType = 2; // number of acceptors per type 124 125 KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true); 126 for (int i=0; i<nc; i++) { 127 kdc.addPrincipal(client(i), OneKDC.PASS); 128 } 129 kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM); 130 for (int i=0; i<ns; i++) { 131 kdc.addPrincipalRandKey(service(i)); 132 } 133 134 kdc.writeKtab(OneKDC.KTAB); 135 KDC.saveConfig(OneKDC.KRB5_CONF, kdc); 136 137 // User-provided libs 138 String userLibs = System.getProperty("test.libs"); 139 140 if (userLibs != null) { 141 libs = userLibs.split(","); 142 } else { 143 if (Platform.isOSX() || Platform.isWindows()) { 144 // macOS uses Heimdal and Windows has no native lib 145 libs = new String[]{"J"}; 146 } else { 147 if (acceptor("N", "sanity").waitFor() != 0) { 148 Proc.d("Native mode sanity check failed, only java"); 149 libs = new String[]{"J"}; 150 } else { 151 libs = new String[]{"J", "N"}; 152 } 153 } 154 } 155 156 pi = Proc.create("ReplayCacheTestProc").debug("C") 157 .args("initiator") 158 .start(); 159 160 int na = libs.length * numPerType; // total number of acceptors 161 pa = new Proc[na]; 162 163 // Acceptors, numPerType for 1st, numForType for 2nd, ... 164 for (int i=0; i<na; i++) { 165 pa[i] = acceptor(libs[i/numPerType], 166 "" + (char)('a' + i%numPerType)); 167 } 168 169 // Manual runs 170 String userRuns = System.getProperty("test.runs"); 171 172 if (userRuns == null) { 173 result = new Ex[Integer.parseInt( 174 System.getProperty("test.autoruns", "100"))]; 175 Random r = new Random(); 176 for (int i = 0; i < result.length; i++) { 177 boolean expected = reqs.isEmpty() || r.nextBoolean(); 178 result[i] = new Ex( 179 i, 180 expected ? 181 req(r.nextInt(nc), r.nextInt(ns)) : 182 r.nextInt(reqs.size()), 183 pa[r.nextInt(na)], 184 expected); 185 } 186 } else if (userRuns.isEmpty()) { 187 int count = 0; 188 result = new Ex[libs.length * libs.length]; 189 for (int i = 0; i < libs.length; i++) { 190 result[count] = new Ex( 191 count, 192 req(0, 0), 193 pa[i * numPerType], 194 true); 195 count++; 196 for (int j = 0; j < libs.length; j++) { 197 if (i == j) { 198 continue; 199 } 200 result[count] = new Ex( 201 count, 202 i, 203 pa[j * numPerType], 204 false); 205 count++; 206 } 207 } 208 } else { 209 String[] runs = userRuns.split(","); 210 result = new Ex[runs.length]; 211 for (int i = 0; i < runs.length; i++) { 212 UserRun run = new UserRun(runs[i]); 213 result[i] = new Ex( 214 i, 215 run.req() == -1 ? 216 req(run.client(), run.service()) : 217 result[run.req()].req, 218 Arrays.stream(pa) 219 .filter(p -> p.debug().equals(run.acceptor())) 220 .findFirst() 221 .orElseThrow(() -> new Exception( 222 "no acceptor named " + run.acceptor())), 223 run.success()); 224 } 225 } 226 227 for (Ex x : result) { 228 x.run(); 229 } 230 231 pi.println("END"); 232 for (int i=0; i<na; i++) { 233 pa[i].println("END"); 234 } 235 System.out.println("\nAll Test Results\n================"); 236 boolean finalOut = true; 237 System.out.println(" req** client service acceptor Result"); 238 System.out.println("---- ------- ------ --------- -------- -------"); 239 for (int i=0; i<result.length; i++) { 240 boolean out = result[i].expected==result[i].actual; 241 finalOut &= out; 242 System.out.printf("%3d: %3d%s c%d s%d %4s %8s %s %s\n", 243 i, 244 result[i].req, 245 result[i].expected ? "**" : " ", 246 reqs.get(result[i].req).client, 247 reqs.get(result[i].req).service, 248 "(" + result[i].csize + ")", 249 result[i].acceptor.debug(), 250 result[i].actual ? "++" : "--", 251 out ? " " : "xxx"); 252 } 253 254 System.out.println("\nPath of Reqs\n============"); 255 for (int j=0; ; j++) { 256 boolean found = false; 257 for (int i=0; i<result.length; i++) { 258 if (result[i].req == j) { 259 if (!found) { 260 System.out.printf("%3d (c%s -> s%s): ", j, 261 reqs.get(j).client, reqs.get(j).service); 262 } 263 System.out.printf("%s%s(%d)%s", 264 found ? " -> " : "", 265 result[i].acceptor.debug(), 266 i, 267 result[i].actual != result[i].expected ? 268 "xxx" : ""); 269 found = true; 270 } 271 } 272 System.out.println(); 273 if (!found) { 274 break; 275 } 276 } 277 if (!finalOut) throw new Exception(); 278 } else if (args[0].equals("Nsanity")) { 279 // Native mode sanity check 280 Proc.d("Detect start"); 281 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 282 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 283 } else if (args[0].equals("initiator")) { 284 while (true) { 285 String title = Proc.textIn(); 286 Proc.d("Client see " + title); 287 if (title.equals("END")) break; 288 String[] cas = title.split(" "); 289 Context c = Context.fromUserPass(cas[0], OneKDC.PASS, false); 290 c.startAsClient(cas[1], GSSUtil.GSS_KRB5_MECH_OID); 291 c.x().requestCredDeleg(true); 292 byte[] token = c.take(new byte[0]); 293 Proc.d("Client AP-REQ generated"); 294 Proc.binOut(token); 295 } 296 } else { 297 Proc.d(System.getProperty("java.vm.version")); 298 Proc.d(System.getProperty("sun.security.jgss.native")); 299 Proc.d(System.getProperty("sun.security.jgss.lib")); 300 Proc.d("---------------------------------\n"); 301 Proc.d("Server start"); 302 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 303 Proc.d("Server login"); 304 while (true) { 305 String title = Proc.textIn(); 306 Proc.d("Server sees " + title); 307 if (title.equals("END")) break; 308 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 309 byte[] token = Proc.binIn(); 310 try { 311 s.take(token); 312 Proc.textOut("true"); 313 Proc.d("Good"); 314 } catch (Exception e) { 315 Proc.textOut("false"); 316 Proc.d("Bad"); 317 } 318 } 319 } 320 } 321 322 public static void main(String[] args) throws Exception { 323 try { 324 main0(args); 325 } catch (Exception e) { 326 Proc.d(e); 327 throw e; 328 } 329 } 330 331 // returns the client name 332 private static String client(int p) { 333 return "client" + p; 334 } 335 336 // returns the service name 337 private static String service(int p) { 338 return SERVICE + p + "/" + HOST; 339 } 340 341 // returns the dfl name for a service 342 private static String dfl(int p) { 343 return SERVICE + p + (uid == -1 ? "" : ("_"+uid)); 344 } 345 346 // generates an ap-req and save into reqs, returns the index 347 private static int req(int client, int service) throws Exception { 348 pi.println(client(client) + " " + service(service)); 349 Req req = new Req(client, service, pi.readData()); 350 reqs.add(req); 351 return reqs.size() - 1; 352 } 353 354 // create a acceptor 355 private static Proc acceptor(String type, String suffix) throws Exception { 356 Proc p; 357 String label; 358 String lib; 359 int pos = type.indexOf('='); 360 if (pos < 0) { 361 label = type; 362 lib = null; 363 } else { 364 label = type.substring(0, pos); 365 lib = type.substring(pos + 1); 366 } 367 if (type.startsWith("J")) { 368 if (lib == null) { 369 p = Proc.create("ReplayCacheTestProc"); 370 } else { 371 p = Proc.create("ReplayCacheTestProc", lib); 372 } 373 p.prop("sun.security.krb5.rcache", "dfl") 374 .prop("java.io.tmpdir", cwd); 375 String useMD5 = System.getProperty("jdk.krb5.rcache.useMD5"); 376 if (useMD5 != null) { 377 p.prop("jdk.krb5.rcache.useMD5", useMD5); 378 } 379 } else { 380 p = Proc.create("ReplayCacheTestProc") 381 .env("KRB5_CONFIG", OneKDC.KRB5_CONF) 382 .env("KRB5_KTNAME", OneKDC.KTAB) 383 .env("KRB5RCACHEDIR", cwd) 384 .prop("sun.security.jgss.native", "true") 385 .prop("javax.security.auth.useSubjectCredsOnly", "false") 386 .prop("sun.security.nativegss.debug", "true"); 387 if (lib != null) { 388 String libDir = lib.substring(0, lib.lastIndexOf('/')); 389 p.prop("sun.security.jgss.lib", lib) 390 .env("DYLD_LIBRARY_PATH", libDir) 391 .env("LD_LIBRARY_PATH", libDir); 392 } 393 } 394 Proc.d(label+suffix+" started"); 395 return p.args(label+suffix).debug(label+suffix).start(); 396 } 397 398 // generates hash of authenticator inside ap-req inside initsectoken 399 private static void record(String label, Req req) throws Exception { 400 byte[] data = Base64.getDecoder().decode(req.msg); 401 data = Arrays.copyOfRange(data, 17, data.length); 402 403 try (PrintStream ps = new PrintStream( 404 new FileOutputStream("log.txt", true))) { 405 ps.printf("%s:\nmsg: %s\nMD5: %s\nSHA-256: %s\n\n", 406 label, 407 req.msg, 408 hex(md5.digest(data)), 409 hex(sha256.digest(data))); 410 } 411 } 412 413 // Returns a compact hexdump for a byte array 414 private static String hex(byte[] hash) { 415 char[] h = new char[hash.length * 2]; 416 char[] hexConst = "0123456789ABCDEF".toCharArray(); 417 for (int i=0; i<hash.length; i++) { 418 h[2*i] = hexConst[(hash[i]&0xff)>>4]; 419 h[2*i+1] = hexConst[hash[i]&0xf]; 420 } 421 return new String(h); 422 } 423 424 // return size of dfl file, excluding the null hash ones 425 private static int csize(int p) throws Exception { 426 try (SeekableByteChannel chan = Files.newByteChannel( 427 Paths.get(cwd, dfl(p)), StandardOpenOption.READ)) { 428 chan.position(6); 429 int cc = 0; 430 while (true) { 431 try { 432 if (AuthTime.readFrom(chan) != null) cc++; 433 } catch (BufferUnderflowException e) { 434 break; 435 } 436 } 437 return cc; 438 } catch (IOException ioe) { 439 return 0; 440 } 441 } 442 443 // models an experiement 444 private static class Ex { 445 int i; // # 446 int req; // which ap-req to send 447 Proc acceptor; // which acceptor to send to 448 boolean expected; // expected result 449 450 boolean actual; // actual output 451 int csize; // size of rcache after test 452 String hash; // the hash of req 453 454 Ex(int i, int req, Proc acceptor, boolean expected) { 455 this.i = i; 456 this.req = req; 457 this.acceptor = acceptor; 458 this.expected = expected; 459 } 460 461 void run() throws Exception { 462 Req r = reqs.get(req); 463 acceptor.println("TEST"); 464 acceptor.println(r.msg); 465 String reply = acceptor.readData(); 466 467 actual = Boolean.valueOf(reply); 468 csize = csize(r.service); 469 470 String label = String.format("%03d-client%d-%s%d-%s-%s", 471 i, r.client, SERVICE, r.service, acceptor.debug(), actual); 472 473 record(label, r); 474 if (new File(cwd, dfl(r.service)).exists()) { 475 Files.copy(Paths.get(cwd, dfl(r.service)), Paths.get(label), 476 StandardCopyOption.COPY_ATTRIBUTES); 477 } 478 } 479 } 480 481 // models a saved ap-req msg 482 private static class Req { 483 String msg; // based64-ed req 484 int client; // which client 485 int service; // which service 486 Req(int client, int service, String msg) { 487 this.msg = msg; 488 this.client= client; 489 this.service = service; 490 } 491 } 492 493 private static class UserRun { 494 static final Pattern p 495 = Pattern.compile("(c(\\d)+s(\\d+)|r(\\d+))(.*)(.)"); 496 final Matcher m; 497 498 UserRun(String run) { m = p.matcher(run); m.find(); } 499 500 int req() { return group(4); } 501 int client() { return group(2); } 502 int service() { return group(3); } 503 String acceptor() { return m.group(5); } 504 boolean success() { return m.group(6).equals("v"); } 505 506 int group(int i) { 507 String g = m.group(i); 508 return g == null ? -1 : Integer.parseInt(g); 509 } 510 } 511} 512