/* * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 7152176 * @summary More krb5 tests * @library ../../../../java/security/testlibrary/ * @compile -XDignore.symbol.file ReplayCacheTestProc.java * @run main/othervm/timeout=100 ReplayCacheTestProc */ import java.io.*; import java.nio.BufferUnderflowException; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.util.*; import sun.security.jgss.GSSUtil; import sun.security.krb5.internal.APReq; import sun.security.krb5.internal.rcache.AuthTime; // This test runs multiple acceptor Procs to mimin AP-REQ replays. public class ReplayCacheTestProc { private static Proc[] ps; private static Proc pc; private static List<Req> reqs = new ArrayList<>(); private static String HOST = "localhost"; // Where should the rcache be saved. It seems KRB5RCACHEDIR is not // recognized on Solaris. Maybe version too low? I see 1.6. private static String cwd = System.getProperty("os.name").startsWith("SunOS") ? "/var/krb5/rcache/" : System.getProperty("user.dir"); private static int uid; public static void main0(String[] args) throws Exception { System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF); if (args.length == 0) { // The controller int ns = 5; // number of servers int nu = 5; // number of users int nx = 50; // number of experiments int np = 5; // number of peers (services) int mode = 0; // native(1), random(0), java(-1) boolean random = true; // random experiments choreograph // Do not test interop with native GSS on some platforms String os = System.getProperty("os.name", "???"); if (!os.startsWith("SunOS") && !os.startsWith("Linux")) { mode = -1; } try { Class<?> clazz = Class.forName( "com.sun.security.auth.module.UnixSystem"); uid = (int)(long)(Long) clazz.getMethod("getUid").invoke(clazz.newInstance()); } catch (Exception e) { uid = -1; } KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true); for (int i=0; i<nu; i++) { kdc.addPrincipal(user(i), OneKDC.PASS); } kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM); for (int i=0; i<np; i++) { kdc.addPrincipalRandKey(peer(i)); } kdc.writeKtab(OneKDC.KTAB); KDC.saveConfig(OneKDC.KRB5_CONF, kdc); if (mode != -1) { // A special native server to check basic sanity if (ns(-1).waitFor() != 0) { Proc.d("Native mode sanity check failed, revert to java"); mode = -1; } } pc = Proc.create("ReplayCacheTestProc").debug("C") .args("client") .start(); ps = new Proc[ns]; Ex[] result = new Ex[nx]; if (!random) { // 2 experiments, 2 server, 1 peer, 1 user nx = 2; ns = 2; np = 1; nu = 1; // Creates reqs from user# to peer# req(0, 0); // Creates server# ps[0] = ns(0); ps[1] = js(1); // Runs ex# using req# to server# with expected result result[0] = round(0, 0, 0, true); result[1] = round(1, 0, 1, false); } else { Random r = new Random(); for (int i=0; i<ns; i++) { boolean useNative = (mode == 1) ? true : (mode == -1 ? false : r.nextBoolean()); ps[i] = useNative?ns(i):js(i); } for (int i=0; i<nx; i++) { result[i] = new Ex(); int old; // which req to send boolean expected; if (reqs.isEmpty() || r.nextBoolean()) { Proc.d("Console get new AP-REQ"); old = req(r.nextInt(nu), r.nextInt(np)); expected = true; } else { Proc.d("Console resue old"); old = r.nextInt(reqs.size()); expected = false; } int s = r.nextInt(ns); Proc.d("Console send to " + s); result[i] = round(i, old, s, expected); Proc.d("Console sees " + result[i].actual); } } pc.println("END"); for (int i=0; i<ns; i++) { ps[i].println("END"); } System.out.println("Result\n======"); boolean finalOut = true; for (int i=0; i<nx; i++) { boolean out = result[i].expected==result[i].actual; finalOut &= out; System.out.printf("%3d: %s (%2d): u%d h%d %s %s %s %2d\n", i, result[i].expected?"----":" ", result[i].old, result[i].user, result[i].peer, result[i].server, result[i].actual?"Good":"Bad ", out?" ":"xxx", result[i].csize); } if (!finalOut) throw new Exception(); } else if (args[0].equals("N-1")) { // Native mode sanity check Proc.d("Detect start"); Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); } else if (args[0].equals("client")) { while (true) { String title = Proc.textIn(); Proc.d("Client see " + title); if (title.equals("END")) break; String[] cas = title.split(" "); Context c = Context.fromUserPass(cas[0], OneKDC.PASS, false); c.startAsClient(cas[1], GSSUtil.GSS_KRB5_MECH_OID); c.x().requestCredDeleg(true); byte[] token = c.take(new byte[0]); Proc.d("Client AP-REQ generated"); Proc.binOut(token); } } else { Proc.d("Server start"); Context s = Context.fromUserKtab("*", OneKDC.KTAB, true); Proc.d("Server login"); while (true) { String title = Proc.textIn(); Proc.d("Server " + args[0] + " sees " + title); if (title.equals("END")) break; s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); byte[] token = Proc.binIn(); try { s.take(token); Proc.textOut("true"); Proc.d(args[0] + " Good"); } catch (Exception e) { Proc.textOut("false"); Proc.d(args[0] + " Bad"); } } } } public static void main(String[] args) throws Exception { try { main0(args); } catch (Exception e) { Proc.d(e); throw e; } } // returns the user name private static String user(int p) { return "USER" + p; } // returns the peer name private static String peer(int p) { return "host" + p + "/" + HOST; } // returns the dfl name for a host private static String dfl(int p) { return cwd + "host" + p + (uid == -1 ? "" : ("_"+uid)); } // generates an ap-req and save into reqs, returns the index private static int req(int user, int peer) throws Exception { pc.println(user(user) + " " + peer(peer)); Req req = new Req(user, peer, pc.readData()); reqs.add(req); return reqs.size() - 1; } // carries out a round of experiment // i: ex#, old: which req, server: which server, expected: result? private static Ex round(int i, int old, int server, boolean expected) throws Exception { ps[server].println("TEST"); ps[server].println(reqs.get(old).msg); String reply = ps[server].readData(); Ex result = new Ex(); result.i = i; result.expected = expected; result.server = ps[server].debug(); result.actual = Boolean.valueOf(reply); result.user = reqs.get(old).user; result.peer = reqs.get(old).peer; result.old = old; result.csize = csize(result.peer); result.hash = hash(reqs.get(old).msg); if (new File(dfl(result.peer)).exists()) { Files.copy(Paths.get(dfl(result.peer)), Paths.get( String.format("%03d-USER%d-host%d-%s-%s", i, result.user, result.peer, result.server, result.actual) + "-" + result.hash), StandardCopyOption.COPY_ATTRIBUTES); } return result; } // create a native server private static Proc ns(int i) throws Exception { return Proc.create("ReplayCacheTestProc") .args("N"+i) .env("KRB5_CONFIG", OneKDC.KRB5_CONF) .env("KRB5_KTNAME", OneKDC.KTAB) .env("KRB5RCACHEDIR", cwd) .prop("sun.security.jgss.native", "true") .prop("javax.security.auth.useSubjectCredsOnly", "false") .prop("sun.security.nativegss.debug", "true") .debug("N"+i) .start(); } // creates a java server private static Proc js(int i) throws Exception { return Proc.create("ReplayCacheTestProc") .debug("S"+i) .args("S"+i) .prop("sun.security.krb5.rcache", "dfl") .prop("java.io.tmpdir", cwd) .start(); } // generates hash of authenticator inside ap-req inside initsectoken private static String hash(String req) throws Exception { byte[] data = Base64.getDecoder().decode(req); data = Arrays.copyOfRange(data, 17, data.length); byte[] hash = MessageDigest.getInstance("MD5").digest(new APReq(data).authenticator.getBytes()); char[] h = new char[hash.length * 2]; char[] hexConst = "0123456789ABCDEF".toCharArray(); for (int i=0; i<hash.length; i++) { h[2*i] = hexConst[(hash[i]&0xff)>>4]; h[2*i+1] = hexConst[hash[i]&0xf]; } return new String(h); } // return size of dfl file, excluding the null hash ones private static int csize(int p) throws Exception { try (SeekableByteChannel chan = Files.newByteChannel( Paths.get(dfl(p)), StandardOpenOption.READ)) { chan.position(6); int cc = 0; while (true) { try { if (AuthTime.readFrom(chan) != null) cc++; } catch (BufferUnderflowException e) { break; } } return cc; } catch (IOException ioe) { return 0; } } // models an experiement private static class Ex { int i; // # boolean expected; // expected result boolean actual; // actual output int old; // which ap-req to send String server; // which server to send to String hash; // the hash of req int user; // which initiator int peer; // which acceptor int csize; // size of rcache after test } // models a saved ap-req msg private static class Req { String msg; // based64-ed req int user; // which initiator int peer; // which accceptor Req(int user, int peer, String msg) { this.msg = msg; this.user= user; this.peer = peer; } } }