You need to sign in or sign up before continuing.
提交 1c14b0a7 编写于 作者: W weijun

8001326: Improve Kerberos caching

Reviewed-by: valeriep
上级 11d0f16c
...@@ -27,9 +27,10 @@ package sun.security.jgss.krb5; ...@@ -27,9 +27,10 @@ package sun.security.jgss.krb5;
import org.ietf.jgss.*; import org.ietf.jgss.*;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ByteArrayInputStream; import java.security.AccessController;
import sun.security.action.GetBooleanAction;
import sun.security.krb5.*; import sun.security.krb5.*;
class AcceptSecContextToken extends InitialToken { class AcceptSecContextToken extends InitialToken {
...@@ -42,23 +43,19 @@ class AcceptSecContextToken extends InitialToken { ...@@ -42,23 +43,19 @@ class AcceptSecContextToken extends InitialToken {
*/ */
public AcceptSecContextToken(Krb5Context context, public AcceptSecContextToken(Krb5Context context,
KrbApReq apReq) KrbApReq apReq)
throws KrbException, IOException { throws KrbException, IOException, GSSException {
/* boolean useSubkey = AccessController.doPrivileged(
* RFC 1964, section 1.2 states: new GetBooleanAction("sun.security.krb5.acceptor.subkey"));
* (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 useSequenceNumber = true; 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()); context.resetMySequenceNumber(apRep.getSeqNumber().intValue());
......
...@@ -297,9 +297,11 @@ public class EncryptionKey ...@@ -297,9 +297,11 @@ public class EncryptionKey
/** /**
* Generates a sub-sessionkey from a given session key. * Generates a sub-sessionkey from a given session key.
*
* Used in AcceptSecContextToken and KrbApReq by acceptor- and initiator-
* side respectively.
*/ */
// Used in KrbApRep, KrbApReq public EncryptionKey(EncryptionKey key) throws KrbCryptoException {
EncryptionKey(EncryptionKey key) throws KrbCryptoException {
// generate random sub-session key // generate random sub-session key
keyValue = Confounder.bytes(key.keyValue.length); keyValue = Confounder.bytes(key.keyValue.length);
for (int i = 0; i < keyValue.length; i++) { for (int i = 0; i < keyValue.length; i++) {
......
...@@ -54,11 +54,9 @@ public class KrbApRep { ...@@ -54,11 +54,9 @@ public class KrbApRep {
// Used in AcceptSecContextToken // Used in AcceptSecContextToken
public KrbApRep(KrbApReq incomingReq, public KrbApRep(KrbApReq incomingReq,
boolean useSeqNumber, boolean useSeqNumber,
boolean useSubKey) throws KrbException, IOException { EncryptionKey subKey)
throws KrbException, IOException {
EncryptionKey subKey =
(useSubKey?
new EncryptionKey(incomingReq.getCreds().getSessionKey()):null);
SeqNumber seqNum = new LocalSeqNumber(); SeqNumber seqNum = new LocalSeqNumber();
init(incomingReq, subKey, seqNum); init(incomingReq, subKey, seqNum);
......
...@@ -33,12 +33,14 @@ package sun.security.krb5; ...@@ -33,12 +33,14 @@ package sun.security.krb5;
import sun.security.krb5.internal.*; import sun.security.krb5.internal.*;
import sun.security.krb5.internal.crypto.*; import sun.security.krb5.internal.crypto.*;
import sun.security.krb5.internal.rcache.*;
import sun.security.jgss.krb5.Krb5AcceptCredential; import sun.security.jgss.krb5.Krb5AcceptCredential;
import java.net.InetAddress; import java.net.InetAddress;
import sun.security.util.*; import sun.security.util.*;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; 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 * This class encapsulates a KRB-AP-REQ that a client sends to a
...@@ -53,11 +55,23 @@ public class KrbApReq { ...@@ -53,11 +55,23 @@ public class KrbApReq {
private Credentials creds; private Credentials creds;
private APReq apReqMessg; 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 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 * @param tgsCred the <code>Credentials</code> to be used to construct the
* AP Request protocol message. * AP Request protocol message.
* @param mutualRequired Whether mutual authentication is required * @param mutualRequired Whether mutual authentication is required
...@@ -81,7 +95,7 @@ public class KrbApReq { ...@@ -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 <code>Credentials</code> to be used to construct the * @param tgsCred the <code>Credentials</code> to be used to construct the
* AP Request protocol message. * AP Request protocol message.
* @param mutualRequired Whether mutual authentication is required * @param mutualRequired Whether mutual authentication is required
...@@ -125,7 +139,7 @@ public class KrbApReq { ...@@ -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. * peer.
* @param message The message received from the peer * @param message The message received from the peer
* @param keys <code>EncrtyptionKey</code>s to decrypt the message; * @param keys <code>EncrtyptionKey</code>s to decrypt the message;
...@@ -146,7 +160,7 @@ public class KrbApReq { ...@@ -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. * peer.
* @param value The <code>DerValue</code> that contains the * @param value The <code>DerValue</code> that contains the
* DER enoded AP-REQ protocol message * DER enoded AP-REQ protocol message
...@@ -297,15 +311,19 @@ public class KrbApReq { ...@@ -297,15 +311,19 @@ public class KrbApReq {
if (!authenticator.ctime.inClockSkew()) if (!authenticator.ctime.inClockSkew())
throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW); throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);
// start to check if it is a replay attack. byte[] hash = md.digest(apReqMessg.authenticator.cipher);
AuthTime time = char[] h = new char[hash.length * 2];
new AuthTime(authenticator.ctime.getTime(), authenticator.cusec); for (int i=0; i<hash.length; i++) {
String client = authenticator.cname.toString(); h[2*i] = hexConst[(hash[i]&0xff)>>4];
if (table.get(time, authenticator.cname.toString()) != null) { h[2*i+1] = hexConst[hash[i]&0xf];
throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT); }
} else { AuthTimeWithHash time = new AuthTimeWithHash(
table.put(client, time, System.currentTimeMillis()); authenticator.cname.toString(),
} apReqMessg.ticket.sname.toString(),
authenticator.ctime.getSeconds(),
authenticator.cusec,
new String(h));
rcache.checkAndStore(KerberosTime.now(), time);
if (initiator != null) { if (initiator != null) {
// sender host address // sender host address
......
/*
* 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;
}
...@@ -32,112 +32,113 @@ ...@@ -32,112 +32,113 @@
package sun.security.krb5.internal.rcache; package sun.security.krb5.internal.rcache;
import sun.security.krb5.internal.Krb5; import sun.security.krb5.internal.Krb5;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.ListIterator; import java.util.ListIterator;
import sun.security.krb5.internal.KerberosTime; import sun.security.krb5.internal.KerberosTime;
import sun.security.krb5.internal.KrbApErrException;
/** /**
* This class provides an efficient caching mechanism to store the timestamp of client authenticators. * This class provides an efficient caching mechanism to store AuthTimeWithHash
* The cache minimizes the memory usage by doing self-cleanup of expired items in the cache. * 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 * @author Yanni Zhang
*/ */
public class ReplayCache extends LinkedList<AuthTime> { public class AuthList {
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; private final LinkedList<AuthTimeWithHash> entries;
private final int lifespan;
/** /**
* Constructs a ReplayCache for a client principal in specified <code>CacheTable</code>. * Constructs a AuthList.
* @param p client principal name.
* @param ct CacheTable.
*/ */
public ReplayCache (String p, CacheTable ct) { public AuthList(int lifespan) {
principal = p; this.lifespan = lifespan;
table = ct; entries = new LinkedList<>();
} }
/** /**
* Puts the authenticator timestamp into the cache in descending order. * Puts the authenticator timestamp into the cache in descending order,
* @param t <code>AuthTime</code> * and throw an exception if it's already there.
*/ */
public synchronized void put(AuthTime t, long currentTime) { public void put(AuthTimeWithHash t, KerberosTime currentTime)
throws KrbApErrException {
if (this.size() == 0) { if (entries.isEmpty()) {
addFirst(t); entries.addFirst(t);
} } else {
else { AuthTimeWithHash temp = entries.getFirst();
AuthTime temp = getFirst(); int cmp = temp.compareTo(t);
if (temp.kerberosTime < t.kerberosTime) { if (cmp < 0) {
// in most cases, newly received authenticator has // This is the most common case, newly received authenticator
// larger timestamp. // has larger timestamp.
addFirst(t); entries.addFirst(t);
} } else if (cmp == 0) {
else if (temp.kerberosTime == t.kerberosTime) { throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
if (temp.cusec < t.cusec) { } else {
addFirst(t);
}
}
else {
//unless client clock being re-adjusted. //unless client clock being re-adjusted.
ListIterator<AuthTime> it = listIterator(1); ListIterator<AuthTimeWithHash> it = entries.listIterator(1);
boolean found = false;
while (it.hasNext()) { while (it.hasNext()) {
temp = it.next(); temp = it.next();
if (temp.kerberosTime < t.kerberosTime) { cmp = temp.compareTo(t);
add(indexOf(temp), t); if (cmp < 0) {
break; // Find an older one, put in front of it
//we always put the bigger timestamp at the front. entries.add(entries.indexOf(temp), t);
} found = true;
else if (temp.kerberosTime == t.kerberosTime) {
if (temp.cusec < t.cusec) {
add(indexOf(temp), t);
break; 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 // let us cleanup while we are here
long timeLimit = currentTime - KerberosTime.getDefaultSkew() * 1000L; long timeLimit = currentTime.getSeconds() - lifespan;
ListIterator<AuthTime> it = listIterator(0); ListIterator<AuthTimeWithHash> it = entries.listIterator(0);
AuthTime temp = null; AuthTimeWithHash temp = null;
int index = -1; int index = -1;
while (it.hasNext()) { while (it.hasNext()) {
//search expired timestamps. // search expired timestamps.
temp = it.next(); temp = it.next();
if (temp.kerberosTime < timeLimit) { if (temp.ctime < timeLimit) {
index = indexOf(temp); index = entries.indexOf(temp);
break; break;
} }
} }
// It would be nice if LinkedList has a method called truncate(index).
if (index > -1) { if (index > -1) {
do { do {
//remove expired timestamps from the list. // remove expired timestamps from the list.
removeLast(); entries.removeLast();
} while(size() > index); } while(entries.size() > index);
}
if (DEBUG) {
printList();
} }
} }
public boolean isEmpty() {
return entries.isEmpty();
}
/** public String toString() {
* Prints out the debug message. StringBuilder sb = new StringBuilder();
*/ Iterator<AuthTimeWithHash> iter = entries.descendingIterator();
private void printList() { int pos = entries.size();
Object[] total = toArray(); while (iter.hasNext()) {
for (int i = 0; i < total.length; i++) { AuthTimeWithHash at = iter.next();
System.out.println("object " + i + ": " + ((AuthTime)total[i]).kerberosTime + "/" sb.append('#').append(pos--).append(": ")
+ ((AuthTime)total[i]).cusec); .append(at.toString()).append('\n');
} }
return sb.toString();
} }
} }
/* /*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -30,54 +31,126 @@ ...@@ -30,54 +31,126 @@
package sun.security.krb5.internal.rcache; 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 * @author Yanni Zhang
*/ */
public class AuthTime { public class AuthTime {
long kerberosTime; final int ctime;
int cusec; final int cusec;
final String client;
final String server;
/** /**
* Constructs a new <code>AuthTime</code>. * Constructs an <code>AuthTime</code>.
* @param time time from the <code>Authenticator</code>.
* @param cusec microsecond field from the <code>Authenticator</code>.
*/ */
public AuthTime(long time, int c) { public AuthTime(String client, String server,
kerberosTime = time; int ctime, int cusec) {
cusec = c; 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 <code>AuthTime</code> object. * Reads an LC style string from a channel, which is a int32 length
* @param o an object. * plus a UTF-8 encoded string possibly ends with \0.
* @return true if two objects are equivalent, otherwise, return false. * @throws IOException if there is a format error
* @throws BufferUnderflowException if goes beyond the end
*/ */
public boolean equals(Object o) { private static String readStringWithLength(SeekableByteChannel chan)
if (o instanceof AuthTime) { throws IOException {
if ((((AuthTime)o).kerberosTime == kerberosTime) ByteBuffer bb = ByteBuffer.allocate(4);
&& (((AuthTime)o).cusec == cusec)) { bb.order(ByteOrder.nativeOrder());
return true; 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");
} }
return false; 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 <code>AuthTime</code> object. * Reads an AuthTime or AuthTimeWithHash object from a channel.
* * @throws IOException if there is a format error
* @return a <code>hash code</code> value for this object. * @throws BufferUnderflowException if goes beyond the end
*/ */
public int hashCode() { public static AuthTime readFrom(SeekableByteChannel chan)
int result = 17; throws IOException {
String client = readStringWithLength(chan);
result = 37 * result + (int)(kerberosTime ^ (kerberosTime >>> 32)); String server = readStringWithLength(chan);
result = 37 * result + cusec; 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);
}
} }
/*
* 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);
}
}
/*
* 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;
}
}
}
/* /*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -30,62 +31,55 @@ ...@@ -30,62 +31,55 @@
package sun.security.krb5.internal.rcache; 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 * @author Yanni Zhang
*/ */
public class CacheTable extends Hashtable<String,ReplayCache> { 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; private final Map<String,AuthList> content = new HashMap<>();
public CacheTable () {
}
/** @Override
* Puts the client timestamp in replay cache. public synchronized void checkAndStore(KerberosTime currTime, AuthTimeWithHash time)
* @params principal the client's principal name. throws KrbApErrException {
* @params time authenticator timestamp. String key = time.client + "|" + time.server;
*/ AuthList rc = content.get(key);
public synchronized void put(String principal, AuthTime time, long currTime) {
ReplayCache rc = super.get(principal);
if (rc == null) {
if (DEBUG) { if (DEBUG) {
System.out.println("replay cache for " + principal + " is null."); System.out.println("MemoryCache: add " + time + " to " + key);
} }
rc = new ReplayCache(principal, this); if (rc == null) {
rc = new AuthList(lifespan);
rc.put(time, currTime); rc.put(time, currTime);
if (!rc.isEmpty()) { 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); rc.put(time, currTime);
if (rc.isEmpty()) { if (rc.isEmpty()) {
super.remove(rc); content.remove(key);
}
if (DEBUG) {
System.out.println("replay cache found.");
} }
} }
} }
/** public String toString() {
* This method tests if replay cache keeps a record of the authenticator's time stamp. StringBuilder sb = new StringBuilder();
* If there is a record (replay attack detected), the server should reject the client request. for (AuthList rc: content.values()) {
* @params principal the client's principal name. sb.append(rc.toString());
* @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; return sb.toString();
} }
} }
/*
* 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<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 + 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();
}
}
...@@ -26,10 +26,10 @@ ...@@ -26,10 +26,10 @@
* @bug 7077646 * @bug 7077646
* @summary gssapi wrap for CFX per-message tokens always set FLAG_ACCEPTOR_SUBKEY * @summary gssapi wrap for CFX per-message tokens always set FLAG_ACCEPTOR_SUBKEY
* @compile -XDignore.symbol.file AcceptorSubKey.java * @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; import sun.security.jgss.GSSUtil;
// The basic krb5 test skeleton you can copy from // The basic krb5 test skeleton you can copy from
...@@ -37,8 +37,14 @@ public class AcceptorSubKey { ...@@ -37,8 +37,14 @@ public class AcceptorSubKey {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
int expected = Integer.parseInt(args[0]);
new OneKDC(null).writeJAASConf(); new OneKDC(null).writeJAASConf();
if (expected != 0) {
System.setProperty("sun.security.krb5.acceptor.subkey", "true");
}
Context c, s; Context c, s;
c = Context.fromJAAS("client"); c = Context.fromJAAS("client");
s = Context.fromJAAS("server"); s = Context.fromJAAS("server");
...@@ -53,8 +59,8 @@ public class AcceptorSubKey { ...@@ -53,8 +59,8 @@ public class AcceptorSubKey {
// FLAG_ACCEPTOR_SUBKEY is 4 // FLAG_ACCEPTOR_SUBKEY is 4
int flagOn = wrapped[2] & 4; int flagOn = wrapped[2] & 4;
if (flagOn != 0) { if (flagOn != expected) {
throw new Exception("Java GSS should not have set acceptor subkey"); throw new Exception("not expected");
} }
s.dispose(); s.dispose();
......
/*
* 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");
}
}
...@@ -195,6 +195,7 @@ public class Context { ...@@ -195,6 +195,7 @@ public class Context {
Krb5LoginModule krb5 = new Krb5LoginModule(); Krb5LoginModule krb5 = new Krb5LoginModule();
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
map.put("isInitiator", "false");
map.put("doNotPrompt", "true"); map.put("doNotPrompt", "true");
map.put("useTicketCache", "false"); map.put("useTicketCache", "false");
map.put("useKeyTab", "true"); map.put("useKeyTab", "true");
...@@ -616,9 +617,10 @@ public class Context { ...@@ -616,9 +617,10 @@ public class Context {
*/ */
static public void handshake(final Context c, final Context s) throws Exception { static public void handshake(final Context c, final Context s) throws Exception {
byte[] t = new byte[0]; byte[] t = new byte[0];
while (!c.x.isEstablished() || !s.x.isEstablished()) { while (true) {
t = c.take(t); if (t != null || !c.x.isEstablished()) t = c.take(t);
t = s.take(t); if (t != null || !s.x.isEstablished()) t = s.take(t);
if (c.x.isEstablished() && s.x.isEstablished()) break;
} }
} }
} }
...@@ -1137,7 +1137,7 @@ public class KDC { ...@@ -1137,7 +1137,7 @@ public class KDC {
* @return REALM.NAME = { kdc = host:port } * @return REALM.NAME = { kdc = host:port }
*/ */
private static String realmLineForKDC(KDC kdc) { 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.realm,
kdc.kdc, kdc.kdc,
kdc.port); kdc.port);
......
/*
* 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();
}
}
}
/*
* 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;
}
}
/*
* 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;
}
}
...@@ -23,38 +23,47 @@ ...@@ -23,38 +23,47 @@
/* /*
* @test * @test
* @bug 7118809 * @bug 7118809 8001326
* @run main/othervm ReplayCache * @run main/othervm ReplayCacheTest jvm
* @run main/othervm ReplayCacheTest dfl
* @summary rcache deadlock * @summary rcache deadlock
*/ */
import java.io.File;
import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSException;
import sun.security.jgss.GSSUtil; import sun.security.jgss.GSSUtil;
import sun.security.krb5.KrbException; import sun.security.krb5.KrbException;
import sun.security.krb5.internal.Krb5; import sun.security.krb5.internal.Krb5;
public class ReplayCache { public class ReplayCacheTest {
public static void main(String[] args) public static void main(String[] args)
throws Exception { throws Exception {
new OneKDC(null).writeJAASConf(); 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; Context c, s;
c = Context.fromJAAS("client"); c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
s = Context.fromJAAS("server"); s = Context.fromUserKtab(OneKDC.SERVER, OneKDC.KTAB, true);
c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
byte[] first = c.take(new byte[0]); byte[] first = c.take(new byte[0]);
s.take(first); c.take(s.take(first));
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
try { try {
s.take(first); // Replay the last token sent s.take(first); // Replay the last apreq sent
throw new Exception("This method should fail"); throw new Exception("This method should fail");
} catch (GSSException gsse) { } catch (GSSException gsse) {
gsse.printStackTrace();
KrbException ke = (KrbException)gsse.getCause(); KrbException ke = (KrbException)gsse.getCause();
if (ke.returnCode() != Krb5.KRB_AP_ERR_REPEAT) { if (ke.returnCode() != Krb5.KRB_AP_ERR_REPEAT) {
throw gsse; throw gsse;
......
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 7152176
* @summary More krb5 tests
* @library ../../../../java/security/testlibrary/
* @compile -XDignore.symbol.file ReplayCacheTestProc.java
* @run main/othervm/timeout=100 ReplayCacheTestProc
*/
import java.io.*;
import java.nio.BufferUnderflowException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.util.*;
import sun.security.jgss.GSSUtil;
import sun.security.krb5.internal.APReq;
import sun.security.krb5.internal.rcache.AuthTime;
// This test runs multiple acceptor Procs to mimin AP-REQ replays.
public class ReplayCacheTestProc {
private static Proc[] ps;
private static Proc pc;
private static List<Req> reqs = new ArrayList<>();
private static String HOST = "localhost";
// Where should the rcache be saved. It seems KRB5RCACHEDIR is not
// recognized on Solaris. Maybe version too low? I see 1.6.
private static String cwd =
System.getProperty("os.name").startsWith("SunOS") ?
"/var/krb5/rcache/" :
System.getProperty("user.dir");
private static int uid;
public static void main0(String[] args) throws Exception {
System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF);
if (args.length == 0) { // The controller
int ns = 5; // number of servers
int nu = 5; // number of users
int nx = 50; // number of experiments
int np = 5; // number of peers (services)
int mode = 0; // native(1), random(0), java(-1)
boolean random = true; // random experiments choreograph
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 = Base64.getDecoder().decode(req);
data = Arrays.copyOfRange(data, 17, data.length);
byte[] hash = MessageDigest.getInstance("MD5").digest(new APReq(data).authenticator.getBytes());
char[] h = new char[hash.length * 2];
char[] hexConst = "0123456789ABCDEF".toCharArray();
for (int i=0; i<hash.length; i++) {
h[2*i] = hexConst[(hash[i]&0xff)>>4];
h[2*i+1] = hexConst[hash[i]&0xf];
}
return new String(h);
}
// return size of dfl file, excluding the null hash ones
private static int csize(int p) throws Exception {
try (SeekableByteChannel chan = Files.newByteChannel(
Paths.get(dfl(p)), StandardOpenOption.READ)) {
chan.position(6);
int cc = 0;
while (true) {
try {
if (AuthTime.readFrom(chan) != null) cc++;
} catch (BufferUnderflowException e) {
break;
}
}
return cc;
} catch (IOException ioe) {
return 0;
}
}
// models an experiement
private static class Ex {
int i; // #
boolean expected; // expected result
boolean actual; // actual output
int old; // which ap-req to send
String server; // which server to send to
String hash; // the hash of req
int user; // which initiator
int peer; // which acceptor
int csize; // size of rcache after test
}
// models a saved ap-req msg
private static class Req {
String msg; // based64-ed req
int user; // which initiator
int peer; // which accceptor
Req(int user, int peer, String msg) {
this.msg = msg;
this.user= user;
this.peer = peer;
}
}
}
...@@ -26,15 +26,12 @@ ...@@ -26,15 +26,12 @@
* @bug 7158329 * @bug 7158329
* @bug 8001208 * @bug 8001208
* @summary NPE in sun.security.krb5.Credentials.acquireDefaultCreds() * @summary NPE in sun.security.krb5.Credentials.acquireDefaultCreds()
* @library ../../../../java/security/testlibrary/
* @compile -XDignore.symbol.file EmptyCC.java * @compile -XDignore.symbol.file EmptyCC.java
* @run main EmptyCC tmpcc * @run main EmptyCC tmpcc
* @run main EmptyCC FILE:tmpcc * @run main EmptyCC FILE:tmpcc
*/ */
import java.io.File; 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.Credentials;
import sun.security.krb5.PrincipalName; import sun.security.krb5.PrincipalName;
import sun.security.krb5.internal.ccache.CredentialsCache; import sun.security.krb5.internal.ccache.CredentialsCache;
...@@ -48,32 +45,9 @@ public class EmptyCC { ...@@ -48,32 +45,9 @@ public class EmptyCC {
// Main process, write the ccache and launch sub process // Main process, write the ccache and launch sub process
CredentialsCache cache = CredentialsCache.create(pn, ccache); CredentialsCache cache = CredentialsCache.create(pn, ccache);
cache.save(); cache.save();
Proc p = Proc.create("EmptyCC").args(ccache, "readcc")
// java -cp $test.classes EmptyCC readcc .env("KRB5CCNAME", ccache).start();
ProcessBuilder pb = new ProcessBuilder( p.waitFor();
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");
}
} else { } else {
// Sub process, read the ccache // Sub process, read the ccache
String cc = System.getenv("KRB5CCNAME"); String cc = System.getenv("KRB5CCNAME");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册