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 cwd = System.getProperty("user.dir"); 93 } 94 95 private static MessageDigest md5, sha256; 96 97 static { 98 try { 99 md5 = MessageDigest.getInstance("MD5"); 100 sha256 = MessageDigest.getInstance("SHA-256"); 101 } catch (NoSuchAlgorithmException nsae) { 102 throw new AssertionError("Impossible", nsae); 103 } 104 } 105 106 107 public static void main0(String[] args) throws Exception { 108 System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF); 109 if (args.length == 0) { // The controller 110 int nc = 5; // number of clients 111 int ns = 5; // number of services 112 String[] libs; // available acceptor types: 113 // J: java 114 // J<suffix>=<java launcher>: another java 115 // N: default native lib 116 // N<suffix>=<libname>: another native lib 117 Ex[] result; 118 int numPerType = 2; // number of acceptors per type 119 120 KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true); 121 for (int i=0; i<nc; i++) { 122 kdc.addPrincipal(client(i), OneKDC.PASS); 123 } 124 kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM); 125 for (int i=0; i<ns; i++) { 126 kdc.addPrincipalRandKey(service(i)); 127 } 128 129 // Native lib might not support aes-sha2 130 KDC.saveConfig(OneKDC.KRB5_CONF, kdc, 131 "default_tkt_enctypes = aes128-cts", 132 "default_tgs_enctypes = aes128-cts"); 133 134 // Write KTAB after krb5.conf so it contains no aes-sha2 keys 135 kdc.writeKtab(OneKDC.KTAB); 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 .inheritProp("jdk.net.hosts.file") 158 .args("initiator") 159 .start(); 160 161 int na = libs.length * numPerType; // total number of acceptors 162 pa = new Proc[na]; 163 164 // Acceptors, numPerType for 1st, numForType for 2nd, ... 165 for (int i=0; i<na; i++) { 166 pa[i] = acceptor(libs[i/numPerType], 167 "" + (char)('a' + i%numPerType)); 168 } 169 170 // Manual runs 171 String userRuns = System.getProperty("test.runs"); 172 173 if (userRuns == null) { 174 result = new Ex[Integer.parseInt( 175 System.getProperty("test.autoruns", "100"))]; 176 Random r = new Random(); 177 for (int i = 0; i < result.length; i++) { 178 boolean expected = reqs.isEmpty() || r.nextBoolean(); 179 result[i] = new Ex( 180 i, 181 expected ? 182 req(r.nextInt(nc), r.nextInt(ns)) : 183 r.nextInt(reqs.size()), 184 pa[r.nextInt(na)], 185 expected); 186 } 187 } else if (userRuns.isEmpty()) { 188 int count = 0; 189 result = new Ex[libs.length * libs.length]; 190 for (int i = 0; i < libs.length; i++) { 191 result[count] = new Ex( 192 count, 193 req(0, 0), 194 pa[i * numPerType], 195 true); 196 count++; 197 for (int j = 0; j < libs.length; j++) { 198 if (i == j) { 199 continue; 200 } 201 result[count] = new Ex( 202 count, 203 i, 204 pa[j * numPerType], 205 false); 206 count++; 207 } 208 } 209 } else { 210 String[] runs = userRuns.split(","); 211 result = new Ex[runs.length]; 212 for (int i = 0; i < runs.length; i++) { 213 UserRun run = new UserRun(runs[i]); 214 result[i] = new Ex( 215 i, 216 run.req() == -1 ? 217 req(run.client(), run.service()) : 218 result[run.req()].req, 219 Arrays.stream(pa) 220 .filter(p -> p.debug().equals(run.acceptor())) 221 .findFirst() 222 .orElseThrow(() -> new Exception( 223 "no acceptor named " + run.acceptor())), 224 run.success()); 225 } 226 } 227 228 for (Ex x : result) { 229 x.run(); 230 } 231 232 pi.println("END"); 233 for (int i=0; i<na; i++) { 234 pa[i].println("END"); 235 } 236 System.out.println("\nAll Test Results\n================"); 237 boolean finalOut = true; 238 System.out.println(" req** client service acceptor Result"); 239 System.out.println("---- ------- ------ --------- -------- -------"); 240 for (int i=0; i<result.length; i++) { 241 boolean out = result[i].expected==result[i].actual; 242 finalOut &= out; 243 System.out.printf("%3d: %3d%s c%d s%d %4s %8s %s %s\n", 244 i, 245 result[i].req, 246 result[i].expected ? "**" : " ", 247 reqs.get(result[i].req).client, 248 reqs.get(result[i].req).service, 249 "(" + result[i].csize + ")", 250 result[i].acceptor.debug(), 251 result[i].actual ? "++" : "--", 252 out ? " " : "xxx"); 253 } 254 255 System.out.println("\nPath of Reqs\n============"); 256 for (int j=0; ; j++) { 257 boolean found = false; 258 for (int i=0; i<result.length; i++) { 259 if (result[i].req == j) { 260 if (!found) { 261 System.out.printf("%3d (c%s -> s%s): ", j, 262 reqs.get(j).client, reqs.get(j).service); 263 } 264 System.out.printf("%s%s(%d)%s", 265 found ? " -> " : "", 266 result[i].acceptor.debug(), 267 i, 268 result[i].actual != result[i].expected ? 269 "xxx" : ""); 270 found = true; 271 } 272 } 273 System.out.println(); 274 if (!found) { 275 break; 276 } 277 } 278 if (!finalOut) throw new Exception(); 279 } else if (args[0].equals("Nsanity")) { 280 // Native mode sanity check 281 Proc.d("Detect start"); 282 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 283 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 284 } else if (args[0].equals("initiator")) { 285 while (true) { 286 String title = Proc.textIn(); 287 Proc.d("Client see " + title); 288 if (title.equals("END")) break; 289 String[] cas = title.split(" "); 290 Context c = Context.fromUserPass(cas[0], OneKDC.PASS, false); 291 c.startAsClient(cas[1], GSSUtil.GSS_KRB5_MECH_OID); 292 c.x().requestCredDeleg(true); 293 byte[] token = c.take(new byte[0]); 294 Proc.d("Client AP-REQ generated"); 295 Proc.binOut(token); 296 } 297 } else { 298 Proc.d(System.getProperty("java.vm.version")); 299 Proc.d(System.getProperty("sun.security.jgss.native")); 300 Proc.d(System.getProperty("sun.security.jgss.lib")); 301 Proc.d("---------------------------------\n"); 302 Proc.d("Server start"); 303 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 304 Proc.d("Server login"); 305 while (true) { 306 String title = Proc.textIn(); 307 Proc.d("Server sees " + title); 308 if (title.equals("END")) break; 309 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 310 byte[] token = Proc.binIn(); 311 try { 312 s.take(token); 313 Proc.textOut("true"); 314 Proc.d("Good"); 315 } catch (Exception e) { 316 Proc.textOut("false"); 317 Proc.d("Bad"); 318 } 319 } 320 } 321 } 322 323 public static void main(String[] args) throws Exception { 324 try { 325 main0(args); 326 } catch (Exception e) { 327 Proc.d(e); 328 throw e; 329 } 330 } 331 332 // returns the client name 333 private static String client(int p) { 334 return "client" + p; 335 } 336 337 // returns the service name 338 private static String service(int p) { 339 return SERVICE + p + "/" + HOST; 340 } 341 342 // returns the dfl name for a service 343 private static String dfl(int p) { 344 return SERVICE + p + (uid == -1 ? "" : ("_"+uid)); 345 } 346 347 // generates an ap-req and save into reqs, returns the index 348 private static int req(int client, int service) throws Exception { 349 pi.println(client(client) + " " + service(service)); 350 Req req = new Req(client, service, pi.readData()); 351 reqs.add(req); 352 return reqs.size() - 1; 353 } 354 355 // create a acceptor 356 private static Proc acceptor(String type, String suffix) throws Exception { 357 Proc p; 358 String label; 359 String lib; 360 int pos = type.indexOf('='); 361 if (pos < 0) { 362 label = type; 363 lib = null; 364 } else { 365 label = type.substring(0, pos); 366 lib = type.substring(pos + 1); 367 } 368 if (type.startsWith("J")) { 369 if (lib == null) { 370 p = Proc.create("ReplayCacheTestProc") 371 .inheritProp("jdk.net.hosts.file"); 372 } else { 373 p = Proc.create("ReplayCacheTestProc", lib) 374 .inheritProp("jdk.net.hosts.file"); 375 } 376 p.prop("sun.security.krb5.rcache", "dfl") 377 .prop("java.io.tmpdir", cwd); 378 String useMD5 = System.getProperty("jdk.krb5.rcache.useMD5"); 379 if (useMD5 != null) { 380 p.prop("jdk.krb5.rcache.useMD5", useMD5); 381 } 382 } else { 383 p = Proc.create("ReplayCacheTestProc") 384 .env("KRB5_CONFIG", OneKDC.KRB5_CONF) 385 .env("KRB5_KTNAME", OneKDC.KTAB) 386 .env("KRB5RCACHEDIR", cwd) 387 .inheritProp("jdk.net.hosts.file") 388 .prop("sun.security.jgss.native", "true") 389 .prop("javax.security.auth.useSubjectCredsOnly", "false") 390 .prop("sun.security.nativegss.debug", "true"); 391 if (lib != null) { 392 String libDir = lib.substring(0, lib.lastIndexOf('/')); 393 p.prop("sun.security.jgss.lib", lib) 394 .env(Platform.sharedLibraryPathVariableName(), libDir); 395 } 396 } 397 Proc.d(label+suffix+" started"); 398 return p.args(label+suffix).debug(label+suffix).start(); 399 } 400 401 // generates hash of authenticator inside ap-req inside initsectoken 402 private static void record(String label, Req req) throws Exception { 403 byte[] data = Base64.getDecoder().decode(req.msg); 404 data = Arrays.copyOfRange(data, 17, data.length); 405 406 try (PrintStream ps = new PrintStream( 407 new FileOutputStream("log.txt", true))) { 408 ps.printf("%s:\nmsg: %s\nMD5: %s\nSHA-256: %s\n\n", 409 label, 410 req.msg, 411 hex(md5.digest(data)), 412 hex(sha256.digest(data))); 413 } 414 } 415 416 // Returns a compact hexdump for a byte array 417 private static String hex(byte[] hash) { 418 char[] h = new char[hash.length * 2]; 419 char[] hexConst = "0123456789ABCDEF".toCharArray(); 420 for (int i=0; i<hash.length; i++) { 421 h[2*i] = hexConst[(hash[i]&0xff)>>4]; 422 h[2*i+1] = hexConst[hash[i]&0xf]; 423 } 424 return new String(h); 425 } 426 427 // return size of dfl file, excluding the null hash ones 428 private static int csize(int p) throws Exception { 429 try (SeekableByteChannel chan = Files.newByteChannel( 430 Paths.get(cwd, dfl(p)), StandardOpenOption.READ)) { 431 chan.position(6); 432 int cc = 0; 433 while (true) { 434 try { 435 if (AuthTime.readFrom(chan) != null) cc++; 436 } catch (BufferUnderflowException e) { 437 break; 438 } 439 } 440 return cc; 441 } catch (IOException ioe) { 442 return 0; 443 } 444 } 445 446 // models an experiement 447 private static class Ex { 448 int i; // # 449 int req; // which ap-req to send 450 Proc acceptor; // which acceptor to send to 451 boolean expected; // expected result 452 453 boolean actual; // actual output 454 int csize; // size of rcache after test 455 String hash; // the hash of req 456 457 Ex(int i, int req, Proc acceptor, boolean expected) { 458 this.i = i; 459 this.req = req; 460 this.acceptor = acceptor; 461 this.expected = expected; 462 } 463 464 void run() throws Exception { 465 Req r = reqs.get(req); 466 acceptor.println("TEST"); 467 acceptor.println(r.msg); 468 String reply = acceptor.readData(); 469 470 actual = Boolean.valueOf(reply); 471 csize = csize(r.service); 472 473 String label = String.format("%03d-client%d-%s%d-%s-%s", 474 i, r.client, SERVICE, r.service, acceptor.debug(), actual); 475 476 record(label, r); 477 if (new File(cwd, dfl(r.service)).exists()) { 478 Files.copy(Paths.get(cwd, dfl(r.service)), Paths.get(label), 479 StandardCopyOption.COPY_ATTRIBUTES); 480 } 481 } 482 } 483 484 // models a saved ap-req msg 485 private static class Req { 486 String msg; // based64-ed req 487 int client; // which client 488 int service; // which service 489 Req(int client, int service, String msg) { 490 this.msg = msg; 491 this.client= client; 492 this.service = service; 493 } 494 } 495 496 private static class UserRun { 497 static final Pattern p 498 = Pattern.compile("(c(\\d)+s(\\d+)|r(\\d+))(.*)(.)"); 499 final Matcher m; 500 501 UserRun(String run) { m = p.matcher(run); m.find(); } 502 503 int req() { return group(4); } 504 int client() { return group(2); } 505 int service() { return group(3); } 506 String acceptor() { return m.group(5); } 507 boolean success() { return m.group(6).equals("v"); } 508 509 int group(int i) { 510 String g = m.group(i); 511 return g == null ? -1 : Integer.parseInt(g); 512 } 513 } 514 }