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/ /test/lib 29 * @compile -XDignore.symbol.file ReplayCacheTestProc.java 30 * @run main/othervm/timeout=300 ReplayCacheTestProc 31 * @run main/othervm/timeout=300 -Djdk.krb5.rcache.hashalg=SHA256 ReplayCacheTestProc 32 */ 33 34 import java.io.*; 35 import java.nio.BufferUnderflowException; 36 import java.nio.channels.SeekableByteChannel; 37 import java.nio.file.Files; 38 import java.nio.file.Paths; 39 import java.nio.file.StandardCopyOption; 40 import java.nio.file.StandardOpenOption; 41 import java.security.MessageDigest; 42 import java.util.*; 43 44 import jdk.test.lib.Platform; 45 import sun.security.jgss.GSSUtil; 46 import sun.security.krb5.internal.APReq; 47 import sun.security.krb5.internal.rcache.AuthTime; 48 import sun.security.krb5.internal.rcache.AuthTimeWithHash; 49 50 /** 51 * This test runs multiple acceptor Procs to mimin AP-REQ replays. 52 * It can either run with automatic (random) test runs or user can provide 53 * these system properties: 54 * 55 * - test.libs on what types of acceptors to use 56 * Format: CSV of (J|N|N<libname>) 57 * Example: J,N,N/krb5-1.14/lib/libgssapi_krb5.so 58 * 59 * - test.runs on runs 60 * Format: (req# | client# service#) acceptor# expected... 61 * Example: c0h0J0v,c1h1N0v,r0J1x means 1st req is new c0 to h0 sent to J0, 62 * 2nd req is new c1 to h1 sent to N0, 63 * 3rd req is old (1st replayed) sent to J1. 64 * For all old reqs, client# and service# MUST be - 65 * 66 * - test.autoruns on how many autoruns 67 * Format: number 68 */ 69 public class ReplayCacheTestProc { 70 71 private static Proc[] pa; // all acceptors 72 private static Proc pi; // the single initiator 73 private static List<Req> reqs = new ArrayList<>(); 74 private static String HOST = "localhost"; 75 76 // Where should the rcache be saved. It seems KRB5RCACHEDIR is not 77 // recognized on Solaris. Maybe version too low? I see 1.6. 78 private static String cwd = 79 System.getProperty("os.name").startsWith("SunOS") ? 80 "/var/krb5/rcache/" : 81 System.getProperty("user.dir"); 82 83 84 private static long uid; 85 86 public static void main0(String[] args) throws Exception { 87 System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF); 88 if (args.length == 0) { // The controller 89 int nu = 5; // number of users 90 int nh = 5; // number of hosts (services) 91 String[] libs; // available acceptor types: 92 // J: java 93 // N: default native lib 94 // N<libname>: native lib with the given name 95 Ex[] result; 96 int numPerType = 2; // number of servers per type 97 98 uid = jdk.internal.misc.VM.geteuid(); 99 100 KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true); 101 for (int i=0; i<nu; i++) { 102 kdc.addPrincipal(user(i), OneKDC.PASS); 103 } 104 kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM); 105 for (int i=0; i<nh; i++) { 106 kdc.addPrincipalRandKey(host(i)); 107 } 108 109 kdc.writeKtab(OneKDC.KTAB); 110 KDC.saveConfig(OneKDC.KRB5_CONF, kdc); 111 112 // User-provided libs 113 String userLibs = System.getProperty("test.libs"); 114 115 if (userLibs != null) { 116 libs = userLibs.split(","); 117 } else { 118 if (Platform.isOSX() || Platform.isWindows()) { 119 // macOS uses Heimdal and Windows has no native lib 120 libs = new String[]{"J"}; 121 } else { 122 // Test interop between Java and native (if available) 123 if (acceptor("N", "sanity").waitFor() != 0) { 124 Proc.d("Native mode sanity check failed, revert to java"); 125 libs = new String[]{"J"}; 126 } else { 127 libs = new String[]{"J", "N"}; 128 } 129 } 130 } 131 132 pi = Proc.create("ReplayCacheTestProc").debug("C") 133 .args("initiator") 134 .start(); 135 136 int na = libs.length * numPerType; // total number of acceptors 137 pa = new Proc[na]; 138 139 // Acceptors, numPerType for 1st, numForType for 2nd, ... 140 for (int i=0; i<na; i++) { 141 int type = i / numPerType; 142 String label; // N/J for N/J, ABC... for N<lib> 143 if (libs[type].length() == 1) { 144 label = libs[type]; 145 } else { 146 label = "" + (char)('A' + type); 147 } 148 pa[i] = acceptor(libs[type], label + i % numPerType); 149 } 150 151 String userRuns = System.getProperty("test.runs"); 152 153 if (userRuns != null) { 154 String[] runs = userRuns.split(","); 155 result = new Ex[runs.length]; 156 for (int i = 0; i < runs.length; i++) { 157 boolean expected = false; 158 int req = -1; 159 int client = -1; 160 int host = -1; 161 UserRun run = new UserRun(runs[i]); 162 while (true) { 163 char type = run.nextAction(); 164 if (type == ' ') { 165 break; 166 } 167 switch (type) { 168 case 'r': 169 req = result[run.nextValue()].req; 170 break; 171 case 'c': 172 client = run.nextValue(); 173 break; 174 case 'h': 175 req = req(client, run.nextValue()); 176 break; 177 case 'J': 178 case 'N': 179 for (int j = 0; j < libs.length; j++) { 180 if (libs[j].equals("" + type)) { 181 host = j * numPerType + run.nextValue(); 182 break; 183 } 184 } 185 break; 186 case 'v': 187 expected = true; 188 break; 189 case 'x': 190 expected = false; 191 break; 192 default: // ABC... 193 host = (type - 'A') * numPerType + run.nextValue(); 194 } 195 } 196 result[i] = new Ex(i, req, host, expected); 197 } 198 } else { 199 result = new Ex[Integer.parseInt( 200 System.getProperty("test.autoruns", "100"))]; 201 Random r = new Random(); 202 for (int i = 0; i < result.length; i++) { 203 int old; // which req to send 204 boolean expected; 205 if (reqs.isEmpty() || r.nextBoolean()) { 206 old = req(r.nextInt(nu), r.nextInt(nh)); 207 expected = true; 208 } else { 209 old = r.nextInt(reqs.size()); 210 expected = false; 211 } 212 int s = r.nextInt(na); 213 result[i] = new Ex(i, old, s, expected); 214 } 215 } 216 217 for (Ex x : result) { 218 x.run(); 219 } 220 221 pi.println("END"); 222 for (int i=0; i<na; i++) { 223 pa[i].println("END"); 224 } 225 System.out.println("Result\n======"); 226 boolean finalOut = true; 227 System.out.println(" #: expected (req): client host acceptor Result size"); 228 System.out.println("---- -------- ------ ------ ---- -------- ------ ----"); 229 for (int i=0; i<result.length; i++) { 230 boolean out = result[i].expected==result[i].actual; 231 finalOut &= out; 232 System.out.printf("%3d: %8s (%3d): u%d h%d %8s %s %s %4d\n", 233 i, 234 result[i].expected?"----":" ", 235 result[i].req, 236 reqs.get(result[i].req).user, 237 reqs.get(result[i].req).peer, 238 pa[result[i].acceptor].debug(), 239 result[i].actual?"Good":"Bad ", 240 out?" ":"xxx", 241 result[i].csize); 242 } 243 if (!finalOut) throw new Exception(); 244 } else if (args[0].equals("sanity")) { 245 // Native mode sanity check 246 Proc.d("Detect start"); 247 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 248 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 249 } else if (args[0].equals("initiator")) { 250 while (true) { 251 String title = Proc.textIn(); 252 Proc.d("Client see " + title); 253 if (title.equals("END")) break; 254 String[] cas = title.split(" "); 255 Context c = Context.fromUserPass(cas[0], OneKDC.PASS, false); 256 c.startAsClient(cas[1], GSSUtil.GSS_KRB5_MECH_OID); 257 c.x().requestCredDeleg(true); 258 byte[] token = c.take(new byte[0]); 259 Proc.d("Client AP-REQ generated"); 260 Proc.binOut(token); 261 } 262 } else { 263 Proc.d("Server start"); 264 Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); 265 Proc.d("Server login"); 266 while (true) { 267 String title = Proc.textIn(); 268 Proc.d("Server " + args[0] + " sees " + title); 269 if (title.equals("END")) break; 270 s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); 271 byte[] token = Proc.binIn(); 272 try { 273 s.take(token); 274 Proc.textOut("true"); 275 Proc.d(args[0] + " Good"); 276 } catch (Exception e) { 277 Proc.textOut("false"); 278 Proc.d(args[0] + " Bad"); 279 } 280 } 281 } 282 } 283 284 public static void main(String[] args) throws Exception { 285 try { 286 main0(args); 287 } catch (Exception e) { 288 Proc.d(e); 289 throw e; 290 } 291 } 292 293 // returns the user name 294 private static String user(int p) { 295 return "user" + p; 296 } 297 298 // returns the host name 299 private static String host(int p) { 300 return "host" + p + "/" + HOST; 301 } 302 303 // returns the dfl name for a host 304 private static String dfl(int p) { 305 return "host" + p + (uid == -1 ? "" : ("_"+uid)); 306 } 307 308 // generates an ap-req and save into reqs, returns the index 309 private static int req(int user, int peer) throws Exception { 310 pi.println(user(user) + " " + host(peer)); 311 Req req = new Req(user, peer, pi.readData()); 312 reqs.add(req); 313 return reqs.size() - 1; 314 } 315 316 // create a acceptor 317 private static Proc acceptor(String type, String label) throws Exception { 318 Proc p = Proc.create("ReplayCacheTestProc") 319 .args(label) 320 .debug(label); 321 if (type.equals("J")) { 322 p.prop("sun.security.krb5.rcache", "dfl") 323 .prop("java.io.tmpdir", cwd); 324 } else { 325 p.env("KRB5_CONFIG", OneKDC.KRB5_CONF) 326 .env("KRB5_KTNAME", OneKDC.KTAB) 327 .env("KRB5RCACHEDIR", cwd) 328 .prop("sun.security.jgss.native", "true") 329 .prop("javax.security.auth.useSubjectCredsOnly", "false") 330 .prop("sun.security.nativegss.debug", "true"); 331 if (type.length() > 1) { 332 String lib = type.substring(1); 333 String libDir = lib.substring(0, lib.lastIndexOf('/')); 334 p.prop("sun.security.jgss.lib", lib) 335 .env("DYLD_LIBRARY_PATH", libDir) 336 .env("LD_LIBRARY_PATH", libDir); 337 } 338 } 339 return p.start(); 340 } 341 342 // generates hash of authenticator inside ap-req inside initsectoken 343 private static String hash(String req) throws Exception { 344 byte[] data = Base64.getDecoder().decode(req); 345 data = Arrays.copyOfRange(data, 17, data.length); 346 byte[] hash = MessageDigest.getInstance( 347 AuthTimeWithHash.realAlg(AuthTimeWithHash.DEFAULT_HASH_ALG)) 348 .digest(new APReq(data).authenticator.getBytes()); 349 char[] h = new char[hash.length * 2]; 350 char[] hexConst = "0123456789ABCDEF".toCharArray(); 351 for (int i=0; i<hash.length; i++) { 352 h[2*i] = hexConst[(hash[i]&0xff)>>4]; 353 h[2*i+1] = hexConst[hash[i]&0xf]; 354 } 355 return new String(h); 356 } 357 358 // return size of dfl file, excluding the null hash ones 359 private static int csize(int p) throws Exception { 360 try (SeekableByteChannel chan = Files.newByteChannel( 361 Paths.get(cwd, dfl(p)), StandardOpenOption.READ)) { 362 chan.position(6); 363 int cc = 0; 364 while (true) { 365 try { 366 if (AuthTime.readFrom(chan) != null) cc++; 367 } catch (BufferUnderflowException e) { 368 break; 369 } 370 } 371 return cc; 372 } catch (IOException ioe) { 373 return 0; 374 } 375 } 376 377 // models an experiement 378 private static class Ex { 379 int i; // # 380 int req; // which ap-req to send 381 int acceptor; // which acceptor to send to 382 boolean expected; // expected result 383 384 boolean actual; // actual output 385 int csize; // size of rcache after test 386 String hash; // the hash of req 387 388 Ex(int i, int req, int acceptor, boolean expected) { 389 this.i = i; 390 this.req = req; 391 this.acceptor = acceptor; 392 this.expected = expected; 393 } 394 395 void run() throws Exception { 396 Req r = reqs.get(req); 397 pa[acceptor].println("TEST"); 398 pa[acceptor].println(r.msg); 399 String reply = pa[acceptor].readData(); 400 401 actual = Boolean.valueOf(reply); 402 csize = csize(r.peer); 403 hash = hash(r.msg); 404 if (new File(cwd, dfl(r.peer)).exists()) { 405 Files.copy(Paths.get(cwd, dfl(r.peer)), Paths.get( 406 String.format("%03d-USER%d-host%d-%s-%s", 407 i, r.user, r.peer, acceptor, 408 actual) 409 + "-" + hash), 410 StandardCopyOption.COPY_ATTRIBUTES); 411 } 412 } 413 } 414 415 // models a saved ap-req msg 416 private static class Req { 417 String msg; // based64-ed req 418 int user; // which client 419 int peer; // which service 420 Req(int user, int peer, String msg) { 421 this.msg = msg; 422 this.user= user; 423 this.peer = peer; 424 } 425 } 426 427 private static class UserRun { 428 String run; 429 int pos = 0; 430 UserRun(String run) { 431 this.run = run; 432 } 433 char nextAction() { 434 return pos < run.length() ? run.charAt(pos++) : ' '; 435 } 436 int nextValue() { 437 int result = 0; 438 for (; pos < run.length(); pos++) { 439 char c = run.charAt(pos); 440 if (!Character.isDigit(c)) { 441 break; 442 } 443 result = result * 10 + (c - '0'); 444 } 445 return result; 446 } 447 } 448 }