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.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.Locale;
32 import javax.net.ssl.SSLSession;
33 import javax.net.ssl.SSLSessionContext;
34 
35 import sun.security.action.GetIntegerAction;
36 import sun.security.util.Cache;
37 
38 
39 final class SSLSessionContextImpl implements SSLSessionContext {
40     private final static int DEFAULT_MAX_CACHE_SIZE = 20480;
41 
42     private final Cache<SessionId, SSLSessionImpl> sessionCache;
43                                         // session cache, session id as key
44     private final Cache<String, SSLSessionImpl> sessionHostPortCache;
45                                         // session cache, "host:port" as key
46     private int cacheLimit;             // the max cache size
47     private int timeout;                // timeout in seconds
48 
49     // package private
SSLSessionContextImpl()50     SSLSessionContextImpl() {
51         cacheLimit = getDefaultCacheLimit();    // default cache size
52         timeout = 86400;                        // default, 24 hours
53 
54         // use soft reference
55         sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
56         sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
57     }
58 
59     /**
60      * Returns the <code>SSLSession</code> bound to the specified session id.
61      */
62     @Override
getSession(byte[] sessionId)63     public SSLSession getSession(byte[] sessionId) {
64         if (sessionId == null) {
65             throw new NullPointerException("session id cannot be null");
66         }
67 
68         SSLSessionImpl sess = sessionCache.get(new SessionId(sessionId));
69         if (!isTimedout(sess)) {
70             return sess;
71         }
72 
73         return null;
74     }
75 
76     /**
77      * Returns an enumeration of the active SSL sessions.
78      */
79     @Override
getIds()80     public Enumeration<byte[]> getIds() {
81         SessionCacheVisitor scVisitor = new SessionCacheVisitor();
82         sessionCache.accept(scVisitor);
83 
84         return scVisitor.getSessionIds();
85     }
86 
87     /**
88      * Sets the timeout limit for cached <code>SSLSession</code> objects
89      *
90      * Note that after reset the timeout, the cached session before
91      * should be timed within the shorter one of the old timeout and the
92      * new timeout.
93      */
94     @Override
setSessionTimeout(int seconds)95     public void setSessionTimeout(int seconds)
96                  throws IllegalArgumentException {
97         if (seconds < 0) {
98             throw new IllegalArgumentException();
99         }
100 
101         if (timeout != seconds) {
102             sessionCache.setTimeout(seconds);
103             sessionHostPortCache.setTimeout(seconds);
104             timeout = seconds;
105         }
106     }
107 
108     /**
109      * Gets the timeout limit for cached <code>SSLSession</code> objects
110      */
111     @Override
getSessionTimeout()112     public int getSessionTimeout() {
113         return timeout;
114     }
115 
116     /**
117      * Sets the size of the cache used for storing
118      * <code>SSLSession</code> objects.
119      */
120     @Override
setSessionCacheSize(int size)121     public void setSessionCacheSize(int size)
122                  throws IllegalArgumentException {
123         if (size < 0)
124             throw new IllegalArgumentException();
125 
126         if (cacheLimit != size) {
127             sessionCache.setCapacity(size);
128             sessionHostPortCache.setCapacity(size);
129             cacheLimit = size;
130         }
131     }
132 
133     /**
134      * Gets the size of the cache used for storing
135      * <code>SSLSession</code> objects.
136      */
137     @Override
getSessionCacheSize()138     public int getSessionCacheSize() {
139         return cacheLimit;
140     }
141 
142     // package-private method, used ONLY by ServerHandshaker
get(byte[] id)143     SSLSessionImpl get(byte[] id) {
144         return (SSLSessionImpl)getSession(id);
145     }
146 
147     // package-private method, find and remove session from cache
148     // return found session
pull(byte[] id)149     SSLSessionImpl pull(byte[] id) {
150         if (id != null) {
151             return sessionCache.pull(new SessionId(id));
152         }
153         return null;
154     }
155 
156     // package-private method, used ONLY by ClientHandshaker
get(String hostname, int port)157     SSLSessionImpl get(String hostname, int port) {
158         /*
159          * If no session caching info is available, we won't
160          * get one, so exit before doing a lookup.
161          */
162         if (hostname == null && port == -1) {
163             return null;
164         }
165 
166         SSLSessionImpl sess = sessionHostPortCache.get(getKey(hostname, port));
167         if (!isTimedout(sess)) {
168             return sess;
169         }
170 
171         return null;
172     }
173 
getKey(String hostname, int port)174     private static String getKey(String hostname, int port) {
175         return (hostname + ":" +
176             String.valueOf(port)).toLowerCase(Locale.ENGLISH);
177     }
178 
179     // cache a SSLSession
180     //
181     // In SunJSSE implementation, a session is created while getting a
182     // client hello or a server hello message, and cached while the
183     // handshaking finished.
184     // Here we time the session from the time it cached instead of the
185     // time it created, which is a little longer than the expected. So
186     // please do check isTimedout() while getting entry from the cache.
put(SSLSessionImpl s)187     void put(SSLSessionImpl s) {
188         sessionCache.put(s.getSessionId(), s);
189 
190         // If no hostname/port info is available, don't add this one.
191         if ((s.getPeerHost() != null) && (s.getPeerPort() != -1)) {
192             sessionHostPortCache.put(
193                 getKey(s.getPeerHost(), s.getPeerPort()), s);
194         }
195 
196         s.setContext(this);
197     }
198 
199     // package-private method, remove a cached SSLSession
remove(SessionId key)200     void remove(SessionId key) {
201         SSLSessionImpl s = sessionCache.get(key);
202         if (s != null) {
203             sessionCache.remove(key);
204             sessionHostPortCache.remove(
205                     getKey(s.getPeerHost(), s.getPeerPort()));
206         }
207     }
208 
getDefaultCacheLimit()209     private static int getDefaultCacheLimit() {
210         try {
211             int defaultCacheLimit = GetIntegerAction.privilegedGetProperty(
212                     "javax.net.ssl.sessionCacheSize", DEFAULT_MAX_CACHE_SIZE);
213 
214             if (defaultCacheLimit >= 0) {
215                 return defaultCacheLimit;
216             } else if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
217                 SSLLogger.warning(
218                     "invalid System Property javax.net.ssl.sessionCacheSize, " +
219                     "use the default session cache size (" +
220                     DEFAULT_MAX_CACHE_SIZE + ") instead");
221             }
222         } catch (Exception e) {
223             // unlikely, log it for safe
224             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
225                 SSLLogger.warning(
226                     "the System Property javax.net.ssl.sessionCacheSize is " +
227                     "not available, use the default value (" +
228                     DEFAULT_MAX_CACHE_SIZE + ") instead");
229             }
230         }
231 
232         return DEFAULT_MAX_CACHE_SIZE;
233     }
234 
isTimedout(SSLSession sess)235     private boolean isTimedout(SSLSession sess) {
236         if (timeout == 0) {
237             return false;
238         }
239 
240         if ((sess != null) && ((sess.getCreationTime() + timeout * 1000L)
241                                         <= (System.currentTimeMillis()))) {
242             sess.invalidate();
243             return true;
244         }
245 
246         return false;
247     }
248 
249     private final class SessionCacheVisitor
250             implements Cache.CacheVisitor<SessionId, SSLSessionImpl> {
251         ArrayList<byte[]> ids = null;
252 
253         // public void visit(java.util.Map<K,V> map) {}
254         @Override
visit(java.util.Map<SessionId, SSLSessionImpl> map)255         public void visit(java.util.Map<SessionId, SSLSessionImpl> map) {
256             ids = new ArrayList<>(map.size());
257 
258             for (SessionId key : map.keySet()) {
259                 SSLSessionImpl value = map.get(key);
260                 if (!isTimedout(value)) {
261                     ids.add(key.getId());
262                 }
263             }
264         }
265 
getSessionIds()266         Enumeration<byte[]> getSessionIds() {
267             return  ids != null ? Collections.enumeration(ids) :
268                                   Collections.emptyEnumeration();
269         }
270     }
271 }
272