• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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