changeset 9873:fdfd34ccc09b

8001326: Improve Kerberos caching Summary: Includes change to not blank EncryptedData.cipher from JDK-6355584 Reviewed-by: mbalao
author andrew
date Tue, 28 Jan 2020 05:30:42 +0000
parents eacf874ffed6
children 18547b43e076
files src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java src/share/classes/sun/security/krb5/EncryptedData.java src/share/classes/sun/security/krb5/EncryptionKey.java src/share/classes/sun/security/krb5/KrbApRep.java src/share/classes/sun/security/krb5/KrbApReq.java src/share/classes/sun/security/krb5/internal/ReplayCache.java src/share/classes/sun/security/krb5/internal/rcache/AuthList.java src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java src/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java src/share/classes/sun/security/krb5/internal/rcache/CacheTable.java src/share/classes/sun/security/krb5/internal/rcache/DflCache.java src/share/classes/sun/security/krb5/internal/rcache/MemoryCache.java src/share/classes/sun/security/krb5/internal/rcache/ReplayCache.java test/java/security/testlibrary/Proc.java test/sun/security/krb5/auto/AcceptorSubKey.java test/sun/security/krb5/auto/BasicProc.java test/sun/security/krb5/auto/Context.java test/sun/security/krb5/auto/KDC.java test/sun/security/krb5/auto/NoneReplayCacheTest.java test/sun/security/krb5/auto/ReplayCache.java test/sun/security/krb5/auto/ReplayCacheExpunge.java test/sun/security/krb5/auto/ReplayCachePrecise.java test/sun/security/krb5/auto/ReplayCacheTest.java test/sun/security/krb5/auto/ReplayCacheTestProc.java test/sun/security/krb5/ccache/EmptyCC.java
diffstat 25 files changed, 2120 insertions(+), 403 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java	Tue Jan 28 05:30:42 2020 +0000
@@ -27,9 +27,10 @@
 
 import org.ietf.jgss.*;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.IOException;
-import java.io.ByteArrayInputStream;
+import java.security.AccessController;
+
+import sun.security.action.GetBooleanAction;
 import sun.security.krb5.*;
 
 class AcceptSecContextToken extends InitialToken {
@@ -42,23 +43,19 @@
      */
     public AcceptSecContextToken(Krb5Context context,
                                  KrbApReq apReq)
-        throws KrbException, IOException {
+        throws KrbException, IOException, GSSException {
 
-        /*
-         * RFC 1964, section 1.2 states:
-         *  (1) context key: uses Kerberos session key (or subkey, if
-         *  present in authenticator emitted by context initiator) directly
-         *
-         * This does not mention context acceptor. Hence we will not
-         * generate a subkey on the acceptor side. Note: Our initiator will
-         * still allow another acceptor to generate a subkey, even though
-         * our acceptor does not do so.
-         */
-        boolean useSubkey = false;
+        boolean useSubkey = AccessController.doPrivileged(
+                new GetBooleanAction("sun.security.krb5.acceptor.subkey"));
 
         boolean useSequenceNumber = true;
 
-        apRep = new KrbApRep(apReq, useSequenceNumber, useSubkey);
+        EncryptionKey subKey = null;
+        if (useSubkey) {
+            subKey = new EncryptionKey(apReq.getCreds().getSessionKey());
+            context.setKey(Krb5Context.ACCEPTOR_SUBKEY, subKey);
+        }
+        apRep = new KrbApRep(apReq, useSequenceNumber, subKey);
 
         context.resetMySequenceNumber(apRep.getSeqNumber().intValue());
 
--- a/src/share/classes/sun/security/krb5/EncryptedData.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/src/share/classes/sun/security/krb5/EncryptedData.java	Tue Jan 28 05:30:42 2020 +0000
@@ -160,8 +160,6 @@
         kvno = key.getKeyVersionNumber();
     }
     */
-
-    // currently destructive on cipher
     public byte[] decrypt(
                           EncryptionKey key, int usage)
         throws KdcErrException, KrbApErrException, KrbCryptoException {
@@ -175,7 +173,8 @@
 
             EType etypeEngine = EType.getInstance(eType);
             plain = etypeEngine.decrypt(cipher, key.getBytes(), usage);
-            cipher = null;
+            // The raw ticket is still needed for the replay cache.
+            //cipher = null;
             return etypeEngine.decryptedData(plain);
         }
 
--- a/src/share/classes/sun/security/krb5/EncryptionKey.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/src/share/classes/sun/security/krb5/EncryptionKey.java	Tue Jan 28 05:30:42 2020 +0000
@@ -330,9 +330,11 @@
 
     /**
      * Generates a sub-sessionkey from a given session key.
+     *
+     * Used in AcceptSecContextToken and KrbApReq by acceptor- and initiator-
+     * side respectively.
      */
-     // Used in KrbApRep, KrbApReq
-    EncryptionKey(EncryptionKey key) throws KrbCryptoException {
+    public EncryptionKey(EncryptionKey key) throws KrbCryptoException {
         // generate random sub-session key
         keyValue = Confounder.bytes(key.keyValue.length);
         for (int i = 0; i < keyValue.length; i++) {
--- a/src/share/classes/sun/security/krb5/KrbApRep.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/src/share/classes/sun/security/krb5/KrbApRep.java	Tue Jan 28 05:30:42 2020 +0000
@@ -53,12 +53,10 @@
      */
      // Used in AcceptSecContextToken
     public KrbApRep(KrbApReq incomingReq,
-                    boolean useSeqNumber,
-        boolean useSubKey) throws KrbException, IOException {
+                     boolean useSeqNumber,
+                     EncryptionKey subKey)
+            throws KrbException, IOException {
 
-        EncryptionKey subKey =
-            (useSubKey?
-             new EncryptionKey(incomingReq.getCreds().getSessionKey()):null);
         SeqNumber seqNum = new LocalSeqNumber();
 
         init(incomingReq, subKey, seqNum);
--- a/src/share/classes/sun/security/krb5/KrbApReq.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/src/share/classes/sun/security/krb5/KrbApReq.java	Tue Jan 28 05:30:42 2020 +0000
@@ -33,11 +33,13 @@
 
 import sun.security.krb5.internal.*;
 import sun.security.krb5.internal.crypto.*;
-import sun.security.krb5.internal.rcache.*;
 import java.net.InetAddress;
 import sun.security.util.*;
 import java.io.IOException;
 import java.util.Arrays;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import sun.security.krb5.internal.rcache.AuthTimeWithHash;
 
 /**
  * This class encapsulates a KRB-AP-REQ that a client sends to a
@@ -52,11 +54,23 @@
     private Credentials creds;
     private APReq apReqMessg;
 
-    private static CacheTable table = new CacheTable();
+    // Used by acceptor side
+    private static ReplayCache rcache = ReplayCache.getInstance();
     private static boolean DEBUG = Krb5.DEBUG;
+    private static final char[] hexConst = "0123456789ABCDEF".toCharArray();
+
+    private static final MessageDigest md;
+
+    static {
+        try {
+            md = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException ex) {
+            throw new RuntimeException("Impossible");
+        }
+    }
 
     /**
-     * Contructs a AP-REQ message to send to the peer.
+     * Constructs an AP-REQ message to send to the peer.
      * @param tgsCred the <code>Credentials</code> to be used to construct the
      *          AP Request  protocol message.
      * @param mutualRequired Whether mutual authentication is required
@@ -80,7 +94,7 @@
 */
 
     /**
-     * Contructs a AP-REQ message to send to the peer.
+     * Constructs an AP-REQ message to send to the peer.
      * @param tgsCred the <code>Credentials</code> to be used to construct the
      *          AP Request  protocol message.
      * @param mutualRequired Whether mutual authentication is required
@@ -124,7 +138,7 @@
     }
 
     /**
-     * Contructs a AP-REQ message from the bytes received from the
+     * Constructs an AP-REQ message from the bytes received from the
      * peer.
      * @param message The message received from the peer
      * @param keys <code>EncrtyptionKey</code>s to decrypt the message;
@@ -145,7 +159,7 @@
     }
 
     /**
-     * Contructs a AP-REQ message from the bytes received from the
+     * Constructs an AP-REQ message from the bytes received from the
      * peer.
      * @param value The <code>DerValue</code> that contains the
      *              DER enoded AP-REQ protocol message
@@ -294,15 +308,19 @@
         if (!authenticator.ctime.inClockSkew())
             throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);
 
-        // start to check if it is a replay attack.
-        AuthTime time =
-            new AuthTime(authenticator.ctime.getTime(), authenticator.cusec);
-        String client = authenticator.cname.toString();
-        if (table.get(time, authenticator.cname.toString()) != null) {
-            throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
-        } else {
-            table.put(client, time, System.currentTimeMillis());
+        byte[] hash = md.digest(apReqMessg.authenticator.cipher);
+        char[] h = new char[hash.length * 2];
+        for (int i=0; i<hash.length; i++) {
+            h[2*i] = hexConst[(hash[i]&0xff)>>4];
+            h[2*i+1] = hexConst[hash[i]&0xf];
         }
+        AuthTimeWithHash time = new AuthTimeWithHash(
+                authenticator.cname.toString(),
+                apReqMessg.ticket.sname.toString(),
+                authenticator.ctime.getSeconds(),
+                authenticator.cusec,
+                new String(h));
+        rcache.checkAndStore(KerberosTime.now(), time);
 
         if (initiator != null) {
             // sender host address
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/krb5/internal/ReplayCache.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,72 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.security.krb5.internal;
+
+import sun.security.action.GetPropertyAction;
+import sun.security.krb5.internal.rcache.AuthTimeWithHash;
+import sun.security.krb5.internal.rcache.MemoryCache;
+import sun.security.krb5.internal.rcache.DflCache;
+
+import java.security.AccessController;
+
+/**
+ * Models the replay cache of an acceptor as described in
+ * RFC 4120 3.2.3.
+ * @since 1.8
+ */
+public abstract class ReplayCache {
+    public static ReplayCache getInstance(String type) {
+        if (type == null) {
+            return new MemoryCache();
+        } else if (type.equals("dfl") || type.startsWith("dfl:")) {
+            return new DflCache(type);
+        } else if (type.equals("none")) {
+            return new ReplayCache() {
+                @Override
+                public void checkAndStore(KerberosTime currTime, AuthTimeWithHash time)
+                        throws KrbApErrException {
+                    // no check at all
+                }
+            };
+        } else {
+            throw new IllegalArgumentException("Unknown type: " + type);
+        }
+    }
+    public static ReplayCache getInstance() {
+        String type = AccessController.doPrivileged(
+                new GetPropertyAction("sun.security.krb5.rcache"));
+        return getInstance(type);
+    }
+
+    /**
+     * Accepts or rejects an AuthTime.
+     * @param currTime the current time
+     * @param time AuthTimeWithHash object calculated from authenticator
+     * @throws KrbApErrException if the authenticator is a replay
+     */
+    public abstract void checkAndStore(KerberosTime currTime, AuthTimeWithHash time)
+            throws KrbApErrException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/krb5/internal/rcache/AuthList.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2000, 2006, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/*
+ *
+ *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
+ *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
+ */
+
+package sun.security.krb5.internal.rcache;
+
+import sun.security.krb5.internal.Krb5;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.KrbApErrException;
+
+/**
+ * This class provides an efficient caching mechanism to store AuthTimeWithHash
+ * from client authenticators. The cache minimizes the memory usage by doing
+ * self-cleanup of expired items in the cache.
+ *
+ * AuthTimeWithHash objects inside a cache are always sorted from big (new) to
+ * small (old) as determined by {@see AuthTimeWithHash#compareTo}. In the most
+ * common case a newcomer should be newer than the first element.
+ *
+ * @author Yanni Zhang
+ */
+public class AuthList {
+
+    private final LinkedList<AuthTimeWithHash> entries;
+    private final int lifespan;
+
+    /**
+     * Constructs a AuthList.
+     */
+    public AuthList(int lifespan) {
+        this.lifespan = lifespan;
+        entries = new LinkedList<>();
+    }
+
+    /**
+     * Puts the authenticator timestamp into the cache in descending order,
+     * and throw an exception if it's already there.
+     */
+    public void put(AuthTimeWithHash t, KerberosTime currentTime)
+            throws KrbApErrException {
+
+        if (entries.isEmpty()) {
+            entries.addFirst(t);
+        } else {
+            AuthTimeWithHash temp = entries.getFirst();
+            int cmp = temp.compareTo(t);
+            if (cmp < 0) {
+                // This is the most common case, newly received authenticator
+                // has larger timestamp.
+                entries.addFirst(t);
+            } else if (cmp == 0) {
+                throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
+            } else {
+                //unless client clock being re-adjusted.
+                ListIterator<AuthTimeWithHash> it = entries.listIterator(1);
+                boolean found = false;
+                while (it.hasNext()) {
+                    temp = it.next();
+                    cmp = temp.compareTo(t);
+                    if (cmp < 0) {
+                        // Find an older one, put in front of it
+                        entries.add(entries.indexOf(temp), t);
+                        found = true;
+                        break;
+                    } else if (cmp == 0) {
+                        throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
+                    }
+                }
+                if (!found) {
+                    // All is newer than the newcomer. Sigh.
+                    entries.addLast(t);
+                }
+            }
+        }
+
+        // let us cleanup while we are here
+        long timeLimit = currentTime.getSeconds() - lifespan;
+        ListIterator<AuthTimeWithHash> it = entries.listIterator(0);
+        AuthTimeWithHash temp = null;
+        int index = -1;
+        while (it.hasNext()) {
+            // search expired timestamps.
+            temp = it.next();
+            if (temp.ctime < timeLimit) {
+                index = entries.indexOf(temp);
+                break;
+            }
+        }
+        // It would be nice if LinkedList has a method called truncate(index).
+        if (index > -1) {
+            do {
+                // remove expired timestamps from the list.
+                entries.removeLast();
+            } while(entries.size() > index);
+        }
+    }
+
+    public boolean isEmpty() {
+        return entries.isEmpty();
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        Iterator<AuthTimeWithHash> iter = entries.descendingIterator();
+        int pos = entries.size();
+        while (iter.hasNext()) {
+            AuthTimeWithHash at = iter.next();
+            sb.append('#').append(pos--).append(": ")
+                    .append(at.toString()).append('\n');
+        }
+        return sb.toString();
+    }
+}
--- a/src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java	Tue Jan 28 05:30:42 2020 +0000
@@ -1,4 +1,5 @@
 /*
+ * 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
@@ -30,54 +31,126 @@
 
 package sun.security.krb5.internal.rcache;
 
-import sun.security.krb5.internal.KerberosTime;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.StringTokenizer;
 
 /**
- * The class represents the timestamp in authenticator.
+ * The class represents an old style replay cache entry. It is only used in
+ * a dfl file.
  *
+ * @author Sun/Oracle
  * @author Yanni Zhang
  */
 public class AuthTime {
-    long kerberosTime;
-    int cusec;
+    final int ctime;
+    final int cusec;
+    final String client;
+    final String server;
+
+    /**
+     * Constructs an <code>AuthTime</code>.
+     */
+    public AuthTime(String client, String server,
+            int ctime, int cusec) {
+        this.ctime = ctime;
+        this.cusec = cusec;
+        this.client = client;
+        this.server = server;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%d/%06d/----/%s", ctime, cusec, client);
+    }
+
+    // Methods used when saved in a dfl file. See DflCache.java
 
     /**
-     * Constructs a new <code>AuthTime</code>.
-     * @param time time from the <code>Authenticator</code>.
-     * @param cusec microsecond field from the <code>Authenticator</code>.
+     * Reads an LC style string from a channel, which is a int32 length
+     * plus a UTF-8 encoded string possibly ends with \0.
+     * @throws IOException if there is a format error
+     * @throws BufferUnderflowException if goes beyond the end
      */
-    public AuthTime(long time, int c) {
-        kerberosTime = time;
-        cusec = c;
+    private static String readStringWithLength(SeekableByteChannel chan)
+            throws IOException {
+        ByteBuffer bb = ByteBuffer.allocate(4);
+        bb.order(ByteOrder.nativeOrder());
+        chan.read(bb);
+        bb.flip();
+        int len = bb.getInt();
+        if (len > 1024) {
+            // Memory attack? The string should be fairly short.
+            throw new IOException("Invalid string length");
+        }
+        bb = ByteBuffer.allocate(len);
+        if (chan.read(bb) != len) {
+            throw new IOException("Not enough string");
+        }
+        byte[] data = bb.array();
+        return (data[len-1] == 0)?
+                new String(data, 0, len-1, StandardCharsets.UTF_8):
+                new String(data, StandardCharsets.UTF_8);
     }
 
     /**
-     * Compares if an object equals to an <code>AuthTime</code> object.
-     * @param o an object.
-     * @return true if two objects are equivalent, otherwise, return false.
+     * Reads an AuthTime or AuthTimeWithHash object from a channel.
+     * @throws IOException if there is a format error
+     * @throws BufferUnderflowException if goes beyond the end
      */
-    public boolean equals(Object o) {
-        if (o instanceof AuthTime) {
-            if ((((AuthTime)o).kerberosTime == kerberosTime)
-                && (((AuthTime)o).cusec == cusec)) {
-                return true;
+    public static AuthTime readFrom(SeekableByteChannel chan)
+            throws IOException {
+        String client = readStringWithLength(chan);
+        String server = readStringWithLength(chan);
+        ByteBuffer bb = ByteBuffer.allocate(8);
+        chan.read(bb);
+        bb.order(ByteOrder.nativeOrder());
+        int cusec = bb.getInt(0);
+        int ctime = bb.getInt(4);
+        if (client.isEmpty()) {
+            StringTokenizer st = new StringTokenizer(server, " :");
+            if (st.countTokens() != 6) {
+                throw new IOException("Incorrect rcache style");
             }
+            st.nextToken();
+            String hash = st.nextToken();
+            st.nextToken();
+            client = st.nextToken();
+            st.nextToken();
+            server = st.nextToken();
+            return new AuthTimeWithHash(
+                    client, server, ctime, cusec, hash);
+        } else {
+            return new AuthTime(
+                    client, server, ctime, cusec);
         }
-        return false;
     }
 
     /**
-     * Returns a hash code for this <code>AuthTime</code> object.
-     *
-     * @return  a <code>hash code</code> value for this object.
+     * Encodes to be used in a dfl file
      */
-    public int hashCode() {
-        int result = 17;
-
-        result = 37 * result + (int)(kerberosTime ^ (kerberosTime >>> 32));
-        result = 37 * result + cusec;
-
-        return result;
+    protected byte[] encode0(String cstring, String sstring) {
+        byte[] c = cstring.getBytes(StandardCharsets.UTF_8);;
+        byte[] s = sstring.getBytes(StandardCharsets.UTF_8);;
+        byte[] zero = new byte[1];
+        int len = 4 + c.length + 1 + 4 + s.length + 1 + 4 + 4;
+        ByteBuffer bb = ByteBuffer.allocate(len)
+                .order(ByteOrder.nativeOrder());
+        bb.putInt(c.length+1).put(c).put(zero)
+                .putInt(s.length+1).put(s).put(zero)
+                .putInt(cusec).putInt(ctime);
+        return bb.array();
     }
 
+    /**
+     * Encodes to be used in a dfl file
+     * @param withHash useless here
+     */
+    public byte[] encode(boolean withHash) {
+        return encode0(client, server);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,124 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.security.krb5.internal.rcache;
+
+import java.util.Objects;
+
+/**
+ * The class represents a new style replay cache entry. It can be either used
+ * inside memory or in a dfl file.
+ */
+public class AuthTimeWithHash extends AuthTime
+        implements Comparable<AuthTimeWithHash> {
+
+    final String hash;
+
+    /**
+     * Constructs a new <code>AuthTimeWithHash</code>.
+     */
+    public AuthTimeWithHash(String client, String server,
+            int ctime, int cusec, String hash) {
+        super(client, server, ctime, cusec);
+        this.hash = hash;
+    }
+
+    /**
+     * Compares if an object equals to an <code>AuthTimeWithHash</code> object.
+     * @param o an object.
+     * @return true if two objects are equivalent, otherwise, return false.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AuthTimeWithHash)) return false;
+        AuthTimeWithHash that = (AuthTimeWithHash)o;
+        return Objects.equals(hash, that.hash)
+                && Objects.equals(client, that.client)
+                && Objects.equals(server, that.server)
+                && ctime == that.ctime
+                && cusec == that.cusec;
+    }
+
+    /**
+     * Returns a hash code for this <code>AuthTimeWithHash</code> object.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(hash);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%d/%06d/%s/%s", ctime, cusec, hash, client);
+    }
+
+    @Override
+    public int compareTo(AuthTimeWithHash other) {
+        int cmp = 0;
+        if (ctime != other.ctime) {
+            cmp = Integer.compare(ctime, other.ctime);
+        } else if (cusec != other.cusec) {
+            cmp = Integer.compare(cusec, other.cusec);
+        } else {
+            cmp = hash.compareTo(other.hash);
+        }
+        return cmp;
+    }
+
+    /**
+     * 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) {
+        return  client.equals(old.client) &&
+                server.equals(old.server) &&
+                ctime == old.ctime &&
+                cusec == old.cusec;
+    }
+
+    // Methods used when saved in a dfl file. See DflCache.java
+
+    /**
+     * Encodes to be used in a dfl file
+     * @param withHash write new style if true
+     */
+    @Override
+    public byte[] encode(boolean withHash) {
+        String cstring;
+        String sstring;
+        if (withHash) {
+            cstring = "";
+            sstring = String.format("HASH:%s %d:%s %d:%s", hash,
+                    client.length(), client,
+                    server.length(), server);
+        } else {
+            cstring = client;
+            sstring = server;
+        }
+        return encode0(cstring, sstring);
+    }
+}
--- a/src/share/classes/sun/security/krb5/internal/rcache/CacheTable.java	Wed Nov 16 11:53:40 2011 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-/*
- *
- *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
- *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
- */
-
-package sun.security.krb5.internal.rcache;
-
-import java.util.Hashtable;
-import sun.security.krb5.internal.KerberosTime;
-
-
-/**
- * This class implements Hashtable to store the replay caches.
- *
- * @author Yanni Zhang
- */
-public class CacheTable extends Hashtable<String,ReplayCache> {
-
-    private static final long serialVersionUID = -4695501354546664910L;
-
-    private boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
-    public CacheTable () {
-    }
-
-    /**
-     * Puts the client timestamp in replay cache.
-     * @params principal the client's principal name.
-     * @params time authenticator timestamp.
-     */
-    public synchronized void put(String principal, AuthTime time, long currTime) {
-        ReplayCache rc = super.get(principal);
-        if (rc == null) {
-            if (DEBUG) {
-                System.out.println("replay cache for " + principal + " is null.");
-            }
-            rc = new ReplayCache(principal, this);
-            rc.put(time, currTime);
-            super.put(principal, rc);
-        }
-        else {
-            rc.put(time, currTime);
-            // re-insert the entry, since rc.put could have removed the entry
-            super.put(principal, rc);
-            if (DEBUG) {
-                System.out.println("replay cache found.");
-            }
-        }
-
-    }
-
-    /**
-     * This method tests if replay cache keeps a record of the authenticator's time stamp.
-     * If there is a record (replay attack detected), the server should reject the client request.
-     * @params principal the client's principal name.
-     * @params time authenticator timestamp.
-     * @return null if no record found, else return an <code>AuthTime</code> object.
-     */
-    public Object get(AuthTime time, String principal) {
-        ReplayCache rc = super.get(principal);
-        if ((rc != null) && (rc.contains(time))) {
-            return time;
-        }
-        return null;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/krb5/internal/rcache/DflCache.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,365 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+
+package sun.security.krb5.internal.rcache;
+
+import java.io.*;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.AccessController;
+import java.util.*;
+
+import sun.security.action.GetPropertyAction;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.Krb5;
+import sun.security.krb5.internal.KrbApErrException;
+import sun.security.krb5.internal.ReplayCache;
+
+
+/**
+ * A dfl file is used to sustores AuthTime entries when the system property
+ * sun.security.krb5.rcache is set to
+ *
+ *    dfl(|:path/|:path/name|:name)
+ *
+ * The file will be path/name. If path is not given, it will be
+ *
+ *    System.getProperty("java.io.tmpdir")
+ *
+ * If name is not given, it will be
+ *
+ *    service_euid
+ *
+ * Java does not have a method to get euid, so uid is used instead. This
+ * should normally to be since a Java program is seldom used as a setuid app.
+ *
+ * The file has a header:
+ *
+ *    i16 0x0501 (KRB5_RC_VNO) in network order
+ *    i32 number of seconds for lifespan (in native order, same below)
+ *
+ * followed by cache entries concatenated, which can be encoded in
+ * 2 styles:
+ *
+ * The traditional style is:
+ *
+ *    LC of client principal
+ *    LC of server principal
+ *    i32 cusec of Authenticator
+ *    i32 ctime of Authenticator
+ *
+ * The new style has a hash:
+ *
+ *    LC of ""
+ *    LC of "HASH:%s %lu:%s %lu:%s" of (hash, clientlen, client, serverlen,
+ *          server) where msghash is 32 char (lower case) text mode md5sum
+ *          of the ciphertext of authenticator.
+ *    i32 cusec of Authenticator
+ *    i32 ctime of Authenticator
+ *
+ * where LC of a string means
+ *
+ *    i32 strlen(string) + 1
+ *    octets of string, with the \0x00 ending
+ *
+ * The old style block is always created by MIT krb5 used even if a new style
+ * is available, which means there can be 2 entries for a single Authenticator.
+ * Java also does this way.
+ *
+ * See src/lib/krb5/rcache/rc_io.c and src/lib/krb5/rcache/rc_dfl.c.
+ */
+public class DflCache extends ReplayCache {
+
+    private static final int KRB5_RV_VNO = 0x501;
+    private static final int EXCESSREPS = 30;   // if missed-hit>this, recreate
+
+    private final String source;
+
+    private static int uid;
+    static {
+        try {
+            // Available on Solaris, Linux and Mac. Otherwise, no _euid suffix
+            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;
+        }
+    }
+
+    public DflCache (String source) {
+        this.source = source;
+    }
+
+    private static String defaultPath() {
+        return AccessController.doPrivileged(
+                new GetPropertyAction("java.io.tmpdir"));
+    }
+
+    private static String defaultFile(String server) {
+        // service/host@REALM -> service
+        int slash = server.indexOf('/');
+        if (slash == -1) {
+            // A normal principal? say, dummy@REALM
+            slash = server.indexOf('@');
+        }
+        if (slash != -1) {
+            // Should not happen, but be careful
+            server= server.substring(0, slash);
+        }
+        if (uid != -1) {
+            server += "_" + uid;
+        }
+        return server;
+    }
+
+    private static Path getFileName(String source, String server) {
+        String path, file;
+        if (source.equals("dfl")) {
+            path = defaultPath();
+            file = defaultFile(server);
+        } else if (source.startsWith("dfl:")) {
+            source = source.substring(4);
+            int pos = source.lastIndexOf('/');
+            int pos1 = source.lastIndexOf('\\');
+            if (pos1 > pos) pos = pos1;
+            if (pos == -1) {
+                // Only file name
+                path = defaultPath();
+                file = source;
+            } else if (new File(source).isDirectory()) {
+                // Only path
+                path = source;
+                file = defaultFile(server);
+            } else {
+                // Full pathname
+                path = null;
+                file = source;
+            }
+        } else {
+            throw new IllegalArgumentException();
+        }
+        return new File(path, file).toPath();
+    }
+
+    @Override
+    public void checkAndStore(KerberosTime currTime, AuthTimeWithHash time)
+            throws KrbApErrException {
+        try {
+            checkAndStore0(currTime, time);
+        } catch (IOException ioe) {
+            KrbApErrException ke = new KrbApErrException(Krb5.KRB_ERR_GENERIC);
+            ke.initCause(ioe);
+            throw ke;
+        }
+    }
+
+    private synchronized void checkAndStore0(KerberosTime currTime, AuthTimeWithHash time)
+            throws IOException, KrbApErrException {
+        Path p = getFileName(source, time.server);
+        int missed = 0;
+        try (Storage s = new Storage()) {
+            try {
+                missed = s.loadAndCheck(p, time, currTime);
+            } catch (IOException ioe) {
+                // Non-existing or invalid file
+                Storage.create(p);
+                missed = s.loadAndCheck(p, time, currTime);
+            }
+            s.append(time);
+        }
+        if (missed > EXCESSREPS) {
+            Storage.expunge(p, currTime);
+        }
+    }
+
+
+    private static class Storage implements Closeable {
+        // Static methods
+        @SuppressWarnings("try")
+        private static void create(Path p) throws IOException {
+            try (SeekableByteChannel newChan = createNoClose(p)) {
+                // Do nothing, wait for close
+            }
+            makeMine(p);
+        }
+
+        private static void makeMine(Path p) throws IOException {
+            // chmod to owner-rw only, otherwise MIT krb5 rejects
+            try {
+                Set<PosixFilePermission> attrs = new HashSet<>();
+                attrs.add(PosixFilePermission.OWNER_READ);
+                attrs.add(PosixFilePermission.OWNER_WRITE);
+                Files.setPosixFilePermissions(p, attrs);
+            } catch (UnsupportedOperationException uoe) {
+                // No POSIX permission. That's OK.
+            }
+        }
+
+        private static SeekableByteChannel createNoClose(Path p)
+                throws IOException {
+            SeekableByteChannel newChan = Files.newByteChannel(
+                    p, StandardOpenOption.CREATE,
+                        StandardOpenOption.TRUNCATE_EXISTING,
+                        StandardOpenOption.WRITE);
+            ByteBuffer buffer = ByteBuffer.allocate(6);
+            buffer.putShort((short)KRB5_RV_VNO);
+            buffer.order(ByteOrder.nativeOrder());
+            buffer.putInt(KerberosTime.getDefaultSkew());
+            buffer.flip();
+            newChan.write(buffer);
+            return newChan;
+        }
+
+        private static void expunge(Path p, KerberosTime currTime)
+                throws IOException {
+            Path p2 = Files.createTempFile(p.getParent(), "rcache", null);
+            try (SeekableByteChannel oldChan = Files.newByteChannel(p);
+                    SeekableByteChannel newChan = createNoClose(p2)) {
+                long timeLimit = currTime.getSeconds() - readHeader(oldChan);
+                while (true) {
+                    try {
+                        AuthTime at = AuthTime.readFrom(oldChan);
+                        if (at.ctime > timeLimit) {
+                            ByteBuffer bb = ByteBuffer.wrap(at.encode(true));
+                            newChan.write(bb);
+                        }
+                    } catch (BufferUnderflowException e) {
+                        break;
+                    }
+                }
+            }
+            makeMine(p2);
+            Files.move(p2, p,
+                    StandardCopyOption.REPLACE_EXISTING,
+                    StandardCopyOption.ATOMIC_MOVE);
+        }
+
+        // Instance methods
+        SeekableByteChannel chan;
+        private int loadAndCheck(Path p, AuthTimeWithHash time,
+                KerberosTime currTime)
+                throws IOException, KrbApErrException {
+            int missed = 0;
+            if (Files.isSymbolicLink(p)) {
+                throw new IOException("Symlink not accepted");
+            }
+            try {
+                Set<PosixFilePermission> perms =
+                        Files.getPosixFilePermissions(p);
+                if (uid != -1 &&
+                        (Integer)Files.getAttribute(p, "unix:uid") != uid) {
+                    throw new IOException("Not mine");
+                }
+                if (perms.contains(PosixFilePermission.GROUP_READ) ||
+                        perms.contains(PosixFilePermission.GROUP_WRITE) ||
+                        perms.contains(PosixFilePermission.GROUP_EXECUTE) ||
+                        perms.contains(PosixFilePermission.OTHERS_READ) ||
+                        perms.contains(PosixFilePermission.OTHERS_WRITE) ||
+                        perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
+                    throw new IOException("Accessible by someone else");
+                }
+            } catch (UnsupportedOperationException uoe) {
+                // No POSIX permissions? Ignore it.
+            }
+            chan = Files.newByteChannel(p, StandardOpenOption.WRITE,
+                    StandardOpenOption.READ);
+
+            long timeLimit = currTime.getSeconds() - readHeader(chan);
+
+            long pos = 0;
+            boolean seeNewButNotSame = false;
+            while (true) {
+                try {
+                    pos = chan.position();
+                    AuthTime a = AuthTime.readFrom(chan);
+                    if (a instanceof AuthTimeWithHash) {
+                        if (time.equals(a)) {
+                            // Exact match, must be a replay
+                            throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
+                        } else if (time.isSameIgnoresHash(a)) {
+                            // Two different authenticators in the same second.
+                            // Remember it
+                            seeNewButNotSame = true;
+                        }
+                    } else {
+                        if (time.isSameIgnoresHash(a)) {
+                            // Two authenticators in the same second. Considered
+                            // same if we haven't seen a new style version of it
+                            if (!seeNewButNotSame) {
+                                throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
+                            }
+                        }
+                    }
+                    if (a.ctime < timeLimit) {
+                        missed++;
+                    } else {
+                        missed--;
+                    }
+                } catch (BufferUnderflowException e) {
+                    // Half-written file?
+                    chan.position(pos);
+                    break;
+                }
+            }
+            return missed;
+        }
+
+        private static int readHeader(SeekableByteChannel chan)
+                throws IOException {
+            ByteBuffer bb = ByteBuffer.allocate(6);
+            chan.read(bb);
+            if (bb.getShort(0) != KRB5_RV_VNO) {
+                throw new IOException("Not correct rcache version");
+            }
+            bb.order(ByteOrder.nativeOrder());
+            return bb.getInt(2);
+        }
+
+        private void append(AuthTimeWithHash at) throws IOException {
+            // Write an entry with hash, to be followed by one without it,
+            // for the benefit of old implementations.
+            ByteBuffer bb;
+            bb = ByteBuffer.wrap(at.encode(true));
+            chan.write(bb);
+            bb = ByteBuffer.wrap(at.encode(false));
+            chan.write(bb);
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (chan != null) chan.close();
+            chan = null;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/krb5/internal/rcache/MemoryCache.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,86 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/*
+ *
+ *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
+ *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
+ */
+
+package sun.security.krb5.internal.rcache;
+
+import java.util.*;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.KrbApErrException;
+import sun.security.krb5.internal.ReplayCache;
+
+
+/**
+ * This class stores replay caches. AuthTimeWithHash objects are categorized
+ * into AuthLists keyed by the names of client and server.
+ *
+ * @author Yanni Zhang
+ */
+public class MemoryCache extends ReplayCache {
+
+    // TODO: One day we'll need to read dynamic krb5.conf.
+    private static final int lifespan = KerberosTime.getDefaultSkew();
+    private static final boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
+
+    private final Map<String,AuthList> content = new HashMap<>();
+
+    @Override
+    public synchronized void checkAndStore(KerberosTime currTime, AuthTimeWithHash time)
+            throws KrbApErrException {
+        String key = time.client + "|" + time.server;
+        AuthList rc = content.get(key);
+        if (DEBUG) {
+            System.out.println("MemoryCache: add " + time + " to " + key);
+        }
+        if (rc == null) {
+            rc = new AuthList(lifespan);
+            rc.put(time, currTime);
+            if (!rc.isEmpty()) {
+                content.put(key, rc);
+            }
+        } else {
+            if (DEBUG) {
+                System.out.println("MemoryCache: Existing AuthList:\n" + rc);
+            }
+            rc.put(time, currTime);
+            if (rc.isEmpty()) {
+                content.remove(key);
+            }
+        }
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (AuthList rc: content.values()) {
+            sb.append(rc.toString());
+        }
+        return sb.toString();
+    }
+}
--- a/src/share/classes/sun/security/krb5/internal/rcache/ReplayCache.java	Wed Nov 16 11:53:40 2011 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-/*
- * Copyright (c) 2000, 2006, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-/*
- *
- *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
- *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
- */
-
-package sun.security.krb5.internal.rcache;
-
-import sun.security.krb5.internal.Krb5;
-import java.util.LinkedList;
-import java.util.ListIterator;
-import sun.security.krb5.internal.KerberosTime;
-
-/**
- * This class provides an efficient caching mechanism to store the timestamp of client authenticators.
- * The cache minimizes the memory usage by doing self-cleanup of expired items in the cache.
- *
- * @author Yanni Zhang
- */
-public class ReplayCache extends LinkedList<AuthTime> {
-
-    private static final long serialVersionUID = 2997933194993803994L;
-
-    // These 3 fields are now useless, keep for serialization compatibility
-    private String principal;
-    private CacheTable table;
-    private int nap = 10 * 60 * 1000; //10 minutes break
-
-    private boolean DEBUG = Krb5.DEBUG;
-
-    /**
-     * Constructs a ReplayCache for a client principal in specified <code>CacheTable</code>.
-     * @param p client principal name.
-     * @param ct CacheTable.
-     */
-    public ReplayCache (String p, CacheTable ct) {
-        principal = p;
-        table = ct;
-    }
-
-    /**
-     * Puts the authenticator timestamp into the cache in descending order.
-     * @param t <code>AuthTime</code>
-     */
-    public synchronized void put(AuthTime t, long currentTime) {
-
-        if (this.size() == 0) {
-            addFirst(t);
-        }
-        else {
-            AuthTime temp = getFirst();
-            if (temp.kerberosTime < t.kerberosTime) {
-                // in most cases, newly received authenticator has
-                // larger timestamp.
-                addFirst(t);
-            }
-            else if (temp.kerberosTime == t.kerberosTime) {
-                if (temp.cusec < t.cusec) {
-                    addFirst(t);
-                }
-            }
-            else {
-                //unless client clock being re-adjusted.
-                ListIterator<AuthTime> it = listIterator(1);
-                while (it.hasNext()) {
-                    temp = it.next();
-                    if (temp.kerberosTime < t.kerberosTime) {
-                        add(indexOf(temp), t);
-                        break;
-                        //we always put the bigger timestamp at the front.
-                    }
-                    else if (temp.kerberosTime == t.kerberosTime) {
-                        if (temp.cusec < t.cusec) {
-                            add(indexOf(temp), t);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-
-        // let us cleanup while we are here
-        long timeLimit = currentTime - KerberosTime.getDefaultSkew() * 1000L;
-        ListIterator<AuthTime> it = listIterator(0);
-        AuthTime temp = null;
-        int index = -1;
-        while (it.hasNext()) {
-            //search expired timestamps.
-            temp = it.next();
-            if (temp.kerberosTime < timeLimit) {
-                index = indexOf(temp);
-                break;
-            }
-        }
-        if (index > -1) {
-            do {
-                //remove expired timestamps from the list.
-                removeLast();
-            } while(size() > index);
-        }
-        if (DEBUG) {
-            printList();
-        }
-    }
-
-
-    /**
-     * Prints out the debug message.
-     */
-    private void printList() {
-        Object[] total = toArray();
-        for (int i = 0; i < total.length; i++) {
-            System.out.println("object " + i + ": " + ((AuthTime)total[i]).kerberosTime + "/"
-                               + ((AuthTime)total[i]).cusec);
-        }
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/security/testlibrary/Proc.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,327 @@
+/*
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import sun.misc.BASE64Decoder;
+import sun.misc.BASE64Encoder;
+
+/**
+ * This is a test library that makes writing a Java test that spawns multiple
+ * Java processes easily.
+ *
+ * Usage:
+ *
+ *    Proc.create("Clazz")      // The class to launch
+ *        .args("x")            // with args
+ *        .env("env", "value")  // and an environment variable
+ *        .prop("key","value")  // and a system property
+ *        .perm(perm)           // with granted permissions
+ *        .start();             // and start
+ *
+ * create/start must be called, args/env/prop/perm can be called zero or
+ * multiple times between create and start.
+ *
+ * The controller can call inheritIO to share its I/O to the process.
+ * Otherwise, it can send data into a proc's stdin with write/println, and
+ * read its stdout with readLine. stderr is always redirected to DFILE
+ * unless nodump() is called. A protocol is designed to make
+ * data exchange among the controller and the processes super easy, in which
+ * useful data are always printed with a special prefix ("PROCISFUN:").
+ * If the data is binary, make it BASE64.
+ *
+ * For example:
+ *
+ * - A producer Proc calls Proc.binOut() or Proc.textOut() to send out data.
+ *   This method would prints to the stdout something like
+ *
+ *      PROCISFUN:[raw text or base64 binary]
+ *
+ * - The controller calls producer.readData() to get the content. This method
+ *   ignores all other output and only reads lines starting with "PROCISFUN:".
+ *
+ * - The controller does not care if the context is text or base64, it simply
+ *   feeds the data to a consumer Proc by calling consumer.println(data).
+ *   This will be printed into System.in of the consumer process.
+ *
+ * - The consumer Proc calls Proc.binIn() or Proc.textIn() to read the data.
+ *   The first method de-base64 the input and return a byte[] block.
+ *
+ * Please note only plain ASCII is supported in raw text at the moment.
+ *
+ * As the Proc objects are hidden so deeply, two static methods, d(String) and
+ * d(Throwable) are provided to output info into stderr, where they will
+ * normally be appended messages to DFILE (unless nodump() is called).
+ * Developers can view the messages in real time by calling
+ *
+ *    tail -f proc.debug
+ *
+ * TODO:
+ *
+ * . launch java tools, say, keytool
+ * . launch another version of java
+ * . start in another directory
+ * . start and finish using one method
+ *
+ * This is not a test, but is the core of
+ * JDK-8009977: A test library to launch multiple Java processes
+ */
+public class Proc {
+    private Process p;
+    private BufferedReader br;      // the stdout of a process
+    private String launcher;        // Optional: the java program
+
+    private List<Permission> perms = new ArrayList<>();
+    private List<String> args = new ArrayList<>();
+    private Map<String,String> env = new HashMap<>();
+    private Map<String,String> prop = new HashMap();
+    private boolean inheritIO = false;
+    private boolean noDump = false;
+
+    private String clazz;           // Class to launch
+    private String debug;           // debug flag, controller will show data
+                                    // transfer between procs
+
+    final private static String PREFIX = "PROCISFUN:";
+    final private static String DFILE = "proc.debug";
+
+    // The following methods are called by controllers
+
+    // Creates a Proc by the Java class name, launcher is an optional
+    // argument to specify the java program
+    public static Proc create(String clazz, String... launcher) {
+        Proc pc = new Proc();
+        pc.clazz = clazz;
+        if (launcher.length > 0) {
+            pc.launcher = launcher[0];
+        }
+        return pc;
+    }
+    // Sets inheritIO flag to proc. If set, proc will same I/O channels as
+    // teh controller. Otherwise, its stdin/stdout is untouched, and its
+    // stderr is redirected to DFILE.
+    public Proc inheritIO() {
+        inheritIO = true;
+        return this;
+    }
+    // When called, stderr inherits parent stderr, otherwise, append to a file
+    public Proc nodump() {
+        noDump = true;
+        return this;
+    }
+    // Specifies some args. Can be called multiple times.
+    public Proc args(String... args) {
+        for (String c: args) {
+            this.args.add(c);
+        }
+        return this;
+    }
+    // Returns debug prefix
+    public String debug() {
+        return debug;
+    }
+    // Enables debug with prefix
+    public Proc debug(String title) {
+        debug = title;
+        return this;
+    }
+    // Specifies an env var. Can be called multiple times.
+    public Proc env(String a, String b) {
+        env.put(a, b);
+        return this;
+    }
+    // Specifies a Java system property. Can be called multiple times.
+    public Proc prop(String a, String b) {
+        prop.put(a, b);
+        return this;
+    }
+    // Adds a perm to policy. Can be called multiple times. In order to make it
+    // effective, please also call prop("java.security.manager", "").
+    public Proc perm(Permission p) {
+        perms.add(p);
+        return this;
+    }
+    // Starts the proc
+    public Proc start() throws IOException {
+        List<String> cmd = new ArrayList<>();
+        if (launcher != null) {
+            cmd.add(launcher);
+        } else {
+            cmd.add(new File(new File(System.getProperty("java.home"), "bin"),
+                        "java").getPath());
+        }
+        cmd.add("-cp");
+        StringBuilder cp = new StringBuilder();
+        for (URL url: ((URLClassLoader)Proc.class.getClassLoader()).getURLs()) {
+            if (cp.length() != 0) {
+                cp.append(File.pathSeparatorChar);
+            }
+            cp.append(url.getFile());
+        }
+        cmd.add(cp.toString());
+        for (Entry<String,String> e: prop.entrySet()) {
+            cmd.add("-D" + e.getKey() + "=" + e.getValue());
+        }
+        if (!perms.isEmpty()) {
+            Path p = Files.createTempFile(
+                    Paths.get(".").toAbsolutePath(), "policy", null);
+            StringBuilder sb = new StringBuilder();
+            sb.append("grant {\n");
+            for (Permission perm: perms) {
+                // Sometimes a permission has no name or actions.
+                // but it's safe to use an empty string.
+                String s = String.format("%s \"%s\", \"%s\"",
+                        perm.getClass().getCanonicalName(),
+                        perm.getName()
+                                .replace("\\", "\\\\").replace("\"", "\\\""),
+                        perm.getActions());
+                sb.append("    permission ").append(s).append(";\n");
+            }
+            sb.append("};\n");
+            Files.write(p, sb.toString().getBytes());
+            cmd.add("-Djava.security.policy=" + p.toString());
+        }
+        cmd.add(clazz);
+        for (String s: args) {
+            cmd.add(s);
+        }
+        if (debug != null) {
+            System.out.println("PROC: " + debug + " cmdline: " + cmd);
+        }
+        ProcessBuilder pb = new ProcessBuilder(cmd);
+        for (Entry<String,String> e: env.entrySet()) {
+            pb.environment().put(e.getKey(), e.getValue());
+        }
+        if (inheritIO) {
+            pb.inheritIO();
+        } else if (noDump) {
+            pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+        } else {
+            pb.redirectError(ProcessBuilder.Redirect.appendTo(new File(DFILE)));
+        }
+        p = pb.start();
+        br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        return this;
+    }
+    // Reads a line from stdout of proc
+    public String readLine() throws IOException {
+        String s = br.readLine();
+        if (debug != null) {
+            System.out.println("PROC: " + debug + " readline: " +
+                    (s == null ? "<EOF>" : s));
+        }
+        return s;
+    }
+    // Reads a special line from stdout of proc
+    public String readData() throws Exception {
+        while (true) {
+            String s = readLine();
+            if (s == null) {
+                if (p.waitFor() != 0) {
+                    throw new Exception("Proc abnormal end");
+                } else {
+                    return s;
+                }
+            }
+            if (s.startsWith(PREFIX)) {
+                return s.substring(PREFIX.length());
+            }
+        }
+    }
+    // Writes text into stdin of proc
+    public void println(String s) throws IOException {
+        if (debug != null) {
+            System.out.println("PROC: " + debug + " println: " + s);
+        }
+        write((s + "\n").getBytes());
+    }
+    // Writes data into stdin of proc
+    public void write(byte[] b) throws IOException {
+        p.getOutputStream().write(b);
+        p.getOutputStream().flush();
+    }
+    // Reads all output and wait for process end
+    public int waitFor() throws Exception {
+        while (true) {
+            String s = readLine();
+            if (s == null) {
+                break;
+            }
+        }
+        return p.waitFor();
+    }
+
+    // The following methods are used inside a proc
+
+    // Writes out a BASE64 binary with a prefix
+    public static void binOut(byte[] data) {
+        System.out.println(PREFIX + new BASE64Encoder().encode(data));
+    }
+    // Reads in a line of BASE64 binary
+    public static byte[] binIn() throws Exception {
+        return new BASE64Decoder().decodeBuffer(textIn());
+    }
+    // Writes out a text with a prefix
+    public static void textOut(String data) {
+        System.out.println(PREFIX + data);
+    }
+    // Reads in a line of text
+    public static String textIn() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        boolean isEmpty = true;
+        while (true) {
+            int i = System.in.read();
+            if (i == -1) break;
+            isEmpty = false;
+            if (i == '\n') break;
+            if (i != 13) {
+                // Force it to a char, so only simple ASCII works.
+                sb.append((char)i);
+            }
+        }
+        return isEmpty ? null : sb.toString();
+    }
+    // Sends string to stderr. If inheritIO is not called, they will
+    // be collected into DFILE
+    public static void d(String s) throws IOException {
+        System.err.println(s);
+    }
+    // Sends an exception to stderr
+    public static void d(Throwable e) throws IOException {
+        e.printStackTrace();
+    }
+}
--- a/test/sun/security/krb5/auto/AcceptorSubKey.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/test/sun/security/krb5/auto/AcceptorSubKey.java	Tue Jan 28 05:30:42 2020 +0000
@@ -26,10 +26,10 @@
  * @bug 7077646
  * @summary gssapi wrap for CFX per-message tokens always set FLAG_ACCEPTOR_SUBKEY
  * @compile -XDignore.symbol.file AcceptorSubKey.java
- * @run main/othervm AcceptorSubKey
+ * @run main/othervm AcceptorSubKey 0
+ * @run main/othervm AcceptorSubKey 4
  */
 
-import java.util.Arrays;
 import sun.security.jgss.GSSUtil;
 
 // The basic krb5 test skeleton you can copy from
@@ -37,8 +37,14 @@
 
     public static void main(String[] args) throws Exception {
 
+        int expected = Integer.parseInt(args[0]);
+
         new OneKDC(null).writeJAASConf();
 
+        if (expected != 0) {
+            System.setProperty("sun.security.krb5.acceptor.subkey", "true");
+        }
+
         Context c, s;
         c = Context.fromJAAS("client");
         s = Context.fromJAAS("server");
@@ -53,8 +59,8 @@
 
         // FLAG_ACCEPTOR_SUBKEY is 4
         int flagOn = wrapped[2] & 4;
-        if (flagOn != 0) {
-            throw new Exception("Java GSS should not have set acceptor subkey");
+        if (flagOn != expected) {
+            throw new Exception("not expected");
         }
 
         s.dispose();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/BasicProc.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,192 @@
+/*
+ * 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 8009977
+ * @summary A test library to launch multiple Java processes
+ * @library ../../../../java/security/testlibrary/
+ * @compile -XDignore.symbol.file BasicProc.java
+ * @run main/othervm BasicProc
+ */
+
+import java.io.File;
+import org.ietf.jgss.Oid;
+
+import javax.security.auth.PrivateCredentialPermission;
+
+public class BasicProc {
+
+    static String CONF = "krb5.conf";
+    static String KTAB = "ktab";
+    public static void main(String[] args) throws Exception {
+        String HOST = "localhost";
+        String SERVER = "server/" + HOST;
+        String BACKEND = "backend/" + HOST;
+        String USER = "user";
+        char[] PASS = "password".toCharArray();
+        String REALM = "REALM";
+
+        Oid oid = new Oid("1.2.840.113554.1.2.2");
+
+        if (args.length == 0) {
+            System.setProperty("java.security.krb5.conf", CONF);
+            KDC kdc = KDC.create(REALM, HOST, 0, true);
+            kdc.addPrincipal(USER, PASS);
+            kdc.addPrincipalRandKey("krbtgt/" + REALM);
+            kdc.addPrincipalRandKey(SERVER);
+            kdc.addPrincipalRandKey(BACKEND);
+
+            String cwd = System.getProperty("user.dir");
+            kdc.writeKtab(KTAB);
+            KDC.saveConfig(CONF, kdc, "forwardable = true");
+
+            Proc pc = Proc.create("BasicProc")
+                    .args("client")
+                    .prop("java.security.krb5.conf", CONF)
+                    .prop("java.security.manager", "")
+                    .perm(new java.util.PropertyPermission(
+                            "sun.security.krb5.principal", "read"))
+                    .perm(new javax.security.auth.AuthPermission(
+                            "modifyPrincipals"))
+                    .perm(new javax.security.auth.AuthPermission(
+                            "modifyPrivateCredentials"))
+                    .perm(new javax.security.auth.AuthPermission("doAs"))
+                    .perm(new javax.security.auth.kerberos.ServicePermission(
+                            "krbtgt/" + REALM + "@" + REALM, "initiate"))
+                    .perm(new javax.security.auth.kerberos.ServicePermission(
+                            "server/localhost@" + REALM, "initiate"))
+                    .perm(new javax.security.auth.kerberos.DelegationPermission(
+                            "\"server/localhost@" + REALM + "\" " +
+                                    "\"krbtgt/" + REALM + "@" + REALM + "\""))
+                    .debug("C")
+                    .start();
+            Proc ps = Proc.create("BasicProc")
+                    .args("server")
+                    .prop("java.security.krb5.conf", CONF)
+                    .prop("java.security.manager", "")
+                    .perm(new java.util.PropertyPermission(
+                            "sun.security.krb5.principal", "read"))
+                    .perm(new javax.security.auth.AuthPermission(
+                            "modifyPrincipals"))
+                    .perm(new javax.security.auth.AuthPermission(
+                            "modifyPrivateCredentials"))
+                    .perm(new javax.security.auth.AuthPermission("doAs"))
+                    .perm(new PrivateCredentialPermission(
+                            "javax.security.auth.kerberos.KeyTab * \"*\"",
+                            "read"))
+                    .perm(new javax.security.auth.kerberos.ServicePermission(
+                            "server/localhost@" + REALM, "accept"))
+                    .perm(new java.io.FilePermission(
+                            cwd + File.separator + KTAB, "read"))
+                    .perm(new javax.security.auth.kerberos.ServicePermission(
+                            "backend/localhost@" + REALM, "initiate"))
+                    .debug("S")
+                    .start();
+            Proc pb = Proc.create("BasicProc")
+                    .args("backend")
+                    .prop("java.security.krb5.conf", CONF)
+                    .prop("java.security.manager", "")
+                    .perm(new java.util.PropertyPermission(
+                            "sun.security.krb5.principal", "read"))
+                    .perm(new javax.security.auth.AuthPermission(
+                            "modifyPrincipals"))
+                    .perm(new javax.security.auth.AuthPermission(
+                            "modifyPrivateCredentials"))
+                    .perm(new javax.security.auth.AuthPermission("doAs"))
+                    .perm(new PrivateCredentialPermission(
+                            "javax.security.auth.kerberos.KeyTab * \"*\"",
+                            "read"))
+                    .perm(new javax.security.auth.kerberos.ServicePermission(
+                            "backend/localhost@" + REALM, "accept"))
+                    .perm(new java.io.FilePermission(
+                            cwd + File.separator + KTAB, "read"))
+                    .debug("B")
+                    .start();
+
+            // Client and server handshake
+            String token = pc.readData();
+            ps.println(token);
+            token = ps.readData();
+            pc.println(token);
+            // Server and backend handshake
+            token = ps.readData();
+            pb.println(token);
+            token = pb.readData();
+            ps.println(token);
+            // wrap/unwrap/getMic/verifyMic and plain text
+            token = ps.readData();
+            pb.println(token);
+            token = pb.readData();
+            ps.println(token);
+            token = pb.readData();
+            ps.println(token);
+
+            if ((pc.waitFor() | ps.waitFor() | pb.waitFor()) != 0) {
+                throw new Exception();
+            }
+        } else if (args[0].equals("client")) {
+            Context c = Context.fromUserPass(USER, PASS, false);
+            c.startAsClient(SERVER, oid);
+            c.x().requestCredDeleg(true);
+            Proc.binOut(c.take(new byte[0]));
+            byte[] token = Proc.binIn();
+            c.take(token);
+        } else if (args[0].equals("server")) {
+            Context s = Context.fromUserKtab(SERVER, KTAB, true);
+            s.startAsServer(oid);
+            byte[] token = Proc.binIn();
+            token = s.take(token);
+            Proc.binOut(token);
+            Context s2 = s.delegated();
+            s2.startAsClient(BACKEND, oid);
+            Proc.binOut(s2.take(new byte[0]));
+            token = Proc.binIn();
+            s2.take(token);
+            byte[] msg = "Hello".getBytes();
+            Proc.binOut(s2.wrap(msg, true));
+            s2.verifyMic(Proc.binIn(), msg);
+            String in = Proc.textIn();
+            if (!in.equals("Hello")) {
+                throw new Exception();
+            }
+        } else if (args[0].equals("backend")) {
+            Context b = Context.fromUserKtab(BACKEND, KTAB, true);
+            b.startAsServer(oid);
+            byte[] token = Proc.binIn();
+            Proc.binOut(b.take(token));
+            byte[] msg = b.unwrap(Proc.binIn(), true);
+            Proc.binOut(b.getMic(msg));
+            Proc.textOut(new String(msg));
+        }
+    }
+    // create a native server
+    private static Proc ns(Proc p) throws Exception {
+        return p
+            .env("KRB5_CONFIG", CONF)
+            .env("KRB5_KTNAME", KTAB)
+            .prop("sun.security.jgss.native", "true")
+            .prop("javax.security.auth.useSubjectCredsOnly", "false")
+            .prop("sun.security.nativegss.debug", "true");
+    }
+}
--- a/test/sun/security/krb5/auto/Context.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/test/sun/security/krb5/auto/Context.java	Tue Jan 28 05:30:42 2020 +0000
@@ -157,6 +157,7 @@
         Krb5LoginModule krb5 = new Krb5LoginModule();
         Map<String, String> map = new HashMap<>();
 
+        map.put("isInitiator", "false");
         map.put("doNotPrompt", "true");
         map.put("useTicketCache", "false");
         map.put("useKeyTab", "true");
@@ -530,9 +531,10 @@
      */
     static public void handshake(final Context c, final Context s) throws Exception {
         byte[] t = new byte[0];
-        while (!c.x.isEstablished() || !s.x.isEstablished()) {
-            t = c.take(t);
-            t = s.take(t);
+        while (true) {
+            if (t != null || !c.x.isEstablished()) t = c.take(t);
+            if (t != null || !s.x.isEstablished()) t = s.take(t);
+            if (c.x.isEstablished() && s.x.isEstablished()) break;
         }
     }
 }
--- a/test/sun/security/krb5/auto/KDC.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/test/sun/security/krb5/auto/KDC.java	Tue Jan 28 05:30:42 2020 +0000
@@ -1157,7 +1157,7 @@
      * @return REALM.NAME = { kdc = host:port }
      */
     private static String realmLineForKDC(KDC kdc) {
-        return String.format("  %s = {\n    kdc = %s:%d\n  }\n",
+        return String.format("%s = {\n    kdc = %s:%d\n}\n",
                 kdc.realm,
                 kdc.kdc,
                 kdc.port);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/NoneReplayCacheTest.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 8001326
+ * @run main/othervm NoneReplayCacheTest
+ * @summary the replaycache type none cannot stop an authenticator replay,
+ * but it can stop a message replay when s.s.k.acceptor.subkey is true.
+ * You should not really use none in production environment. This test merely
+ * shows there can be other protections when replay cache is not working fine.
+ */
+
+import org.ietf.jgss.GSSException;
+import sun.security.jgss.GSSUtil;
+
+public class NoneReplayCacheTest {
+
+    public static void main(String[] args)
+            throws Exception {
+
+        new OneKDC(null);
+
+        System.setProperty("sun.security.krb5.rcache", "none");
+        System.setProperty("sun.security.krb5.acceptor.subkey", "true");
+
+        Context c, s;
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        s = Context.fromUserKtab(OneKDC.SERVER, OneKDC.KTAB, true);
+
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+
+        byte[] first = c.take(new byte[0]);
+
+        c.take(s.take(first));
+
+        byte[] msg = c.wrap("hello".getBytes(), true);
+        s.unwrap(msg, true);
+
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+        s.take(first);  // apreq replay not detectable
+        try {
+            s.unwrap(msg, true);    // msg replay detectable
+            throw new Exception("This method should fail");
+        } catch (GSSException gsse) {
+            gsse.printStackTrace();
+        }
+    }
+}
--- a/test/sun/security/krb5/auto/ReplayCache.java	Wed Nov 16 11:53:40 2011 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-/*
- * @test
- * @bug 7118809
- * @run main/othervm ReplayCache
- * @summary rcache deadlock
- */
-
-import org.ietf.jgss.GSSException;
-import sun.security.jgss.GSSUtil;
-import sun.security.krb5.KrbException;
-import sun.security.krb5.internal.Krb5;
-
-public class ReplayCache {
-
-    public static void main(String[] args)
-            throws Exception {
-
-        new OneKDC(null).writeJAASConf();
-
-        Context c, s;
-        c = Context.fromJAAS("client");
-        s = Context.fromJAAS("server");
-
-        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
-        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
-
-        byte[] first = c.take(new byte[0]);
-        s.take(first);
-
-        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
-        try {
-            s.take(first);  // Replay the last token sent
-            throw new Exception("This method should fail");
-        } catch (GSSException gsse) {
-            KrbException ke = (KrbException)gsse.getCause();
-            if (ke.returnCode() != Krb5.KRB_AP_ERR_REPEAT) {
-                throw gsse;
-            }
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/ReplayCacheExpunge.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,79 @@
+/*
+ * 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 8001326
+ * @run main/othervm ReplayCacheExpunge 16
+ * @run main/othervm/fail ReplayCacheExpunge 15
+ * @summary when number of expired entries minus number of good entries
+ * is more than 30, expunge occurs, and expired entries are forgotten.
+*/
+
+import java.util.Random;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.ReplayCache;
+import sun.security.krb5.internal.rcache.AuthTimeWithHash;
+
+public class ReplayCacheExpunge {
+    static final String client = "dummy@REALM";
+    static final String server = "server/localhost@REALM";
+    static final Random rand = new Random();
+
+    public static void main(String[] args) throws Exception {
+        int count = Integer.parseInt(args[0]);
+        ReplayCache cache = ReplayCache.getInstance("dfl:./");
+        AuthTimeWithHash a1 =
+                new AuthTimeWithHash(client, server, time(-400), 0, hash("1"));
+        AuthTimeWithHash a2 =
+                new AuthTimeWithHash(client, server, time(0), 0, 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(""));
+            cache.checkAndStore(then, a1);
+        }
+
+        // Now, we add a new one. If some conditions hold, the old ones
+        // will be expunged.
+        cache.checkAndStore(now, a2);
+
+        // and adding an old one will not trigger any error
+        cache.checkAndStore(now, a1);
+    }
+
+    private static String hash(String s) {
+        char[] data = new char[16];
+        for (int i=0; i<16; i++) {
+            if (i < s.length()) data[i] = s.charAt(i);
+            else data[i] = (char)('0' + rand.nextInt(10));
+        }
+        return new String(data);
+    }
+
+    private static int time(int x) {
+        return (int)(System.currentTimeMillis()/1000) + x;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/ReplayCachePrecise.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,84 @@
+/*
+ * 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 8001326
+ * @run main/othervm ReplayCachePrecise
+ * @summary when there are 2 two AuthTime with the same time but different hash,
+ * it's not a replay.
+*/
+
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Random;
+import sun.security.krb5.KrbException;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.ReplayCache;
+import sun.security.krb5.internal.rcache.AuthTimeWithHash;
+
+public class ReplayCachePrecise {
+    static final String client = "dummy@REALM";
+    static final String server = "server/localhost@REALM";
+    static final Random rand = new Random();
+
+    public static void main(String[] args) throws Exception {
+
+        AuthTimeWithHash a1 = new AuthTimeWithHash(client, server, time(0), 0,
+                "1111111111111111");
+        AuthTimeWithHash a2 = new AuthTimeWithHash(client, server, time(0), 0,
+                "2222222222222222");
+        KerberosTime now = new KerberosTime(time(0)*1000L);
+
+        // When all new styles, must exact match
+        ReplayCache cache = ReplayCache.getInstance("dfl:./c1");
+        cache.checkAndStore(now, a1);
+        cache.checkAndStore(now, a2);
+
+        // When only old style in cache, partial match
+        cache = ReplayCache.getInstance("dfl:./c2");
+        cache.checkAndStore(now, a1);
+        // A small surgery to remove the new style from the cache file
+        SeekableByteChannel ch = Files.newByteChannel(Paths.get("c2"),
+                StandardOpenOption.WRITE,
+                StandardOpenOption.READ);
+        ch.position(6);
+        ch.write(ByteBuffer.wrap(a1.encode(false)));
+        ch.truncate(ch.position());
+        ch.close();
+        try {
+            cache.checkAndStore(now, a2);
+            throw new Exception();
+        } catch (KrbException ke) {
+            // Correct
+            System.out.println(ke);
+        }
+    }
+
+    private static int time(int x) {
+        return (int)(System.currentTimeMillis()/1000) + x;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/ReplayCacheTest.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 7118809 8001326
+ * @run main/othervm ReplayCacheTest jvm
+ * @run main/othervm ReplayCacheTest dfl
+ * @summary rcache deadlock
+ */
+
+import java.io.File;
+import org.ietf.jgss.GSSException;
+import sun.security.jgss.GSSUtil;
+import sun.security.krb5.KrbException;
+import sun.security.krb5.internal.Krb5;
+
+public class ReplayCacheTest {
+
+    public static void main(String[] args)
+            throws Exception {
+
+        new OneKDC(null);
+
+        if (args[0].equals("dfl")) {
+            // Store file in scratch directory
+            args[0] = "dfl:" + System.getProperty("user.dir") + File.separator;
+            System.setProperty("sun.security.krb5.rcache", args[0]);
+        }
+
+        Context c, s;
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        s = Context.fromUserKtab(OneKDC.SERVER, OneKDC.KTAB, true);
+
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+
+        byte[] first = c.take(new byte[0]);
+        c.take(s.take(first));
+
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+        try {
+            s.take(first);  // Replay the last apreq sent
+            throw new Exception("This method should fail");
+        } catch (GSSException gsse) {
+            gsse.printStackTrace();
+            KrbException ke = (KrbException)gsse.getCause();
+            if (ke.returnCode() != Krb5.KRB_AP_ERR_REPEAT) {
+                throw gsse;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/ReplayCacheTestProc.java	Tue Jan 28 05:30:42 2020 +0000
@@ -0,0 +1,329 @@
+/*
+ * 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.misc.BASE64Decoder;
+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
+
+            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);
+
+            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("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 = new BASE64Decoder().decodeBuffer(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;
+        }
+    }
+}
--- a/test/sun/security/krb5/ccache/EmptyCC.java	Wed Nov 16 11:53:40 2011 +0800
+++ b/test/sun/security/krb5/ccache/EmptyCC.java	Tue Jan 28 05:30:42 2020 +0000
@@ -26,15 +26,12 @@
  * @bug 7158329
  * @bug 8001208
  * @summary NPE in sun.security.krb5.Credentials.acquireDefaultCreds()
+ * @library ../../../../java/security/testlibrary/
  * @compile -XDignore.symbol.file EmptyCC.java
  * @run main EmptyCC tmpcc
  * @run main EmptyCC FILE:tmpcc
  */
 import java.io.File;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 import sun.security.krb5.Credentials;
 import sun.security.krb5.PrincipalName;
 import sun.security.krb5.internal.ccache.CredentialsCache;
@@ -48,32 +45,9 @@
             // Main process, write the ccache and launch sub process
             CredentialsCache cache = CredentialsCache.create(pn, ccache);
             cache.save();
-
-            // java -cp $test.classes EmptyCC readcc
-            ProcessBuilder pb = new ProcessBuilder(
-                    new File(new File(System.getProperty("java.home"), "bin"),
-                        "java").getPath(),
-                    "-cp",
-                    System.getProperty("test.classes"),
-                    "EmptyCC",
-                    ccache,
-                    "readcc"
-                    );
-
-            pb.environment().put("KRB5CCNAME", ccache);
-            pb.redirectErrorStream(true);
-
-            Process p = pb.start();
-            try (InputStream ins = p.getInputStream()) {
-                byte[] buf = new byte[8192];
-                int n;
-                while ((n = ins.read(buf)) > 0) {
-                    System.out.write(buf, 0, n);
-                }
-            }
-            if (p.waitFor() != 0) {
-                throw new Exception("Test failed");
-            }
+            Proc p = Proc.create("EmptyCC").args(ccache, "readcc")
+                    .env("KRB5CCNAME", ccache).start();
+            p.waitFor();
         } else {
             // Sub process, read the ccache
             String cc = System.getenv("KRB5CCNAME");