1 /*
2  * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.security.ssl;
27 
28 import java.security.AccessController;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Enumeration;
32 import java.util.Locale;
33 import javax.net.ssl.SSLSession;
34 import javax.net.ssl.SSLSessionContext;
35 
36 import sun.security.action.GetIntegerAction;
37 import sun.security.util.Cache;
38 
39 
40 final class SSLSessionContextImpl implements SSLSessionContext {
41     private final static int DEFAULT_MAX_CACHE_SIZE = 20480;
42 
43     private final Cache<SessionId, SSLSessionImpl> sessionCache;
44                                         // session cache, session id as key
45     private final Cache<String, SSLSessionImpl> sessionHostPortCache;
46                                         // session cache, "host:port" as key
47     private int cacheLimit;             // the max cache size
48     private int timeout;                // timeout in seconds
49 
50     // package private
SSLSessionContextImpl()51     SSLSessionContextImpl() {
52         cacheLimit = getDefaultCacheLimit();    // default cache size
53         timeout = 86400;                        // default, 24 hours
54 
55         // use soft reference
56         sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
57         sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
58     }
59 
60     /**
61      * Returns the <code>SSLSession</code> bound to the specified session id.
62      */
63     @Override
getSession(byte[] sessionId)64     public SSLSession getSession(byte[] sessionId) {
65         if (sessionId == null) {
66             throw new NullPointerException("session id cannot be null");
67         }
68 
69         SSLSessionImpl sess = sessionCache.get(new SessionId(sessionId));
70         if (!isTimedout(sess)) {
71             return sess;
72         }
73 
74         return null;
75     }
76 
77     /**
78      * Returns an enumeration of the active SSL sessions.
79      */
80     @Override
getIds()81     public Enumeration<byte[]> getIds() {
82         SessionCacheVisitor scVisitor = new SessionCacheVisitor();
83         sessionCache.accept(scVisitor);
84 
85         return scVisitor.getSessionIds();
86     }
87 
88     /**
89      * Sets the timeout limit for cached <code>SSLSession</code> objects
90      *
91      * Note that after reset the timeout, the cached session before
92      * should be timed within the shorter one of the old timeout and the
93      * new timeout.
94      */
95     @Override
setSessionTimeout(int seconds)96     public void setSessionTimeout(int seconds)
97                  throws IllegalArgumentException {
98         if (seconds < 0) {
99             throw new IllegalArgumentException();
100         }
101 
102         if (timeout != seconds) {
103             sessionCache.setTimeout(seconds);
104             sessionHostPortCache.setTimeout(seconds);
105             timeout = seconds;
106         }
107     }
108 
109     /**
110      * Gets the timeout limit for cached <code>SSLSession</code> objects
111      */
112     @Override
getSessionTimeout()113     public int getSessionTimeout() {
114         return timeout;
115     }
116 
117     /**
118      * Sets the size of the cache used for storing
119      * <code>SSLSession</code> objects.
120      */
121     @Override
setSessionCacheSize(int size)122     public void setSessionCacheSize(int size)
123                  throws IllegalArgumentException {
124         if (size < 0)
125             throw new IllegalArgumentException();
126 
127         if (cacheLimit != size) {
128             sessionCache.setCapacity(size);
129             sessionHostPortCache.setCapacity(size);
130             cacheLimit = size;
131         }
132     }
133 
134     /**
135      * Gets the size of the cache used for storing
136      * <code>SSLSession</code> objects.
137      */
138     @Override
getSessionCacheSize()139     public int getSessionCacheSize() {
140         return cacheLimit;
141     }
142 
143     // package-private method, used ONLY by ServerHandshaker
get(byte[] id)144     SSLSessionImpl get(byte[] id) {
145         return (SSLSessionImpl)getSession(id);
146     }
147 
148     // package-private method, find and remove session from cache
149     // return found session
pull(byte[] id)150     SSLSessionImpl pull(byte[] id) {
151         if (id != null) {
152             return sessionCache.pull(new SessionId(id));
153         }
154         return null;
155     }
156 
157     // package-private method, used ONLY by ClientHandshaker
get(String hostname, int port)158     SSLSessionImpl get(String hostname, int port) {
159         /*
160          * If no session caching info is available, we won't
161          * get one, so exit before doing a lookup.
162          */
163         if (hostname == null && port == -1) {
164             return null;
165         }
166 
167         SSLSessionImpl sess = sessionHostPortCache.get(getKey(hostname, port));
168         if (!isTimedout(sess)) {
169             return sess;
170         }
171 
172         return null;
173     }
174 
getKey(String hostname, int port)175     private static String getKey(String hostname, int port) {
176         return (hostname + ":" +
177             String.valueOf(port)).toLowerCase(Locale.ENGLISH);
178     }
179 
180     // cache a SSLSession
181     //
182     // In SunJSSE implementation, a session is created while getting a
183     // client hello or a server hello message, and cached while the
184     // handshaking finished.
185     // Here we time the session from the time it cached instead of the
186     // time it created, which is a little longer than the expected. So
187     // please do check isTimedout() while getting entry from the cache.
put(SSLSessionImpl s)188     void put(SSLSessionImpl s) {
189         sessionCache.put(s.getSessionId(), s);
190 
191         // If no hostname/port info is available, don't add this one.
192         if ((s.getPeerHost() != null) && (s.getPeerPort() != -1)) {
193             sessionHostPortCache.put(
194                 getKey(s.getPeerHost(), s.getPeerPort()), s);
195         }
196 
197         s.setContext(this);
198     }
199 
200     // package-private method, remove a cached SSLSession
remove(SessionId key)201     void remove(SessionId key) {
202         SSLSessionImpl s = sessionCache.get(key);
203         if (s != null) {
204             sessionCache.remove(key);
205             sessionHostPortCache.remove(
206                     getKey(s.getPeerHost(), s.getPeerPort()));
207         }
208     }
209 
getDefaultCacheLimit()210     private static int getDefaultCacheLimit() {
211         try {
212             int defaultCacheLimit = AccessController.doPrivileged(
213                     new GetIntegerAction("javax.net.ssl.sessionCacheSize",
214                             DEFAULT_MAX_CACHE_SIZE));
215             if (defaultCacheLimit >= 0) {
216                 return defaultCacheLimit;
217             } else if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
218                 SSLLogger.warning(
219                     "invalid System Property javax.net.ssl.sessionCacheSize, " +
220                     "use the default session cache size (" +
221                     DEFAULT_MAX_CACHE_SIZE + ") instead");
222             }
223         } catch (Exception e) {
224             // unlikely, log it for safe
225             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
226                 SSLLogger.warning(
227                     "the System Property javax.net.ssl.sessionCacheSize is " +
228                     "not available, use the default value (" +
229                     DEFAULT_MAX_CACHE_SIZE + ") instead");
230             }
231         }
232 
233         return DEFAULT_MAX_CACHE_SIZE;
234     }
235 
isTimedout(SSLSession sess)236     private boolean isTimedout(SSLSession sess) {
237         if (timeout == 0) {
238             return false;
239         }
240 
241         if ((sess != null) && ((sess.getCreationTime() + timeout * 1000L)
242                                         <= (System.currentTimeMillis()))) {
243             sess.invalidate();
244             return true;
245         }
246 
247         return false;
248     }
249 
250     private final class SessionCacheVisitor
251             implements Cache.CacheVisitor<SessionId, SSLSessionImpl> {
252         ArrayList<byte[]> ids = null;
253 
254         // public void visit(java.util.Map<K,V> map) {}
255         @Override
visit(java.util.Map<SessionId, SSLSessionImpl> map)256         public void visit(java.util.Map<SessionId, SSLSessionImpl> map) {
257             ids = new ArrayList<>(map.size());
258 
259             for (SessionId key : map.keySet()) {
260                 SSLSessionImpl value = map.get(key);
261                 if (!isTimedout(value)) {
262                     ids.add(key.getId());
263                 }
264             }
265         }
266 
getSessionIds()267         Enumeration<byte[]> getSessionIds() {
268             return  ids != null ? Collections.enumeration(ids) :
269                                   Collections.emptyEnumeration();
270         }
271     }
272 }
273