diff --git a/src/share/classes/sun/security/ssl/AppInputStream.java b/src/share/classes/sun/security/ssl/AppInputStream.java index d638b8f46e9fcbc9fbce954383e7cc82eedb0a32..2ca889593c0131f73285fd2bf4827188ceac1ae5 100644 --- a/src/share/classes/sun/security/ssl/AppInputStream.java +++ b/src/share/classes/sun/security/ssl/AppInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1996-2009 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 @@ -81,6 +81,14 @@ class AppInputStream extends InputStream { */ public synchronized int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + if (c.checkEOF()) { return -1; } diff --git a/src/share/classes/sun/security/ssl/AppOutputStream.java b/src/share/classes/sun/security/ssl/AppOutputStream.java index d039cacb8b2c5567c40eef59e076acd7ca51ba53..d8c78a8eb5038d443337e15d2669e7674838786c 100644 --- a/src/share/classes/sun/security/ssl/AppOutputStream.java +++ b/src/share/classes/sun/security/ssl/AppOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1996-2009 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 @@ -58,18 +58,25 @@ class AppOutputStream extends OutputStream { */ synchronized public void write(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + // check if the Socket is invalid (error or closed) c.checkWrite(); - // + // Always flush at the end of each application level record. // This lets application synchronize read and write streams // however they like; if we buffered here, they couldn't. - // - // NOTE: *must* call c.writeRecord() even for len == 0 try { do { int howmuch = Math.min(len, r.availableDataBytes()); + // NOTE: *must* call c.writeRecord() even for howmuch == 0 if (howmuch > 0) { r.write(b, off, howmuch); off += howmuch; diff --git a/src/share/classes/sun/security/ssl/ByteBufferInputStream.java b/src/share/classes/sun/security/ssl/ByteBufferInputStream.java index 9c484d87ed3f0f5215de9a3a12e406fabac6eebc..d69e2bfdc54a882ce9036dca8f8f15cc1f6b7c17 100644 --- a/src/share/classes/sun/security/ssl/ByteBufferInputStream.java +++ b/src/share/classes/sun/security/ssl/ByteBufferInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2007 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2003-2009 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 @@ -89,8 +89,7 @@ class ByteBufferInputStream extends InputStream { if (b == null) { throw new NullPointerException(); - } else if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) > b.length) || ((off + len) < 0)) { + } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; diff --git a/src/share/classes/sun/security/ssl/SSLSessionContextImpl.java b/src/share/classes/sun/security/ssl/SSLSessionContextImpl.java index c98f1cc2c261848287634eb8d5c1d9e03cf4d380..3aeef2d8eee63b8956045ba17c8d07cc929c55c0 100644 --- a/src/share/classes/sun/security/ssl/SSLSessionContextImpl.java +++ b/src/share/classes/sun/security/ssl/SSLSessionContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1999-2009 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 @@ -41,88 +41,112 @@ import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; -import sun.misc.Cache; +import sun.security.util.Cache; -final class SSLSessionContextImpl implements SSLSessionContext -{ - private Cache sessionCache = new Cache(); - private Cache sessionHostPortCache = new Cache(); - private int cacheLimit; - private long timeoutMillis; +final class SSLSessionContextImpl implements SSLSessionContext { + private Cache sessionCache; // session cache, session id as key + private Cache sessionHostPortCache; // session cache, "host:port" as key + private int cacheLimit; // the max cache size + private int timeout; // timeout in seconds + private static final Debug debug = Debug.getInstance("ssl"); - // file private - SSLSessionContextImpl() - { - cacheLimit = getCacheLimit(); - timeoutMillis = 86400000; // default, 24 hours + // package private + SSLSessionContextImpl() { + cacheLimit = getDefaultCacheLimit(); // default cache size + timeout = 86400; // default, 24 hours + + // use soft reference + sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout); + sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout); } /** - * Returns the SSL session object associated with the - * specific session ID passed. + * Returns the SSLSession bound to the specified session id. */ - public SSLSession getSession(byte[] id) - { - SSLSession sess = (SSLSession) sessionCache.get( - new SessionId(id)); - return checkTimeValidity(sess); + public SSLSession getSession(byte[] sessionId) { + if (sessionId == null) { + throw new NullPointerException("session id cannot be null"); + } + + SSLSessionImpl sess = + (SSLSessionImpl)sessionCache.get(new SessionId(sessionId)); + if (!isTimedout(sess)) { + return sess; + } + + return null; } /** * Returns an enumeration of the active SSL sessions. */ public Enumeration getIds() { - Vector v = new Vector(sessionCache.size()); - SessionId sessId; + SessionCacheVisitor scVisitor = new SessionCacheVisitor(); + sessionCache.accept(scVisitor); - for (Enumeration e = sessionCache.keys(); e.hasMoreElements(); ) { - sessId = (SessionId) e.nextElement(); - if (!isTimedout((SSLSession)sessionCache.get(sessId))) - v.addElement(sessId.getId()); - } - return v.elements(); + return scVisitor.getSessionIds(); } + /** + * Sets the timeout limit for cached SSLSession objects + * + * Note that after reset the timeout, the cached session before + * should be timed within the shorter one of the old timeout and the + * new timeout. + */ public void setSessionTimeout(int seconds) throws IllegalArgumentException { - if (seconds < 0) + if (seconds < 0) { throw new IllegalArgumentException(); - timeoutMillis = seconds * 1000L; + } + + if (timeout != seconds) { + sessionCache.setTimeout(seconds); + sessionHostPortCache.setTimeout(seconds); + timeout = seconds; + } } + /** + * Gets the timeout limit for cached SSLSession objects + */ public int getSessionTimeout() { - return (int) (timeoutMillis / 1000); + return timeout; } + /** + * Sets the size of the cache used for storing + * SSLSession objects. + */ public void setSessionCacheSize(int size) throws IllegalArgumentException { if (size < 0) throw new IllegalArgumentException(); - cacheLimit = size; - /** - * If cache size limit is reduced, when the cache is full to its - * previous limit, trim the cache before its contents - * are used. - */ - if ((cacheLimit != 0) && (sessionCache.size() > cacheLimit)) - adjustCacheSizeTo(cacheLimit); + if (cacheLimit != size) { + sessionCache.setCapacity(size); + sessionHostPortCache.setCapacity(size); + cacheLimit = size; + } } + /** + * Gets the size of the cache used for storing + * SSLSession objects. + */ public int getSessionCacheSize() { return cacheLimit; } + + // package-private method, used ONLY by ServerHandshaker SSLSessionImpl get(byte[] id) { - return (SSLSessionImpl) getSession(id); + return (SSLSessionImpl)getSession(id); } - /** - * Returns the SSL session object associated with the - * specific host name and port number passed. - */ + // package-private method, used ONLY by ClientHandshaker SSLSessionImpl get(String hostname, int port) { /* * If no session caching info is available, we won't @@ -131,96 +155,51 @@ final class SSLSessionContextImpl implements SSLSessionContext if (hostname == null && port == -1) { return null; } - SSLSession sess = (SSLSessionImpl) sessionHostPortCache - .get(getKey(hostname, port)); - return (SSLSessionImpl) checkTimeValidity(sess); + + SSLSessionImpl sess = + (SSLSessionImpl)sessionHostPortCache.get(getKey(hostname, port)); + if (!isTimedout(sess)) { + return sess; + } + + return null; } private String getKey(String hostname, int port) { - return (hostname + ":" + String.valueOf(port)) - .toLowerCase(); + return (hostname + ":" + String.valueOf(port)).toLowerCase(); } + // cache a SSLSession + // + // In SunJSSE implementation, a session is created while getting a + // client hello or a server hello message, and cached while the + // handshaking finished. + // Here we time the session from the time it cached instead of the + // time it created, which is a little longer than the expected. So + // please do check isTimedout() while getting entry from the cache. void put(SSLSessionImpl s) { - // make space for the new session to be added - if ((cacheLimit != 0) && (sessionCache.size() >= cacheLimit)) - adjustCacheSizeTo(cacheLimit - 1); - - /* - * Can always add the session id. - */ sessionCache.put(s.getSessionId(), s); - /* - * If no hostname/port info is available, don't add this one. - */ + // If no hostname/port info is available, don't add this one. if ((s.getPeerHost() != null) && (s.getPeerPort() != -1)) { sessionHostPortCache.put( getKey(s.getPeerHost(), s.getPeerPort()), s); } + s.setContext(this); } - private void adjustCacheSizeTo(int targetSize) { - - int cacheSize = sessionCache.size(); - - if (targetSize < 0) - return; - - while (cacheSize > targetSize) { - SSLSessionImpl lru = null; - SSLSessionImpl s = null; - Enumeration e; - - if (debug != null && Debug.isOn("sessioncache")) { - System.out.println("exceeded cache limit of " + cacheLimit); - } - - /* - * Count the number of elements in the cache. The size() method - * does not reflect the cache entries that are no longer available, - * i.e entries that are garbage collected (the cache entries are - * held using soft references and are garbage collected when not - * in use). - */ - int count; - for (count = 0, e = sessionCache.elements(); - e.hasMoreElements(); count++) { - try { - s = (SSLSessionImpl)e.nextElement(); - } catch (NoSuchElementException nsee) { - break; - } - if (isTimedout(s)) { - lru = s; - break; - } else if ((lru == null) || (s.getLastAccessedTime() - < lru.getLastAccessedTime())) { - lru = s; - } - } - if ((lru != null) && (count > targetSize)) { - if (debug != null && Debug.isOn("sessioncache")) { - System.out.println("uncaching " + lru); - } - lru.invalidate(); - count--; // element removed from the cache - } - cacheSize = count; + // package-private method, remove a cached SSLSession + void remove(SessionId key) { + SSLSessionImpl s = (SSLSessionImpl)sessionCache.get(key); + if (s != null) { + sessionCache.remove(key); + sessionHostPortCache.remove( + getKey(s.getPeerHost(), s.getPeerPort())); } } - // file private - void remove(SessionId key) - { - SSLSessionImpl s = (SSLSessionImpl) sessionCache.get(key); - sessionCache.remove(key); - sessionHostPortCache.remove(getKey(s.getPeerHost(), - s.getPeerPort())); - } - - private int getCacheLimit() { + private int getDefaultCacheLimit() { int cacheLimit = 0; try { String s = java.security.AccessController.doPrivileged( @@ -237,21 +216,40 @@ final class SSLSessionContextImpl implements SSLSessionContext return (cacheLimit > 0) ? cacheLimit : 0; } - SSLSession checkTimeValidity(SSLSession sess) { - if (isTimedout(sess)) { - sess.invalidate(); - return null; - } else - return sess; - } - boolean isTimedout(SSLSession sess) { - if (timeoutMillis == 0) + if (timeout == 0) { return false; - if ((sess != null) && - ((sess.getCreationTime() + timeoutMillis) - <= (System.currentTimeMillis()))) + } + + if ((sess != null) && ((sess.getCreationTime() + timeout * 1000L) + <= (System.currentTimeMillis()))) { + sess.invalidate(); return true; + } + return false; } + + final class SessionCacheVisitor + implements sun.security.util.Cache.CacheVisitor { + Vector ids = null; + + // public void visit(java.util.Map map) {} + public void visit(java.util.Map map) { + ids = new Vector(map.size()); + + for (Object key : map.keySet()) { + SSLSessionImpl value = (SSLSessionImpl)map.get(key); + if (!isTimedout(value)) { + ids.addElement(((SessionId)key).getId()); + } + } + } + + public Enumeration getSessionIds() { + return ids != null ? ids.elements() : + new Vector().elements(); + } + } + } diff --git a/src/share/classes/sun/security/util/Cache.java b/src/share/classes/sun/security/util/Cache.java index e6eddb6f07d1e2a972c60d68e53d59fb0a9d4166..35d648a5a7fa1dfd9e9c9b6e203da913fdf371e7 100644 --- a/src/share/classes/sun/security/util/Cache.java +++ b/src/share/classes/sun/security/util/Cache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2002-2009 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 @@ -100,6 +100,21 @@ public abstract class Cache { */ public abstract void remove(Object key); + /** + * Set the maximum size. + */ + public abstract void setCapacity(int size); + + /** + * Set the timeout(in seconds). + */ + public abstract void setTimeout(int timeout); + + /** + * accept a visitor + */ + public abstract void accept(CacheVisitor visitor); + /** * Return a new memory cache with the specified maximum size, unlimited * lifetime for entries, with the values held by SoftReferences. @@ -178,6 +193,10 @@ public abstract class Cache { } } + public interface CacheVisitor { + public void visit(Map map); + } + } class NullCache extends Cache { @@ -208,6 +227,18 @@ class NullCache extends Cache { // empty } + public void setCapacity(int size) { + // empty + } + + public void setTimeout(int timeout) { + // empty + } + + public void accept(CacheVisitor visitor) { + // empty + } + } class MemoryCache extends Cache { @@ -218,8 +249,8 @@ class MemoryCache extends Cache { private final static boolean DEBUG = false; private final Map cacheMap; - private final int maxSize; - private final int lifetime; + private int maxSize; + private long lifetime; private final ReferenceQueue queue; public MemoryCache(boolean soft, int maxSize) { @@ -328,7 +359,7 @@ class MemoryCache extends Cache { oldEntry.invalidate(); return; } - if (cacheMap.size() > maxSize) { + if (maxSize > 0 && cacheMap.size() > maxSize) { expungeExpiredEntries(); if (cacheMap.size() > maxSize) { // still too large? Iterator t = cacheMap.values().iterator(); @@ -368,6 +399,55 @@ class MemoryCache extends Cache { } } + public synchronized void setCapacity(int size) { + expungeExpiredEntries(); + if (size > 0 && cacheMap.size() > size) { + Iterator t = cacheMap.values().iterator(); + for (int i = cacheMap.size() - size; i > 0; i--) { + CacheEntry lruEntry = t.next(); + if (DEBUG) { + System.out.println("** capacity reset removal " + + lruEntry.getKey() + " | " + lruEntry.getValue()); + } + t.remove(); + lruEntry.invalidate(); + } + } + + maxSize = size > 0 ? size : 0; + + if (DEBUG) { + System.out.println("** capacity reset to " + size); + } + } + + public synchronized void setTimeout(int timeout) { + emptyQueue(); + lifetime = timeout > 0 ? timeout * 1000L : 0L; + + if (DEBUG) { + System.out.println("** lifetime reset to " + timeout); + } + } + + // it is a heavyweight method. + public synchronized void accept(CacheVisitor visitor) { + expungeExpiredEntries(); + Map cached = getCachedEntries(); + + visitor.visit(cached); + } + + private Map getCachedEntries() { + Map kvmap = new HashMap(cacheMap.size()); + + for (CacheEntry entry : cacheMap.values()) { + kvmap.put(entry.getKey(), entry.getValue()); + } + + return kvmap; + } + protected CacheEntry newEntry(Object key, Object value, long expirationTime, ReferenceQueue queue) { if (queue != null) { diff --git a/test/sun/security/ssl/com/sun/net/ssl/internal/ssl/AppInputStream/ReadZeroBytes.java b/test/sun/security/ssl/com/sun/net/ssl/internal/ssl/AppInputStream/ReadZeroBytes.java new file mode 100644 index 0000000000000000000000000000000000000000..56134e0e773a8d1e36b61caebba79833e213cef7 --- /dev/null +++ b/test/sun/security/ssl/com/sun/net/ssl/internal/ssl/AppInputStream/ReadZeroBytes.java @@ -0,0 +1,254 @@ +/* + * Copyright 2009 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 6697270 + * @summary Inputstream dosent behave correct + */ + +import java.io.*; +import java.net.*; +import javax.net.ssl.*; + +public class ReadZeroBytes { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * Define the server side of the test. + */ + void doServerSide() throws Exception { + SSLServerSocketFactory sslssf = + (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket(serverPort); + + serverPort = sslServerSocket.getLocalPort(); + + /* + * Signal Client, we're ready for his connect. + */ + serverReady = true; + + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + // no read, no write. + SSLSession sess = sslSocket.getSession(); + if (!sess.isValid()) { + throw new Exception("Error occurs during the initial handshake"); + } + + sslIS.close(); + sslOS.close(); + sslSocket.close(); + } + + /* + * Define the client side of the test. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + // read zero byte, write zero byte. + sslIS.read(new byte[0], 0, 0); + sslOS.write(new byte[0], 0, 0); + + // if byte array length matters. + sslIS.read(new byte[1], 0, 0); + sslOS.write(new byte[1], 0, 0); + + // note that the above read/write should not kickoff handshaking. + SSLSession sess = sslSocket.getSession(); + if (!sess.isValid()) { + throw new Exception("Error occurs during the initial handshake"); + } + + sslIS.close(); + sslOS.close(); + sslSocket.close(); + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", "./") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", "./") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new ReadZeroBytes(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + ReadZeroBytes () throws Exception { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) + throw serverException; + if (clientException != null) + throw clientException; + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not already active... + */ + System.out.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.out.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } +} +