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