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