1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt; 18 19 import java.util.Arrays; 20 import java.util.Enumeration; 21 import java.util.Iterator; 22 import java.util.LinkedHashMap; 23 import java.util.Map; 24 import java.util.NoSuchElementException; 25 import javax.net.ssl.SSLSession; 26 import javax.net.ssl.SSLSessionContext; 27 28 /** 29 * Supports SSL session caches. 30 */ 31 abstract class AbstractSessionContext implements SSLSessionContext { 32 33 /** 34 * Maximum lifetime of a session (in seconds) after which it's considered invalid and should not 35 * be used to for new connections. 36 */ 37 private static final int DEFAULT_SESSION_TIMEOUT_SECONDS = 8 * 60 * 60; 38 39 private volatile int maximumSize; 40 private volatile int timeout = DEFAULT_SESSION_TIMEOUT_SECONDS; 41 42 final long sslCtxNativePointer = NativeCrypto.SSL_CTX_new(); 43 44 @SuppressWarnings("serial") 45 private final Map<ByteArray, SslSessionWrapper> sessions = 46 new LinkedHashMap<ByteArray, SslSessionWrapper>() { 47 @Override 48 protected boolean removeEldestEntry( 49 Map.Entry<ByteArray, SslSessionWrapper> eldest) { 50 // NOTE: does not take into account any session that may have become 51 // invalid. 52 if (maximumSize > 0 && size() > maximumSize) { 53 // Let the subclass know. 54 onBeforeRemoveSession(eldest.getValue()); 55 return true; 56 } 57 return false; 58 } 59 }; 60 61 /** 62 * Constructs a new session context. 63 * 64 * @param maximumSize of cache 65 */ AbstractSessionContext(int maximumSize)66 AbstractSessionContext(int maximumSize) { 67 this.maximumSize = maximumSize; 68 } 69 70 /** 71 * This method is provided for API-compatibility only, not intended for use. No guarantees 72 * are made WRT performance. 73 */ 74 @Override getIds()75 public final Enumeration<byte[]> getIds() { 76 // Make a copy of the IDs. 77 final Iterator<SslSessionWrapper> iter; 78 synchronized (sessions) { 79 iter = Arrays.asList(sessions.values().toArray(new SslSessionWrapper[sessions.size()])) 80 .iterator(); 81 } 82 return new Enumeration<byte[]>() { 83 private SslSessionWrapper next; 84 85 @Override 86 public boolean hasMoreElements() { 87 if (next != null) { 88 return true; 89 } 90 while (iter.hasNext()) { 91 SslSessionWrapper session = iter.next(); 92 if (session.isValid()) { 93 next = session; 94 return true; 95 } 96 } 97 next = null; 98 return false; 99 } 100 101 @Override 102 public byte[] nextElement() { 103 if (hasMoreElements()) { 104 byte[] id = next.getId(); 105 next = null; 106 return id; 107 } 108 throw new NoSuchElementException(); 109 } 110 }; 111 } 112 113 /** 114 * This is provided for API-compatibility only, not intended for use. No guarantees are 115 * made WRT performance or the validity of the returned session. 116 */ 117 @Override getSession(byte[] sessionId)118 public final SSLSession getSession(byte[] sessionId) { 119 if (sessionId == null) { 120 throw new NullPointerException("sessionId"); 121 } 122 ByteArray key = new ByteArray(sessionId); 123 SslSessionWrapper session; 124 synchronized (sessions) { 125 session = sessions.get(key); 126 } 127 if (session != null && session.isValid()) { 128 return session.toSSLSession(); 129 } 130 return null; 131 } 132 133 @Override getSessionCacheSize()134 public final int getSessionCacheSize() { 135 return maximumSize; 136 } 137 138 @Override getSessionTimeout()139 public final int getSessionTimeout() { 140 return timeout; 141 } 142 143 @Override setSessionTimeout(int seconds)144 public final void setSessionTimeout(int seconds) throws IllegalArgumentException { 145 if (seconds < 0) { 146 throw new IllegalArgumentException("seconds < 0"); 147 } 148 149 synchronized (sessions) { 150 // Set the timeout on this context. 151 timeout = seconds; 152 NativeCrypto.SSL_CTX_set_timeout(sslCtxNativePointer, seconds); 153 154 Iterator<SslSessionWrapper> i = sessions.values().iterator(); 155 while (i.hasNext()) { 156 SslSessionWrapper session = i.next(); 157 // SSLSession's know their context and consult the 158 // timeout as part of their validity condition. 159 if (!session.isValid()) { 160 // Let the subclass know. 161 onBeforeRemoveSession(session); 162 i.remove(); 163 } 164 } 165 } 166 } 167 168 @Override setSessionCacheSize(int size)169 public final void setSessionCacheSize(int size) throws IllegalArgumentException { 170 if (size < 0) { 171 throw new IllegalArgumentException("size < 0"); 172 } 173 174 int oldMaximum = maximumSize; 175 maximumSize = size; 176 177 // Trim cache to size if necessary. 178 if (size < oldMaximum) { 179 trimToSize(); 180 } 181 } 182 183 @Override finalize()184 protected void finalize() throws Throwable { 185 try { 186 NativeCrypto.SSL_CTX_free(sslCtxNativePointer); 187 } finally { 188 super.finalize(); 189 } 190 } 191 192 /** 193 * Adds the given session to the cache. 194 */ cacheSession(SslSessionWrapper session)195 final void cacheSession(SslSessionWrapper session) { 196 byte[] id = session.getId(); 197 if (id == null || id.length == 0) { 198 return; 199 } 200 201 // Let the subclass know. 202 onBeforeAddSession(session); 203 204 ByteArray key = new ByteArray(id); 205 synchronized (sessions) { 206 sessions.put(key, session); 207 } 208 } 209 210 /** 211 * Called for server sessions only. Retrieves the session by its ID. Overridden by 212 * {@link ServerSessionContext} to 213 */ getSessionFromCache(byte[] sessionId)214 final SslSessionWrapper getSessionFromCache(byte[] sessionId) { 215 if (sessionId == null) { 216 return null; 217 } 218 219 // First, look in the in-memory cache. 220 SslSessionWrapper session; 221 synchronized (sessions) { 222 session = sessions.get(new ByteArray(sessionId)); 223 } 224 if (session != null && session.isValid()) { 225 return session; 226 } 227 228 // Not found in-memory - look it up in the persistent cache. 229 return getSessionFromPersistentCache(sessionId); 230 } 231 232 /** 233 * Called when the given session is about to be added. Used by {@link ClientSessionContext} to 234 * update its host-and-port based cache. 235 * 236 * <p>Visible for extension only, not intended to be called directly. 237 */ onBeforeAddSession(SslSessionWrapper session)238 abstract void onBeforeAddSession(SslSessionWrapper session); 239 240 /** 241 * Called when a session is about to be removed. Used by {@link ClientSessionContext} 242 * to update its host-and-port based cache. 243 * 244 * <p>Visible for extension only, not intended to be called directly. 245 */ onBeforeRemoveSession(SslSessionWrapper session)246 abstract void onBeforeRemoveSession(SslSessionWrapper session); 247 248 /** 249 * Called for server sessions only. Retrieves the session by ID from the persistent cache. 250 * 251 * <p>Visible for extension only, not intended to be called directly. 252 */ getSessionFromPersistentCache(byte[] sessionId)253 abstract SslSessionWrapper getSessionFromPersistentCache(byte[] sessionId); 254 255 /** 256 * Makes sure cache size is < maximumSize. 257 */ trimToSize()258 private void trimToSize() { 259 synchronized (sessions) { 260 int size = sessions.size(); 261 if (size > maximumSize) { 262 int removals = size - maximumSize; 263 Iterator<SslSessionWrapper> i = sessions.values().iterator(); 264 while (removals-- > 0) { 265 SslSessionWrapper session = i.next(); 266 onBeforeRemoveSession(session); 267 i.remove(); 268 } 269 } 270 } 271 } 272 } 273