diff --git a/src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java b/src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java
index 80da88eadb5f8c410a2e25654a17f99280c05c3d..35882bc3fdc412ce1a2e0aded52c4a3393af7720 100644
--- a/src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java
+++ b/src/share/classes/sun/security/jgss/krb5/AcceptSecContextToken.java
@@ -27,9 +27,10 @@ package sun.security.jgss.krb5;
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 @@ class AcceptSecContextToken extends InitialToken {
*/
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());
diff --git a/src/share/classes/sun/security/krb5/EncryptionKey.java b/src/share/classes/sun/security/krb5/EncryptionKey.java
index 155ccf67138cf8d65675c61ec0561bff1892e9b4..08fd9d2675e0625e1f59b597f230718d750b8e0c 100644
--- a/src/share/classes/sun/security/krb5/EncryptionKey.java
+++ b/src/share/classes/sun/security/krb5/EncryptionKey.java
@@ -297,9 +297,11 @@ public class EncryptionKey
/**
* 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++) {
diff --git a/src/share/classes/sun/security/krb5/KrbApRep.java b/src/share/classes/sun/security/krb5/KrbApRep.java
index 2b994f7ea3a805b79370d75ef9dffac6b2aa458d..792e5ae7659ab57f499cfc6bd34d521fefb58307 100644
--- a/src/share/classes/sun/security/krb5/KrbApRep.java
+++ b/src/share/classes/sun/security/krb5/KrbApRep.java
@@ -53,12 +53,10 @@ public class KrbApRep {
*/
// 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);
diff --git a/src/share/classes/sun/security/krb5/KrbApReq.java b/src/share/classes/sun/security/krb5/KrbApReq.java
index 7ea5d85bf363145c4b5f31954501e24cbec12821..d05ee69c44e4187dcc3fb0ceb296bfd55960e5c1 100644
--- a/src/share/classes/sun/security/krb5/KrbApReq.java
+++ b/src/share/classes/sun/security/krb5/KrbApReq.java
@@ -33,12 +33,14 @@ package sun.security.krb5;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.crypto.*;
-import sun.security.krb5.internal.rcache.*;
import sun.security.jgss.krb5.Krb5AcceptCredential;
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
@@ -53,11 +55,23 @@ public class KrbApReq {
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 Credentials to be used to construct the
* AP Request protocol message.
* @param mutualRequired Whether mutual authentication is required
@@ -81,7 +95,7 @@ public class KrbApReq {
*/
/**
- * Contructs a AP-REQ message to send to the peer.
+ * Constructs an AP-REQ message to send to the peer.
* @param tgsCred the Credentials to be used to construct the
* AP Request protocol message.
* @param mutualRequired Whether mutual authentication is required
@@ -125,7 +139,7 @@ public class KrbApReq {
}
/**
- * 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 EncrtyptionKeys to decrypt the message;
@@ -146,7 +160,7 @@ public class KrbApReq {
}
/**
- * 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 DerValue that contains the
* DER enoded AP-REQ protocol message
@@ -297,15 +311,19 @@ public class KrbApReq {
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>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
diff --git a/src/share/classes/sun/security/krb5/internal/ReplayCache.java b/src/share/classes/sun/security/krb5/internal/ReplayCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6dc5702c477d77fee88d9ce3950b546ba454988
--- /dev/null
+++ b/src/share/classes/sun/security/krb5/internal/ReplayCache.java
@@ -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;
+}
diff --git a/src/share/classes/sun/security/krb5/internal/rcache/AuthList.java b/src/share/classes/sun/security/krb5/internal/rcache/AuthList.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ec8e3ec8d343416b1ff3d24289781670431ca68
--- /dev/null
+++ b/src/share/classes/sun/security/krb5/internal/rcache/AuthList.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2000, 2012, 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 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 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 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 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();
+ }
+}
diff --git a/src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java b/src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java
index e7ebb7e08ecf9cf6e8ee795581032da626ed12c9..7183d7d5c0537cecd7b200f211a411e03a6696ab 100644
--- a/src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java
+++ b/src/share/classes/sun/security/krb5/internal/rcache/AuthTime.java
@@ -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 a new AuthTime.
- * @param time time from the Authenticator.
- * @param cusec microsecond field from the Authenticator.
+ * Constructs an AuthTime.
*/
- public AuthTime(long time, int c) {
- kerberosTime = time;
- cusec = c;
+ 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
+
/**
- * Compares if an object equals to an AuthTime object.
- * @param o an object.
- * @return true if two objects are equivalent, otherwise, return false.
+ * 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 boolean equals(Object o) {
- if (o instanceof AuthTime) {
- if ((((AuthTime)o).kerberosTime == kerberosTime)
- && (((AuthTime)o).cusec == cusec)) {
- return true;
- }
+ 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");
}
- return false;
+ 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);
}
/**
- * Returns a hash code for this AuthTime object.
- *
- * @return a hash code value for this object.
+ * 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 int hashCode() {
- int result = 17;
-
- result = 37 * result + (int)(kerberosTime ^ (kerberosTime >>> 32));
- result = 37 * result + cusec;
+ 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 result;
+ /**
+ * Encodes to be used in a dfl file
+ */
+ 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);
+ }
}
diff --git a/src/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java b/src/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java
new file mode 100644
index 0000000000000000000000000000000000000000..52bc8a8260baa52c054868977d09f5c7052d6916
--- /dev/null
+++ b/src/share/classes/sun/security/krb5/internal/rcache/AuthTimeWithHash.java
@@ -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 {
+
+ final String hash;
+
+ /**
+ * Constructs a new AuthTimeWithHash.
+ */
+ 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 AuthTimeWithHash 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 AuthTimeWithHash 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);
+ }
+}
diff --git a/src/share/classes/sun/security/krb5/internal/rcache/DflCache.java b/src/share/classes/sun/security/krb5/internal/rcache/DflCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c5d124f0cbefad7cf0b03db445d965fb1b3813f
--- /dev/null
+++ b/src/share/classes/sun/security/krb5/internal/rcache/DflCache.java
@@ -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 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 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;
+ }
+ }
+}
diff --git a/src/share/classes/sun/security/krb5/internal/rcache/CacheTable.java b/src/share/classes/sun/security/krb5/internal/rcache/MemoryCache.java
similarity index 51%
rename from src/share/classes/sun/security/krb5/internal/rcache/CacheTable.java
rename to src/share/classes/sun/security/krb5/internal/rcache/MemoryCache.java
index ac1879adba7f6c3c12f75514da2b0431a46d4ee4..403d97f831921a66afa809f1bc3515782bbba3ce 100644
--- a/src/share/classes/sun/security/krb5/internal/rcache/CacheTable.java
+++ b/src/share/classes/sun/security/krb5/internal/rcache/MemoryCache.java
@@ -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,62 +31,55 @@
package sun.security.krb5.internal.rcache;
-import java.util.Hashtable;
+import java.util.*;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.KrbApErrException;
+import sun.security.krb5.internal.ReplayCache;
/**
- * This class implements Hashtable to store the replay caches.
+ * This class stores replay caches. AuthTimeWithHash objects are categorized
+ * into AuthLists keyed by the names of client and server.
*
* @author Yanni Zhang
*/
-public class CacheTable extends Hashtable {
+public class MemoryCache extends ReplayCache {
- private static final long serialVersionUID = -4695501354546664910L;
+ // 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 boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
- public CacheTable () {
- }
+ private final Map content = new HashMap<>();
- /**
- * 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);
+ @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) {
- if (DEBUG) {
- System.out.println("replay cache for " + principal + " is null.");
- }
- rc = new ReplayCache(principal, this);
+ rc = new AuthList(lifespan);
rc.put(time, currTime);
if (!rc.isEmpty()) {
- super.put(principal, rc);
+ content.put(key, rc);
+ }
+ } else {
+ if (DEBUG) {
+ System.out.println("MemoryCache: Existing AuthList:\n" + rc);
}
- }
- else {
rc.put(time, currTime);
if (rc.isEmpty()) {
- super.remove(rc);
- }
- if (DEBUG) {
- System.out.println("replay cache found.");
+ content.remove(key);
}
}
-
}
- /**
- * 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 AuthTime object.
- */
- public Object get(AuthTime time, String principal) {
- ReplayCache rc = super.get(principal);
- if ((rc != null) && (rc.contains(time))) {
- return time;
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (AuthList rc: content.values()) {
+ sb.append(rc.toString());
}
- return null;
+ return sb.toString();
}
}
diff --git a/src/share/classes/sun/security/krb5/internal/rcache/ReplayCache.java b/src/share/classes/sun/security/krb5/internal/rcache/ReplayCache.java
deleted file mode 100644
index 6993c186230a955a366757ddd3a9846964354cd0..0000000000000000000000000000000000000000
--- a/src/share/classes/sun/security/krb5/internal/rcache/ReplayCache.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (c) 2000, 2012, 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 {
-
- 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 CacheTable.
- * @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 AuthTime
- */
- 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 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 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);
- }
- }
-
-}
diff --git a/test/java/security/testlibrary/Proc.java b/test/java/security/testlibrary/Proc.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ab58bf52125eaf4f74076d348df7923e4e9b39d
--- /dev/null
+++ b/test/java/security/testlibrary/Proc.java
@@ -0,0 +1,325 @@
+/*
+ * 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.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * 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 perms = new ArrayList<>();
+ private List args = new ArrayList<>();
+ private Map env = new HashMap<>();
+ private Map 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 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 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 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 ? "" : 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 + Base64.getEncoder().encodeToString(data));
+ }
+ // Reads in a line of BASE64 binary
+ public static byte[] binIn() throws Exception {
+ return Base64.getDecoder().decode(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();
+ }
+}
diff --git a/test/sun/security/krb5/auto/AcceptorSubKey.java b/test/sun/security/krb5/auto/AcceptorSubKey.java
index 9aba5e485829210e677a93f2be9866fb042a9243..26b1fbda72487b11057e9e6ab038c9b3002d3a84 100644
--- a/test/sun/security/krb5/auto/AcceptorSubKey.java
+++ b/test/sun/security/krb5/auto/AcceptorSubKey.java
@@ -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 class AcceptorSubKey {
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 @@ public class AcceptorSubKey {
// 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();
diff --git a/test/sun/security/krb5/auto/BasicProc.java b/test/sun/security/krb5/auto/BasicProc.java
new file mode 100644
index 0000000000000000000000000000000000000000..54b204fb420313f3e2e317446651cf98c52b6cac
--- /dev/null
+++ b/test/sun/security/krb5/auto/BasicProc.java
@@ -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");
+ }
+}
diff --git a/test/sun/security/krb5/auto/Context.java b/test/sun/security/krb5/auto/Context.java
index 738f2c3d4e34ab75c0c22f0189ffe32a8618081a..cb3d6e98defdd1f272ac96a4532fc5b5f7d56db5 100644
--- a/test/sun/security/krb5/auto/Context.java
+++ b/test/sun/security/krb5/auto/Context.java
@@ -195,6 +195,7 @@ public class Context {
Krb5LoginModule krb5 = new Krb5LoginModule();
Map map = new HashMap<>();
+ map.put("isInitiator", "false");
map.put("doNotPrompt", "true");
map.put("useTicketCache", "false");
map.put("useKeyTab", "true");
@@ -616,9 +617,10 @@ public class Context {
*/
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;
}
}
}
diff --git a/test/sun/security/krb5/auto/KDC.java b/test/sun/security/krb5/auto/KDC.java
index 44330dfc723360f41ca3b9799ae5b3bfaeb37551..0157a930909aca305fc99a3905d5cb9b279a8f93 100644
--- a/test/sun/security/krb5/auto/KDC.java
+++ b/test/sun/security/krb5/auto/KDC.java
@@ -1137,7 +1137,7 @@ public class KDC {
* @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);
diff --git a/test/sun/security/krb5/auto/NoneReplayCacheTest.java b/test/sun/security/krb5/auto/NoneReplayCacheTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b92ad20395afcf367a0d2e56530da81ba77c8309
--- /dev/null
+++ b/test/sun/security/krb5/auto/NoneReplayCacheTest.java
@@ -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();
+ }
+ }
+}
diff --git a/test/sun/security/krb5/auto/ReplayCacheExpunge.java b/test/sun/security/krb5/auto/ReplayCacheExpunge.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c00d809517630cd05dccb6d0c46d6ad70516565
--- /dev/null
+++ b/test/sun/security/krb5/auto/ReplayCacheExpunge.java
@@ -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 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>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;
+ }
+ }
+}
diff --git a/test/sun/security/krb5/ccache/EmptyCC.java b/test/sun/security/krb5/ccache/EmptyCC.java
index a0cd759373dd4cb9157f821ee9e3840552ab140f..f70df4f010aa4eba6098cf22115a6f443d6478e3 100644
--- a/test/sun/security/krb5/ccache/EmptyCC.java
+++ b/test/sun/security/krb5/ccache/EmptyCC.java
@@ -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 @@ public class EmptyCC {
// 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");