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