2 * Copyright (c) 1999-2001,2005-2008,2010-2012 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * appleSession.c - Session storage module, Apple CDSA version.
29 * The current implementation stores sessions in a linked list, a member of a
30 * SessionCache object for which we keep a single global instance. It is
31 * expected that at a given time, only a small number of sessions will be
32 * cached, so the random insertion access provided by a map<> is unnecessary.
33 * New entries are placed in the head of the list, assuming a LIFO usage
36 * Entries in this cache have a time to live of SESSION_CACHE_TTL, currently
37 * ten minutes. Entries are tested for being stale upon lookup; also, the global
38 * sslCleanupSession() tests all entries in the cache, deleting entries which
39 * are stale. This function is currently called whenever an SSLContext is deleted.
40 * The current design does not provide any asynchronous timed callouts to perform
41 * further cache cleanup; it was decided that the thread overhead of this would
42 * outweight the benefits (again assuming a small number of entries in the
45 * When a session is added via sslAddSession, and a cache entry already
46 * exists for the specifed key (sessionID), the sessionData for the existing
47 * cache entry is updated with the new sessionData. The entry's expiration
48 * time is unchanged (thus a given session entry can only be used for a finite
49 * time no mattter how often it is re-used),
53 #include "sslMemory.h"
55 #include "appleSession.h"
57 #include <CoreFoundation/CFDate.h>
61 /* default time-to-live in cache, in seconds */
62 #define QUICK_CACHE_TEST 0
64 #define SESSION_CACHE_TTL ((CFTimeInterval)5)
66 #define SESSION_CACHE_TTL ((CFTimeInterval)(10 * 60))
67 #endif /* QUICK_CACHE_TEST */
71 #define DUMP_ALL_CACHE 0
73 static void cachePrint(
76 const SSLBuffer
*data
)
78 printf("entry: %p ", entry
);
79 unsigned char *kd
= key
->data
;
81 unsigned char *dd
= data
->data
;
82 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X"
83 " data: %02X%02X%02X%02X... (len %d)\n",
84 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7],
85 dd
[0],dd
[1],dd
[2],dd
[3], (unsigned)data
->length
);
89 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n",
90 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7]);
93 #else /* !CACHE_PRINT */
94 #define cachePrint(e, k, d)
95 #define DUMP_ALL_CACHE 0
96 #endif /* CACHE_PRINT */
99 static void dumpAllCache(void);
101 #define dumpAllCache()
105 * One entry (value) in SessionCache.
107 typedef struct SessionCacheEntry SessionCacheEntry
;
108 struct SessionCacheEntry
{
109 /* Linked list of SessionCacheEntries. */
110 SessionCacheEntry
*next
;
113 SSLBuffer mSessionData
;
115 /* this entry to be removed from session map at this time */
116 CFAbsoluteTime mExpiration
;
120 * Note: the caller passes in the expiration time solely to accomodate the
121 * instantiation of a single const Time::Interval for use in calculating
122 * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache.
125 * This constructor, the only one, allocs copies of the key and value
128 static SessionCacheEntry
*SessionCacheEntryCreate(
129 const SSLBuffer
*key
,
130 const SSLBuffer
*sessionData
,
131 CFAbsoluteTime expirationTime
)
135 SessionCacheEntry
*entry
= sslMalloc(sizeof(SessionCacheEntry
));
139 serr
= SSLCopyBuffer(key
, &entry
->mKey
);
144 serr
= SSLCopyBuffer(sessionData
, &entry
->mSessionData
);
146 SSLFreeBuffer(&entry
->mKey
, NULL
);
151 sslLogSessCacheDebug("SessionCacheEntryCreate(buf,buf) %p", entry
);
152 entry
->mExpiration
= expirationTime
;
157 static void SessionCacheEntryDelete(SessionCacheEntry
*entry
)
159 sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry
);
160 SSLFreeBuffer(&entry
->mKey
, NULL
); // no SSLContext
161 SSLFreeBuffer(&entry
->mSessionData
, NULL
);
165 /* basic lookup/match function */
166 static bool SessionCacheEntryMatchKey(SessionCacheEntry
*entry
,
167 const SSLBuffer
*key
)
169 if(key
->length
!= entry
->mKey
.length
) {
172 if((key
->data
== NULL
) || (entry
->mKey
.data
== NULL
)) {
175 return (memcmp(key
->data
, entry
->mKey
.data
, entry
->mKey
.length
) == 0);
178 static bool SessionCacheEntryIsStale(SessionCacheEntry
*entry
,
181 return now
> entry
->mExpiration
;
184 /* has this expired? */
185 static bool SessionCacheEntryIsStaleNow(SessionCacheEntry
*entry
)
187 return SessionCacheEntryIsStale(entry
, CFAbsoluteTimeGetCurrent());
190 /* replace existing mSessionData */
191 static OSStatus
SessionCacheEntrySetSessionData(SessionCacheEntry
*entry
,
192 const SSLBuffer
*data
)
194 SSLFreeBuffer(&entry
->mSessionData
, NULL
);
195 return SSLCopyBuffer(data
, &entry
->mSessionData
);
199 * Global list of sessions and associated state. We maintain a singleton of
202 typedef struct SessionCache
{
203 SessionCacheEntry
*head
;
204 CFTimeInterval mTimeToLive
; /* default time-to-live in seconds */
207 static pthread_mutex_t gSessionCacheLock
= PTHREAD_MUTEX_INITIALIZER
;
208 static SessionCache
*gSessionCache
= NULL
;
210 static void SessionCacheInit(void) {
211 gSessionCache
= sslMalloc(sizeof(SessionCache
));
212 gSessionCache
->head
= NULL
;
213 gSessionCache
->mTimeToLive
= SESSION_CACHE_TTL
;
216 static SessionCache
*SessionCacheGetLockedInstance(void) {
217 pthread_mutex_lock(&gSessionCacheLock
);
218 if (!gSessionCache
) {
219 /* We could use pthread_once, but we already have a mutex for other
224 return gSessionCache
;
227 /* these three correspond to the C functions exported by this file */
228 static OSStatus
SessionCacheAddEntry(
230 const SSLBuffer
*sessionKey
,
231 const SSLBuffer
*sessionData
,
232 uint32_t timeToLive
) /* optional time-to-live in seconds; 0 ==> default */
234 SessionCacheEntry
*entry
= NULL
;
235 SessionCacheEntry
**current
;
236 CFTimeInterval expireTime
;
238 for (current
= &(cache
->head
); *current
; current
= &((*current
)->next
)) {
240 if (SessionCacheEntryMatchKey(entry
, sessionKey
)) {
241 /* cache hit - just update this entry's sessionData if necessary */
242 /* Note we leave expiration time and position in queue unchanged
244 /* What if the entry has already expired? */
245 if((entry
->mSessionData
.length
== sessionData
->length
) &&
246 (memcmp(entry
->mSessionData
.data
, sessionData
->data
,
247 sessionData
->length
) == 0)) {
249 * These usually match, and a memcmp is a lot cheaper than
250 * a malloc and a free, hence this quick optimization.....
252 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
253 "entry = %p", entry
);
257 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
258 "entry = %p", entry
);
259 return SessionCacheEntrySetSessionData(entry
, sessionData
);
264 expireTime
= CFAbsoluteTimeGetCurrent();
266 /* caller-specified */
267 expireTime
+= (CFTimeInterval
)timeToLive
;
271 expireTime
+= cache
->mTimeToLive
;
273 /* this allocs new copy of incoming sessionKey and sessionData */
274 entry
= SessionCacheEntryCreate(sessionKey
, sessionData
, expireTime
);
276 sslLogSessCacheDebug("SessionCache::addEntry %p", entry
);
277 cachePrint(entry
, sessionKey
, sessionData
);
280 /* add to head of queue for LIFO caching */
281 entry
->next
= cache
->head
;
287 static OSStatus
SessionCacheLookupEntry(
289 const SSLBuffer
*sessionKey
,
290 SSLBuffer
*sessionData
)
292 SessionCacheEntry
*entry
= NULL
;
293 SessionCacheEntry
**current
;
294 for (current
= &(cache
->head
); *current
; current
= &((*current
)->next
)) {
296 if (SessionCacheEntryMatchKey(entry
, sessionKey
))
300 if (*current
== NULL
)
301 return errSSLSessionNotFound
;
303 if (SessionCacheEntryIsStaleNow(entry
)) {
304 sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
305 "entry, deleting; current %p, entry->next %p",
306 entry
, current
, entry
->next
);
307 cachePrint(entry
, sessionKey
, &entry
->mSessionData
);
308 *current
= entry
->next
;
309 SessionCacheEntryDelete(entry
);
310 return errSSLSessionNotFound
;
313 /* alloc/copy sessionData from existing entry (caller must free) */
314 return SSLCopyBuffer(&entry
->mSessionData
, sessionData
);
317 static OSStatus
SessionCacheDeleteEntry(
319 const SSLBuffer
*sessionKey
)
321 SessionCacheEntry
**current
;
323 for (current
= &(cache
->head
); *current
; current
= &((*current
)->next
)) {
324 SessionCacheEntry
*entry
= *current
;
325 if (SessionCacheEntryMatchKey(entry
, sessionKey
)) {
327 sslLogSessCacheDebug("...SessionCacheDeleteEntry: deleting "
328 "cached session (%p)", entry
);
329 cachePrint(entry
, &entry
->mKey
, &entry
->mSessionData
);
331 *current
= entry
->next
;
332 SessionCacheEntryDelete(entry
);
340 /* cleanup, delete stale entries */
341 static bool SessionCacheCleanup(SessionCache
*cache
)
344 CFAbsoluteTime rightNow
= CFAbsoluteTimeGetCurrent();
345 SessionCacheEntry
**current
;
347 for (current
= &(cache
->head
); *current
;) {
348 SessionCacheEntry
*entry
= *current
;
349 if(SessionCacheEntryIsStale(entry
, rightNow
)) {
351 sslLogSessCacheDebug("...SessionCacheCleanup: deleting "
352 "cached session (%p)", entry
);
353 cachePrint(entry
, &entry
->mKey
, &entry
->mSessionData
);
355 *current
= entry
->next
;
356 SessionCacheEntryDelete(entry
);
359 current
= &((*current
)->next
);
360 /* we're leaving one in the map */
368 static void dumpAllCache(void)
370 SessionCache
*cache
= gSessionCache
;
371 SessionCacheEntry
*entry
;
373 printf("Contents of sessionCache:\n");
374 for(entry
= cache
->head
; entry
; entry
= entry
->next
) {
375 cachePrint(entry
, &entry
->mKey
, &entry
->mSessionData
);
378 #endif /* DUMP_ALL_CACHE */
381 * Store opaque sessionData, associated with opaque sessionKey.
383 OSStatus
sslAddSession (
384 const SSLBuffer sessionKey
,
385 const SSLBuffer sessionData
,
386 uint32_t timeToLive
) /* optional time-to-live in seconds; 0 ==> default */
388 SessionCache
*cache
= SessionCacheGetLockedInstance();
391 serr
= errSSLSessionNotFound
;
394 serr
= SessionCacheAddEntry(cache
, &sessionKey
, &sessionData
, timeToLive
);
399 pthread_mutex_unlock(&gSessionCacheLock
);
404 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
406 OSStatus
sslGetSession (
407 const SSLBuffer sessionKey
,
408 SSLBuffer
*sessionData
)
410 SessionCache
*cache
= SessionCacheGetLockedInstance();
413 serr
= errSSLSessionNotFound
;
416 serr
= SessionCacheLookupEntry(cache
, &sessionKey
, sessionData
);
418 sslLogSessCacheDebug("sslGetSession(%d, %p): %ld",
419 (int)sessionKey
.length
, sessionKey
.data
,
422 cachePrint(NULL
, &sessionKey
, sessionData
);
425 cachePrint(NULL
, &sessionKey
, NULL
);
430 pthread_mutex_unlock(&gSessionCacheLock
);
435 OSStatus
sslDeleteSession (
436 const SSLBuffer sessionKey
)
438 SessionCache
*cache
= SessionCacheGetLockedInstance();
441 serr
= errSSLSessionNotFound
;
444 serr
= SessionCacheDeleteEntry(cache
, &sessionKey
);
447 pthread_mutex_unlock(&gSessionCacheLock
);
451 /* cleanup up session cache, deleting stale entries. */
452 OSStatus
sslCleanupSession(void)
454 SessionCache
*cache
= SessionCacheGetLockedInstance();
455 OSStatus serr
= noErr
;
456 bool moreToGo
= false;
459 serr
= errSSLSessionNotFound
;
462 moreToGo
= SessionCacheCleanup(cache
);
464 /* Possible TBD: if moreToGo, schedule a timed callback to this function */
466 pthread_mutex_unlock(&gSessionCacheLock
);