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