1 /*
2  * Copyright (c) 1999, 2019, 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.action.GetPropertyAction;
37 import sun.security.util.Cache;
38 
39 
40 /**
41  * @systemProperty jdk.tls.server.enableSessionTicketExtension} determines if the
42  * server will provide stateless session tickets, if the client supports it,
43  * as described in RFC 5077 and RFC 8446.  a stateless session ticket
44  * contains the encrypted server's state which saves server resources.
45  *
46  * {@systemProperty jdk.tls.client.enableSessionTicketExtension} determines if the
47  * client will send an extension in the ClientHello in the pre-TLS 1.3.
48  * This extension allows the client to accept the server's session state for
49  * Server Side stateless resumption (RFC 5077).  Setting the property to
50  * "true" turns this on, by default it is false.  For TLS 1.3, the system
51  * property is not needed as this support is part of the spec.
52  *
53  * {@systemProperty jdk.tls.server.sessionTicketTimeout} determines how long
54  * a session in the server cache or the stateless resumption tickets are
55  * available for use.  The value set by the property can be modified by
56  * {@code SSLSessionContext.setSessionTimeout()} during runtime.
57  *
58  */
59 
60 final class SSLSessionContextImpl implements SSLSessionContext {
61     private final static int DEFAULT_MAX_CACHE_SIZE = 20480;
62     // Default lifetime of a session. 24 hours
63     final static int DEFAULT_SESSION_TIMEOUT = 86400;
64 
65     private final Cache<SessionId, SSLSessionImpl> sessionCache;
66                                         // session cache, session id as key
67     private final Cache<String, SSLSessionImpl> sessionHostPortCache;
68                                         // session cache, "host:port" as key
69     private int cacheLimit;             // the max cache size
70     private int timeout;                // timeout in seconds
71 
72     // Default setting for stateless session resumption support (RFC 5077)
73     private boolean statelessSession = true;
74 
75     // package private
SSLSessionContextImpl(boolean server)76     SSLSessionContextImpl(boolean server) {
77         timeout = DEFAULT_SESSION_TIMEOUT;
78         cacheLimit = getDefaults(server);    // default cache size
79 
80         // use soft reference
81         sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
82         sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
83     }
84 
85     // Stateless sessions when available, but there is a cache
statelessEnabled()86     boolean statelessEnabled() {
87         return statelessSession;
88     }
89 
90     /**
91      * Returns the <code>SSLSession</code> bound to the specified session id.
92      */
93     @Override
getSession(byte[] sessionId)94     public SSLSession getSession(byte[] sessionId) {
95         if (sessionId == null) {
96             throw new NullPointerException("session id cannot be null");
97         }
98 
99         SSLSessionImpl sess = sessionCache.get(new SessionId(sessionId));
100         if (!isTimedout(sess)) {
101             return sess;
102         }
103 
104         return null;
105     }
106 
107     /**
108      * Returns an enumeration of the active SSL sessions.
109      */
110     @Override
getIds()111     public Enumeration<byte[]> getIds() {
112         SessionCacheVisitor scVisitor = new SessionCacheVisitor();
113         sessionCache.accept(scVisitor);
114 
115         return scVisitor.getSessionIds();
116     }
117 
118     /**
119      * Sets the timeout limit for cached <code>SSLSession</code> objects
120      *
121      * Note that after reset the timeout, the cached session before
122      * should be timed within the shorter one of the old timeout and the
123      * new timeout.
124      */
125     @Override
setSessionTimeout(int seconds)126     public void setSessionTimeout(int seconds)
127                  throws IllegalArgumentException {
128         if (seconds < 0) {
129             throw new IllegalArgumentException();
130         }
131 
132         if (timeout != seconds) {
133             sessionCache.setTimeout(seconds);
134             sessionHostPortCache.setTimeout(seconds);
135             timeout = seconds;
136         }
137     }
138 
139     /**
140      * Gets the timeout limit for cached <code>SSLSession</code> objects
141      */
142     @Override
getSessionTimeout()143     public int getSessionTimeout() {
144         return timeout;
145     }
146 
147     /**
148      * Sets the size of the cache used for storing
149      * <code>SSLSession</code> objects.
150      */
151     @Override
setSessionCacheSize(int size)152     public void setSessionCacheSize(int size)
153                  throws IllegalArgumentException {
154         if (size < 0)
155             throw new IllegalArgumentException();
156 
157         if (cacheLimit != size) {
158             sessionCache.setCapacity(size);
159             sessionHostPortCache.setCapacity(size);
160             cacheLimit = size;
161         }
162     }
163 
164     /**
165      * Gets the size of the cache used for storing
166      * <code>SSLSession</code> objects.
167      */
168     @Override
getSessionCacheSize()169     public int getSessionCacheSize() {
170         return cacheLimit;
171     }
172 
173     // package-private method, used ONLY by ServerHandshaker
get(byte[] id)174     SSLSessionImpl get(byte[] id) {
175         return (SSLSessionImpl)getSession(id);
176     }
177 
178     // package-private method, find and remove session from cache
179     // return found session
pull(byte[] id)180     SSLSessionImpl pull(byte[] id) {
181         if (id != null) {
182             return sessionCache.pull(new SessionId(id));
183         }
184         return null;
185     }
186 
187     // package-private method, used ONLY by ClientHandshaker
get(String hostname, int port)188     SSLSessionImpl get(String hostname, int port) {
189         /*
190          * If no session caching info is available, we won't
191          * get one, so exit before doing a lookup.
192          */
193         if (hostname == null && port == -1) {
194             return null;
195         }
196 
197         SSLSessionImpl sess = sessionHostPortCache.get(getKey(hostname, port));
198         if (!isTimedout(sess)) {
199             return sess;
200         }
201 
202         return null;
203     }
204 
getKey(String hostname, int port)205     private static String getKey(String hostname, int port) {
206         return (hostname + ":" + port).toLowerCase(Locale.ENGLISH);
207     }
208 
209     // cache a SSLSession
210     //
211     // In SunJSSE implementation, a session is created while getting a
212     // client hello or a server hello message, and cached while the
213     // handshaking finished.
214     // Here we time the session from the time it cached instead of the
215     // time it created, which is a little longer than the expected. So
216     // please do check isTimedout() while getting entry from the cache.
put(SSLSessionImpl s)217     void put(SSLSessionImpl s) {
218         sessionCache.put(s.getSessionId(), s);
219 
220         // If no hostname/port info is available, don't add this one.
221         if ((s.getPeerHost() != null) && (s.getPeerPort() != -1)) {
222             sessionHostPortCache.put(
223                 getKey(s.getPeerHost(), s.getPeerPort()), s);
224         }
225 
226         s.setContext(this);
227     }
228 
229     // package-private method, remove a cached SSLSession
remove(SessionId key)230     void remove(SessionId key) {
231         SSLSessionImpl s = sessionCache.get(key);
232         if (s != null) {
233             sessionCache.remove(key);
234             sessionHostPortCache.remove(
235                     getKey(s.getPeerHost(), s.getPeerPort()));
236         }
237     }
238 
getDefaults(boolean server)239     private int getDefaults(boolean server) {
240         try {
241             String st;
242 
243             // Property for Session Cache state
244             if (server) {
245                 st = GetPropertyAction.privilegedGetProperty(
246                         "jdk.tls.server.enableSessionTicketExtension", "true");
247             } else {
248                 st = GetPropertyAction.privilegedGetProperty(
249                         "jdk.tls.client.enableSessionTicketExtension", "true");
250             }
251 
252             if (st.compareToIgnoreCase("false") == 0) {
253                 statelessSession = false;
254             }
255 
256             // Property for Session Ticket Timeout.  The value can be changed
257             // by SSLSessionContext.setSessionTimeout(int)
258             String s = GetPropertyAction.privilegedGetProperty(
259                     "jdk.tls.server.sessionTicketTimeout");
260             if (s != null) {
261                 try {
262                     int t = Integer.parseInt(s);
263                     if (t < 0 ||
264                             t > NewSessionTicket.MAX_TICKET_LIFETIME) {
265                         timeout = DEFAULT_SESSION_TIMEOUT;
266                         if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
267                             SSLLogger.warning("Invalid timeout given " +
268                                     "jdk.tls.server.sessionTicketTimeout: " + t +
269                                     ".  Set to default value " + timeout);
270                         }
271                     } else {
272                         timeout = t;
273                     }
274                 } catch (NumberFormatException e) {
275                     setSessionTimeout(DEFAULT_SESSION_TIMEOUT);
276                     if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
277                         SSLLogger.warning("Invalid timeout for " +
278                                 "jdk.tls.server.sessionTicketTimeout: " + s +
279                                 ".  Set to default value " + timeout);
280 
281                     }
282                 }
283             }
284 
285             int defaultCacheLimit = GetIntegerAction.privilegedGetProperty(
286                     "javax.net.ssl.sessionCacheSize", DEFAULT_MAX_CACHE_SIZE);
287 
288             if (defaultCacheLimit >= 0) {
289                 return defaultCacheLimit;
290             } else if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
291                 SSLLogger.warning(
292                     "invalid System Property javax.net.ssl.sessionCacheSize, " +
293                     "use the default session cache size (" +
294                     DEFAULT_MAX_CACHE_SIZE + ") instead");
295             }
296         } catch (Exception e) {
297             // unlikely, log it for safe
298             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
299                 SSLLogger.warning(
300                     "the System Property javax.net.ssl.sessionCacheSize is " +
301                     "not available, use the default value (" +
302                     DEFAULT_MAX_CACHE_SIZE + ") instead");
303             }
304         }
305 
306         return DEFAULT_MAX_CACHE_SIZE;
307     }
308 
isTimedout(SSLSession sess)309     private boolean isTimedout(SSLSession sess) {
310         if (timeout == 0) {
311             return false;
312         }
313 
314         if ((sess != null) && ((sess.getCreationTime() + timeout * 1000L)
315                                         <= (System.currentTimeMillis()))) {
316             sess.invalidate();
317             return true;
318         }
319 
320         return false;
321     }
322 
323     private final class SessionCacheVisitor
324             implements Cache.CacheVisitor<SessionId, SSLSessionImpl> {
325         ArrayList<byte[]> ids = null;
326 
327         // public void visit(java.util.Map<K,V> map) {}
328         @Override
visit(java.util.Map<SessionId, SSLSessionImpl> map)329         public void visit(java.util.Map<SessionId, SSLSessionImpl> map) {
330             ids = new ArrayList<>(map.size());
331 
332             for (SessionId key : map.keySet()) {
333                 SSLSessionImpl value = map.get(key);
334                 if (!isTimedout(value)) {
335                     ids.add(key.getId());
336                 }
337             }
338         }
339 
getSessionIds()340         Enumeration<byte[]> getSessionIds() {
341             return  ids != null ? Collections.enumeration(ids) :
342                                   Collections.emptyEnumeration();
343         }
344     }
345 }
346