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