changeset 15997:79d71eeecfc8

8168518: rcache interop with krb5-1.15 Reviewed-by: xuelei
author weijun
date Wed, 02 Nov 2016 14:44:15 +0800
parents 58796fb3241f
children be7aedddbb76
files src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/AuthTime.java src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/DflCache.java test/java/security/testlibrary/Proc.java test/sun/security/krb5/auto/ReplayCacheExpunge.java test/sun/security/krb5/auto/ReplayCachePrecise.java test/sun/security/krb5/auto/ReplayCacheTestProc.java test/sun/security/krb5/auto/rcache_usemd5.sh
diffstat 9 files changed, 412 insertions(+), 172 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java	Wed Nov 02 14:44:15 2016 +0800
@@ -301,12 +301,13 @@
         if (!authenticator.ctime.inClockSkew())
             throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);
 
+        String alg = AuthTimeWithHash.DEFAULT_HASH_ALG;
         byte[] hash;
         try {
-            hash = MessageDigest.getInstance("MD5")
+            hash = MessageDigest.getInstance(AuthTimeWithHash.realAlg(alg))
                     .digest(apReqMessg.authenticator.cipher);
         } catch (NoSuchAlgorithmException ex) {
-            throw new AssertionError("Impossible");
+            throw new AssertionError("Impossible " + alg);
         }
 
         char[] h = new char[hash.length * 2];
@@ -319,6 +320,7 @@
                 apReqMessg.ticket.sname.toString(),
                 authenticator.ctime.getSeconds(),
                 authenticator.cusec,
+                alg,
                 new String(h));
         rcache.checkAndStore(KerberosTime.now(), time);
 
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/AuthTime.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/AuthTime.java	Wed Nov 02 14:44:15 2016 +0800
@@ -116,14 +116,14 @@
             if (st.countTokens() != 6) {
                 throw new IOException("Incorrect rcache style");
             }
-            st.nextToken();
+            String hashAlg = st.nextToken();
             String hash = st.nextToken();
             st.nextToken();
             client = st.nextToken();
             st.nextToken();
             server = st.nextToken();
             return new AuthTimeWithHash(
-                    client, server, ctime, cusec, hash);
+                    client, server, ctime, cusec, hashAlg, hash);
         } else {
             return new AuthTime(
                     client, server, ctime, cusec);
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java	Wed Nov 02 14:44:15 2016 +0800
@@ -25,6 +25,8 @@
 
 package sun.security.krb5.internal.rcache;
 
+import sun.security.action.GetPropertyAction;
+
 import java.util.Objects;
 
 /**
@@ -34,14 +36,39 @@
 public class AuthTimeWithHash extends AuthTime
         implements Comparable<AuthTimeWithHash> {
 
+    // The hash algorithm can be "HASH" or "SHA256".
+    public static final String DEFAULT_HASH_ALG;
+
+    static {
+        if (GetPropertyAction.privilegedGetProperty(
+                "jdk.krb5.rcache.useMD5", "false").equals("true")) {
+            DEFAULT_HASH_ALG = "HASH";
+        } else {
+            DEFAULT_HASH_ALG = "SHA256";
+        }
+    }
+
+    public static String realAlg(String alg) {
+        switch (alg) {
+            case "HASH":
+                return "MD5";
+            case "SHA256":
+                return "SHA-256";
+            default:
+                throw new AssertionError(alg + " is not HASH or SHA256");
+        }
+    }
+
+    final String hashAlg;
     final String hash;
 
     /**
      * Constructs a new <code>AuthTimeWithHash</code>.
      */
     public AuthTimeWithHash(String client, String server,
-            int ctime, int cusec, String hash) {
+            int ctime, int cusec, String hashAlg, String hash) {
         super(client, server, ctime, cusec);
+        this.hashAlg = hashAlg;
         this.hash = hash;
     }
 
@@ -56,6 +83,7 @@
         if (!(o instanceof AuthTimeWithHash)) return false;
         AuthTimeWithHash that = (AuthTimeWithHash)o;
         return Objects.equals(hash, that.hash)
+                && Objects.equals(hashAlg, that.hashAlg)
                 && Objects.equals(client, that.client)
                 && Objects.equals(server, that.server)
                 && ctime == that.ctime
@@ -91,6 +119,19 @@
     /**
      * Compares with a possibly old style object. Used
      * in DflCache$Storage#loadAndCheck.
+     * @return true if all AuthTime fields are the same but different hash
+     */
+    public boolean sameTimeDiffHash(AuthTimeWithHash old) {
+        if (!this.isSameIgnoresHash(old)) {
+            return false;
+        }
+        return this.hashAlg.equals(old.hashAlg) &&
+                !this.hash.equals(old.hash);
+    }
+
+    /**
+     * Compares with a possibly old style object. Used
+     * in DflCache$Storage#loadAndCheck.
      * @return true if all AuthTime fields are the same
      */
     public boolean isSameIgnoresHash(AuthTime old) {
@@ -112,7 +153,7 @@
         String sstring;
         if (withHash) {
             cstring = "";
-            sstring = String.format("HASH:%s %d:%s %d:%s", hash,
+            sstring = String.format("%s:%s %d:%s %d:%s", hashAlg, hash,
                     client.length(), client,
                     server.length(), server);
         } else {
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/DflCache.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/rcache/DflCache.java	Wed Nov 02 14:44:15 2016 +0800
@@ -96,6 +96,8 @@
  * Java also does this way.
  *
  * See src/lib/krb5/rcache/rc_io.c and src/lib/krb5/rcache/rc_dfl.c.
+ *
+ * Update: New version can use other hash algorithms.
  */
 public class DflCache extends ReplayCache {
 
@@ -300,7 +302,7 @@
                         if (time.equals(a)) {
                             // Exact match, must be a replay
                             throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
-                        } else if (time.isSameIgnoresHash(a)) {
+                        } else if (time.sameTimeDiffHash((AuthTimeWithHash)a)) {
                             // Two different authenticators in the same second.
                             // Remember it
                             seeNewButNotSame = true;
--- a/test/java/security/testlibrary/Proc.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/test/java/security/testlibrary/Proc.java	Wed Nov 02 14:44:15 2016 +0800
@@ -243,16 +243,23 @@
     // Starts the proc
     public Proc start() throws IOException {
         List<String> cmd = new ArrayList<>();
+        boolean hasModules;
         if (launcher != null) {
             cmd.add(launcher);
+            File base = new File(launcher).getParentFile().getParentFile();
+            hasModules = new File(base, "modules").exists() ||
+                    new File(base, "jmods").exists();
         } else {
             cmd.add(new File(new File(System.getProperty("java.home"), "bin"),
                         "java").getPath());
+            hasModules = true;
         }
 
-        Stream.of(jdk.internal.misc.VM.getRuntimeArguments())
-            .filter(arg -> arg.startsWith("--add-exports="))
-            .forEach(cmd::add);
+        if (hasModules) {
+            Stream.of(jdk.internal.misc.VM.getRuntimeArguments())
+                    .filter(arg -> arg.startsWith("--add-exports="))
+                    .forEach(cmd::add);
+        }
 
         Collections.addAll(cmd, splitProperty("test.vm.opts"));
         Collections.addAll(cmd, splitProperty("test.java.opts"));
--- a/test/sun/security/krb5/auto/ReplayCacheExpunge.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/test/sun/security/krb5/auto/ReplayCacheExpunge.java	Wed Nov 02 14:44:15 2016 +0800
@@ -47,15 +47,15 @@
         int count = Integer.parseInt(args[0]);
         ReplayCache cache = ReplayCache.getInstance("dfl:./");
         AuthTimeWithHash a1 =
-                new AuthTimeWithHash(client, server, time(-400), 0, hash("1"));
+                new AuthTimeWithHash(client, server, time(-400), 0, "HASH", hash("1"));
         AuthTimeWithHash a2 =
-                new AuthTimeWithHash(client, server, time(0), 0, hash("4"));
+                new AuthTimeWithHash(client, server, time(0), 0, "HASH", hash("4"));
         KerberosTime now = new KerberosTime(time(0)*1000L);
         KerberosTime then = new KerberosTime(time(-300)*1000L);
 
         // Once upon a time, we added a lot of events
         for (int i=0; i<count; i++) {
-            a1 = new AuthTimeWithHash(client, server, time(-400), 0, hash(""));
+            a1 = new AuthTimeWithHash(client, server, time(-400), 0, "HASH", hash(""));
             cache.checkAndStore(then, a1);
         }
 
--- a/test/sun/security/krb5/auto/ReplayCachePrecise.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/test/sun/security/krb5/auto/ReplayCachePrecise.java	Wed Nov 02 14:44:15 2016 +0800
@@ -48,9 +48,9 @@
     public static void main(String[] args) throws Exception {
 
         AuthTimeWithHash a1 = new AuthTimeWithHash(client, server, time(0), 0,
-                "1111111111111111");
+                "HASH", "1111111111111111");
         AuthTimeWithHash a2 = new AuthTimeWithHash(client, server, time(0), 0,
-                "2222222222222222");
+                "HASH", "2222222222222222");
         KerberosTime now = new KerberosTime(time(0)*1000L);
 
         // When all new styles, must exact match
--- a/test/sun/security/krb5/auto/ReplayCacheTestProc.java	Wed Nov 02 10:49:15 2016 +0530
+++ b/test/sun/security/krb5/auto/ReplayCacheTestProc.java	Wed Nov 02 14:44:15 2016 +0800
@@ -23,11 +23,10 @@
 
 /*
  * @test
- * @bug 7152176
+ * @bug 7152176 8168518
  * @summary More krb5 tests
- * @library ../../../../java/security/testlibrary/
- * @compile -XDignore.symbol.file ReplayCacheTestProc.java
- * @run main/othervm/timeout=100 ReplayCacheTestProc
+ * @library ../../../../java/security/testlibrary/ /test/lib
+ * @run main/othervm/timeout=300 ReplayCacheTestProc
  */
 
 import java.io.*;
@@ -38,17 +37,40 @@
 import java.nio.file.StandardCopyOption;
 import java.nio.file.StandardOpenOption;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
+import jdk.test.lib.Platform;
 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.
+/**
+ * This test runs multiple acceptor Procs to mimic AP-REQ replays.
+ * These system properties are supported:
+ *
+ * - test.libs on what types of acceptors to use
+ *   Format: CSV of (J|N|N<suffix>=<libname>|J<suffix>=<launcher>)
+ *   Default: J,N on Solaris and Linux where N is available, or J
+ *   Example: J,N,N14=/krb5-1.14/lib/libgssapi_krb5.so,J8=/java8/bin/java
+ *
+ * - test.runs on manual runs. If empty, a iterate through all pattern
+ *   Format: (req# | client# service#) acceptor# expected, ...
+ *   Default: null
+ *   Example: c0s0Jav,c1s1N14av,r0Jbx means 0th req is new c0->s0 sent to Ja,
+ *            1st req is new c1 to s1 sent to N14a,
+ *            2nd req is old (0th replayed) sent to Jb.
+ *            a/b at the end of acceptor is different acceptors of the same lib
+ *
+ * - test.autoruns on number of automatic runs
+ *   Format: number
+ *   Default: 100
+ */
 public class ReplayCacheTestProc {
 
-    private static Proc[] ps;
-    private static Proc pc;
+    private static Proc[] pa;   // all acceptors
+    private static Proc pi;     // the single initiator
     private static List<Req> reqs = new ArrayList<>();
     private static String HOST = "localhost";
 
@@ -59,119 +81,193 @@
                 "/var/krb5/rcache/" :
                 System.getProperty("user.dir");
 
+    private static MessageDigest md5, sha256;
+
+    static {
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+            sha256 = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new AssertionError("Impossible", nsae);
+        }
+    }
 
     private static long 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;
-            }
+            int nc = 5;     // number of clients
+            int ns = 5;     // number of services
+            String[] libs;  // available acceptor types:
+                            // J: java
+                            // J<suffix>=<java launcher>: another java
+                            // N: default native lib
+                            // N<suffix>=<libname>: another native lib
+            Ex[] result;
+            int numPerType = 2; // number of acceptors per type
 
             uid = jdk.internal.misc.VM.geteuid();
 
             KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true);
-            for (int i=0; i<nu; i++) {
-                kdc.addPrincipal(user(i), OneKDC.PASS);
+            for (int i=0; i<nc; i++) {
+                kdc.addPrincipal(client(i), OneKDC.PASS);
             }
             kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM);
-            for (int i=0; i<np; i++) {
-                kdc.addPrincipalRandKey(peer(i));
+            for (int i=0; i<ns; i++) {
+                kdc.addPrincipalRandKey(service(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;
+            // User-provided libs
+            String userLibs = System.getProperty("test.libs");
+
+            if (userLibs != null) {
+                libs = userLibs.split(",");
+            } else {
+                if (Platform.isOSX() || Platform.isWindows()) {
+                    // macOS uses Heimdal and Windows has no native lib
+                    libs = new String[]{"J"};
+                } else {
+                    if (acceptor("N", "sanity").waitFor() != 0) {
+                        Proc.d("Native mode sanity check failed, only java");
+                        libs = new String[]{"J"};
+                    } else {
+                        libs = new String[]{"J", "N"};
+                    }
                 }
             }
 
-            pc = Proc.create("ReplayCacheTestProc").debug("C")
-                    .args("client")
+            pi = Proc.create("ReplayCacheTestProc").debug("C")
+                    .args("initiator")
                     .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;
+            int na = libs.length * numPerType;  // total number of acceptors
+            pa = new Proc[na];
 
-                // Creates reqs from user# to peer#
-                req(0, 0);
+            // Acceptors, numPerType for 1st, numForType for 2nd, ...
+            for (int i=0; i<na; i++) {
+                pa[i] = acceptor(libs[i/numPerType],
+                        "" + (char)('a' + i%numPerType));
+            }
 
-                // Creates server#
-                ps[0] = ns(0);
-                ps[1] = js(1);
+            // Manual runs
+            String userRuns = System.getProperty("test.runs");
 
-                // Runs ex# using req# to server# with expected result
-                result[0] = round(0, 0, 0, true);
-                result[1] = round(1, 0, 1, false);
+            if (userRuns == null) {
+                result = new Ex[Integer.parseInt(
+                        System.getProperty("test.autoruns", "100"))];
+                Random r = new Random();
+                for (int i = 0; i < result.length; i++) {
+                    boolean expected = reqs.isEmpty() || r.nextBoolean();
+                    result[i] = new Ex(
+                            i,
+                            expected ?
+                                    req(r.nextInt(nc), r.nextInt(ns)) :
+                                    r.nextInt(reqs.size()),
+                            pa[r.nextInt(na)],
+                            expected);
+                }
+            } else if (userRuns.isEmpty()) {
+                int count = 0;
+                result = new Ex[libs.length * libs.length];
+                for (int i = 0; i < libs.length; i++) {
+                    result[count] = new Ex(
+                            count,
+                            req(0, 0),
+                            pa[i * numPerType],
+                            true);
+                    count++;
+                    for (int j = 0; j < libs.length; j++) {
+                        if (i == j) {
+                            continue;
+                        }
+                        result[count] = new Ex(
+                                count,
+                                i,
+                                pa[j * numPerType],
+                                false);
+                        count++;
+                    }
+                }
             } 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);
+                String[] runs = userRuns.split(",");
+                result = new Ex[runs.length];
+                for (int i = 0; i < runs.length; i++) {
+                    UserRun run = new UserRun(runs[i]);
+                    result[i] = new Ex(
+                            i,
+                            run.req() == -1 ?
+                                    req(run.client(), run.service()) :
+                                    result[run.req()].req,
+                            Arrays.stream(pa)
+                                    .filter(p -> p.debug().equals(run.acceptor()))
+                                    .findFirst()
+                                    .orElseThrow(() -> new Exception(
+                                            "no acceptor named " + run.acceptor())),
+                            run.success());
                 }
             }
 
-            pc.println("END");
-            for (int i=0; i<ns; i++) {
-                ps[i].println("END");
+            for (Ex x : result) {
+                x.run();
             }
-            System.out.println("Result\n======");
+
+            pi.println("END");
+            for (int i=0; i<na; i++) {
+                pa[i].println("END");
+            }
+            System.out.println("\nAll Test Results\n================");
             boolean finalOut = true;
-            for (int i=0; i<nx; i++) {
+            System.out.println("        req**  client    service  acceptor   Result");
+            System.out.println("----  -------  ------  ---------  --------  -------");
+            for (int i=0; i<result.length; 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",
+                System.out.printf("%3d:    %3d%s      c%d    s%d %4s  %8s   %s  %s\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);
+                        result[i].req,
+                        result[i].expected ? "**" : "  ",
+                        reqs.get(result[i].req).client,
+                        reqs.get(result[i].req).service,
+                        "(" + result[i].csize + ")",
+                        result[i].acceptor.debug(),
+                        result[i].actual ? "++" : "--",
+                        out ? "   " : "xxx");
+            }
+
+            System.out.println("\nPath of Reqs\n============");
+            for (int j=0; ; j++) {
+                boolean found = false;
+                for (int i=0; i<result.length; i++) {
+                    if (result[i].req == j) {
+                        if (!found) {
+                            System.out.printf("%3d (c%s -> s%s): ", j,
+                                    reqs.get(j).client, reqs.get(j).service);
+                        }
+                        System.out.printf("%s%s(%d)%s",
+                                found ? " -> " : "",
+                                result[i].acceptor.debug(),
+                                i,
+                                result[i].actual != result[i].expected ?
+                                        "xxx" : "");
+                        found = true;
+                    }
+                }
+                System.out.println();
+                if (!found) {
+                    break;
+                }
             }
             if (!finalOut) throw new Exception();
-        } else if (args[0].equals("N-1")) {
+        } else if (args[0].equals("Nsanity")) {
             // 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")) {
+        } else if (args[0].equals("initiator")) {
             while (true) {
                 String title = Proc.textIn();
                 Proc.d("Client see " + title);
@@ -185,22 +281,26 @@
                 Proc.binOut(token);
             }
         } else {
+            Proc.d(System.getProperty("java.vm.version"));
+            Proc.d(System.getProperty("sun.security.jgss.native"));
+            Proc.d(System.getProperty("sun.security.jgss.lib"));
+            Proc.d("---------------------------------\n");
             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);
+                Proc.d("Server 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");
+                    Proc.d("Good");
                 } catch (Exception e) {
                     Proc.textOut("false");
-                    Proc.d(args[0] + " Bad");
+                    Proc.d("Bad");
                 }
             }
         }
@@ -215,79 +315,90 @@
         }
     }
 
-    // returns the user name
-    private static String user(int p) {
-        return "USER" + p;
+    // returns the client name
+    private static String client(int p) {
+        return "client" + p;
     }
-    // returns the peer name
-    private static String peer(int p) {
-        return "host" + p + "/" + HOST;
+
+    // returns the service name
+    private static String service(int p) {
+        return "service" + p + "/" + HOST;
     }
-    // returns the dfl name for a host
+
+    // returns the dfl name for a service
     private static String dfl(int p) {
-        return cwd + "host" + p + (uid == -1 ? "" : ("_"+uid));
+        return "service" + 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());
+    private static int req(int client, int service) throws Exception {
+        pi.println(client(client) + " " + service(service));
+        Req req = new Req(client, service, pi.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);
+
+    // create a acceptor
+    private static Proc acceptor(String type, String suffix) throws Exception {
+        Proc p;
+        String label;
+        String lib;
+        int pos = type.indexOf('=');
+        if (pos < 0) {
+            label = type;
+            lib = null;
+        } else {
+            label = type.substring(0, pos);
+            lib = type.substring(pos + 1);
         }
-        return result;
+        if (type.startsWith("J")) {
+            if (lib == null) {
+                p = Proc.create("ReplayCacheTestProc");
+            } else {
+                p = Proc.create("ReplayCacheTestProc", lib);
+            }
+            p.prop("sun.security.krb5.rcache", "dfl")
+                    .prop("java.io.tmpdir", cwd);
+            String useMD5 = System.getProperty("jdk.krb5.rcache.useMD5");
+            if (useMD5 != null) {
+                p.prop("jdk.krb5.rcache.useMD5", useMD5);
+            }
+        } else {
+            p = Proc.create("ReplayCacheTestProc")
+                    .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");
+            if (lib != null) {
+                String libDir = lib.substring(0, lib.lastIndexOf('/'));
+                p.prop("sun.security.jgss.lib", lib)
+                        .env("DYLD_LIBRARY_PATH", libDir)
+                        .env("LD_LIBRARY_PATH", libDir);
+            }
+        }
+        Proc.d(label+suffix+" started");
+        return p.args(label+suffix).debug(label+suffix).start();
     }
-    // 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();
+
+    // generates hash of authenticator inside ap-req inside initsectoken
+    private static void record(String label, Req req) throws Exception {
+        byte[] data = Base64.getDecoder().decode(req.msg);
+        data = Arrays.copyOfRange(data, 17, data.length);
+
+        try (PrintStream ps = new PrintStream(
+                new FileOutputStream("log.txt", true))) {
+            ps.printf("%s:\nmsg: %s\nMD5: %s\nSHA-256: %s\n\n",
+                    label,
+                    req.msg,
+                    hex(md5.digest(data)),
+                    hex(sha256.digest(data)));
+        }
     }
-    // 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());
+
+    // Returns a compact hexdump for a byte array
+    private static String hex(byte[] hash) {
         char[] h = new char[hash.length * 2];
         char[] hexConst = "0123456789ABCDEF".toCharArray();
         for (int i=0; i<hash.length; i++) {
@@ -296,10 +407,11 @@
         }
         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)) {
+                Paths.get(cwd, dfl(p)), StandardOpenOption.READ)) {
             chan.position(6);
             int cc = 0;
             while (true) {
@@ -314,27 +426,73 @@
             return 0;
         }
     }
+
     // models an experiement
     private static class Ex {
         int i;              // #
+        int req;            // which ap-req to send
+        Proc acceptor;      // which acceptor to send to
         boolean expected;   // expected result
+
         boolean actual;     // actual output
-        int old;            // which ap-req to send
-        String server;      // which server to send to
+        int csize;          // size of rcache after test
         String hash;        // the hash of req
-        int user;           // which initiator
-        int peer;           // which acceptor
-        int csize;          // size of rcache after test
+
+        Ex(int i, int req, Proc acceptor, boolean expected) {
+            this.i = i;
+            this.req = req;
+            this.acceptor = acceptor;
+            this.expected = expected;
+        }
+
+        void run() throws Exception {
+            Req r = reqs.get(req);
+            acceptor.println("TEST");
+            acceptor.println(r.msg);
+            String reply = acceptor.readData();
+
+            actual = Boolean.valueOf(reply);
+            csize = csize(r.service);
+
+            String label = String.format("%03d-CLIENT%d-SERVICE%d-%s-%s",
+                    i, r.client, r.service, acceptor.debug(), actual);
+
+            record(label, r);
+            if (new File(cwd, dfl(r.service)).exists()) {
+                Files.copy(Paths.get(cwd, dfl(r.service)), Paths.get(label),
+                        StandardCopyOption.COPY_ATTRIBUTES);
+            }
+        }
     }
+
     // 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) {
+        int client;         // which client
+        int service;        // which service
+        Req(int client, int service, String msg) {
             this.msg = msg;
-            this.user= user;
-            this.peer = peer;
+            this.client= client;
+            this.service = service;
+        }
+    }
+
+    private static class UserRun {
+        static final Pattern p
+                = Pattern.compile("(c(\\d)+s(\\d+)|r(\\d+))(.*)(.)");
+        final Matcher m;
+
+        UserRun(String run) { m = p.matcher(run); m.find(); }
+
+        int req() { return group(4); }
+        int client() { return group(2); }
+        int service() { return group(3); }
+        String acceptor() { return m.group(5); }
+        boolean success() { return m.group(6).equals("v"); }
+
+        int group(int i) {
+            String g = m.group(i);
+            return g == null ? -1 : Integer.parseInt(g);
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/rcache_usemd5.sh	Wed Nov 02 14:44:15 2016 +0800
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2016, 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 8168518
+# @library ../../../../java/security/testlibrary/ /test/lib
+# @run main/othervm/timeout=300 -Djdk.krb5.rcache.useMD5=true ReplayCacheTestProc
+# @summary  testing jdk.krb5.rcache.useMD5. This action is put in a separate
+#           test so that ReplayCacheTestProc.java can be launched with special
+#           test.* system properties easily.