2 * Copyright (c) 1999-2001,2005-2008,2010-2014 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 #include <utilities/SecIOFormat.h>
63 /* default time-to-live in cache, in seconds */
64 #define QUICK_CACHE_TEST 0
66 #define SESSION_CACHE_TTL ((CFTimeInterval)5)
68 #define SESSION_CACHE_TTL ((CFTimeInterval)(10 * 60))
69 #endif /* QUICK_CACHE_TEST */
73 #define DUMP_ALL_CACHE 0
75 static void cachePrint(
78 const SSLBuffer
*data
)
80 printf("entry: %p ", entry
);
81 unsigned char *kd
= key
->data
;
83 unsigned char *dd
= data
->data
;
84 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X"
85 " data: %02X%02X%02X%02X... (len %d)\n",
86 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7],
87 dd
[0],dd
[1],dd
[2],dd
[3], (unsigned)data
->length
);
91 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n",
92 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7]);
95 #else /* !CACHE_PRINT */
96 #define cachePrint(e, k, d)
97 #define DUMP_ALL_CACHE 0
98 #endif /* CACHE_PRINT */
101 static void dumpAllCache(void);
103 #define dumpAllCache()
107 * One entry (value) in SessionCache.
109 typedef struct SessionCacheEntry SessionCacheEntry
;
110 struct SessionCacheEntry
{
111 /* Linked list of SessionCacheEntries. */
112 SessionCacheEntry
*next
;
115 SSLBuffer mSessionData
;
117 /* this entry to be removed from session map at this time */
118 CFAbsoluteTime mExpiration
;
122 * Note: the caller passes in the expiration time solely to accomodate the
123 * instantiation of a single const Time::Interval for use in calculating
124 * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache.
127 * This constructor, the only one, allocs copies of the key and value
130 static SessionCacheEntry
*SessionCacheEntryCreate(
131 const SSLBuffer
*key
,
132 const SSLBuffer
*sessionData
,
133 CFAbsoluteTime expirationTime
)
137 SessionCacheEntry
*entry
= sslMalloc(sizeof(SessionCacheEntry
));
141 serr
= SSLCopyBuffer(key
, &entry
->mKey
);
146 serr
= SSLCopyBuffer(sessionData
, &entry
->mSessionData
);
148 SSLFreeBuffer(&entry
->mKey
);
153 sslLogSessCacheDebug("SessionCacheEntryCreate(buf,buf) %p", entry
);
154 entry
->mExpiration
= expirationTime
;
159 static void SessionCacheEntryDelete(SessionCacheEntry
*entry
)
161 sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry
);
162 SSLFreeBuffer(&entry
->mKey
); // no SSLContext
163 SSLFreeBuffer(&entry
->mSessionData
);
167 /* basic lookup/match function */
168 static bool SessionCacheEntryMatchKey(SessionCacheEntry
*entry
,
169 const SSLBuffer
*key
)
171 if(key
->length
!= entry
->mKey
.length
) {
174 if((key
->data
== NULL
) || (entry
->mKey
.data
== NULL
)) {
177 return (memcmp(key
->data
, entry
->mKey
.data
, entry
->mKey
.length
) == 0);
180 static bool SessionCacheEntryIsStale(SessionCacheEntry
*entry
,
183 return now
> entry
->mExpiration
;
186 /* has this expired? */
187 static bool SessionCacheEntryIsStaleNow(SessionCacheEntry
*entry
)
189 return SessionCacheEntryIsStale(entry
, CFAbsoluteTimeGetCurrent());
192 /* replace existing mSessionData */
193 static OSStatus
SessionCacheEntrySetSessionData(SessionCacheEntry
*entry
,
194 const SSLBuffer
*data
)
196 SSLFreeBuffer(&entry
->mSessionData
);
197 return SSLCopyBuffer(data
, &entry
->mSessionData
);
201 * Global list of sessions and associated state. We maintain a singleton of
204 typedef struct SessionCache
{
205 SessionCacheEntry
*head
;
206 CFTimeInterval mTimeToLive
; /* default time-to-live in seconds */
209 static pthread_mutex_t gSessionCacheLock
= PTHREAD_MUTEX_INITIALIZER
;
210 static SessionCache
*gSessionCache
= NULL
;
212 static void SessionCacheInit(void) {
213 gSessionCache
= sslMalloc(sizeof(SessionCache
));
214 gSessionCache
->head
= NULL
;
215 gSessionCache
->mTimeToLive
= SESSION_CACHE_TTL
;
218 static SessionCache
*SessionCacheGetLockedInstance(void) {
219 pthread_mutex_lock(&gSessionCacheLock
);
220 if (!gSessionCache
) {
221 /* We could use pthread_once, but we already have a mutex for other
226 return gSessionCache
;
229 /* these three correspond to the C functions exported by this file */
230 static OSStatus
SessionCacheAddEntry(
232 const SSLBuffer
*sessionKey
,
233 const SSLBuffer
*sessionData
,
234 uint32_t timeToLive
) /* optional time-to-live in seconds; 0 ==> default */
236 SessionCacheEntry
*entry
= NULL
;
237 SessionCacheEntry
**current
;
238 CFTimeInterval expireTime
;
240 for (current
= &(cache
->head
); *current
; current
= &((*current
)->next
)) {
242 if (SessionCacheEntryMatchKey(entry
, sessionKey
)) {
243 /* cache hit - just update this entry's sessionData if necessary */
244 /* Note we leave expiration time and position in queue unchanged
246 /* What if the entry has already expired? */
247 if((entry
->mSessionData
.length
== sessionData
->length
) &&
248 (memcmp(entry
->mSessionData
.data
, sessionData
->data
,
249 sessionData
->length
) == 0)) {
251 * These usually match, and a memcmp is a lot cheaper than
252 * a malloc and a free, hence this quick optimization.....
254 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
255 "entry = %p", entry
);
256 return errSecSuccess
;
259 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
260 "entry = %p", entry
);
261 return SessionCacheEntrySetSessionData(entry
, sessionData
);
266 expireTime
= CFAbsoluteTimeGetCurrent();
268 /* caller-specified */
269 expireTime
+= (CFTimeInterval
)timeToLive
;
273 expireTime
+= cache
->mTimeToLive
;
275 /* this allocs new copy of incoming sessionKey and sessionData */
276 entry
= SessionCacheEntryCreate(sessionKey
, sessionData
, expireTime
);
278 sslLogSessCacheDebug("SessionCache::addEntry %p", entry
);
279 cachePrint(entry
, sessionKey
, sessionData
);
282 /* add to head of queue for LIFO caching */
283 entry
->next
= cache
->head
;
286 return errSecSuccess
;
289 static OSStatus
SessionCacheLookupEntry(
291 const SSLBuffer
*sessionKey
,
292 SSLBuffer
*sessionData
)
294 SessionCacheEntry
*entry
= NULL
;
295 SessionCacheEntry
**current
;
296 for (current
= &(cache
->head
); *current
; current
= &((*current
)->next
)) {
298 if (SessionCacheEntryMatchKey(entry
, sessionKey
))
302 if (*current
== NULL
)
303 return errSSLSessionNotFound
;
305 if (SessionCacheEntryIsStaleNow(entry
)) {
306 sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
307 "entry, deleting; current %p, entry->next %p",
308 entry
, current
, entry
->next
);
309 cachePrint(entry
, sessionKey
, &entry
->mSessionData
);
310 *current
= entry
->next
;
311 SessionCacheEntryDelete(entry
);
312 return errSSLSessionNotFound
;
315 /* alloc/copy sessionData from existing entry (caller must free) */
316 return SSLCopyBuffer(&entry
->mSessionData
, sessionData
);
319 static OSStatus
SessionCacheDeleteEntry(
321 const SSLBuffer
*sessionKey
)
323 SessionCacheEntry
**current
;
325 for (current
= &(cache
->head
); *current
; current
= &((*current
)->next
)) {
326 SessionCacheEntry
*entry
= *current
;
327 if (SessionCacheEntryMatchKey(entry
, sessionKey
)) {
329 sslLogSessCacheDebug("...SessionCacheDeleteEntry: deleting "
330 "cached session (%p)", entry
);
331 cachePrint(entry
, &entry
->mKey
, &entry
->mSessionData
);
333 *current
= entry
->next
;
334 SessionCacheEntryDelete(entry
);
335 return errSecSuccess
;
339 return errSecSuccess
;
342 /* cleanup, delete stale entries */
343 static bool SessionCacheCleanup(SessionCache
*cache
)
346 CFAbsoluteTime rightNow
= CFAbsoluteTimeGetCurrent();
347 SessionCacheEntry
**current
;
349 for (current
= &(cache
->head
); *current
;) {
350 SessionCacheEntry
*entry
= *current
;
351 if(SessionCacheEntryIsStale(entry
, rightNow
)) {
353 sslLogSessCacheDebug("...SessionCacheCleanup: deleting "
354 "cached session (%p)", entry
);
355 cachePrint(entry
, &entry
->mKey
, &entry
->mSessionData
);
357 *current
= entry
->next
;
358 SessionCacheEntryDelete(entry
);
361 current
= &((*current
)->next
);
362 /* we're leaving one in the map */
370 static void dumpAllCache(void)
372 SessionCache
*cache
= gSessionCache
;
373 SessionCacheEntry
*entry
;
375 printf("Contents of sessionCache:\n");
376 for(entry
= cache
->head
; entry
; entry
= entry
->next
) {
377 cachePrint(entry
, &entry
->mKey
, &entry
->mSessionData
);
380 #endif /* DUMP_ALL_CACHE */
383 * Store opaque sessionData, associated with opaque sessionKey.
385 OSStatus
sslAddSession (
386 const SSLBuffer sessionKey
,
387 const SSLBuffer sessionData
,
388 uint32_t timeToLive
) /* optional time-to-live in seconds; 0 ==> default */
390 SessionCache
*cache
= SessionCacheGetLockedInstance();
393 serr
= errSSLSessionNotFound
;
396 serr
= SessionCacheAddEntry(cache
, &sessionKey
, &sessionData
, timeToLive
);
401 pthread_mutex_unlock(&gSessionCacheLock
);
406 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
408 OSStatus
sslCopySession (
409 const SSLBuffer sessionKey
,
410 SSLBuffer
*sessionData
)
412 SessionCache
*cache
= SessionCacheGetLockedInstance();
415 serr
= errSSLSessionNotFound
;
418 serr
= SessionCacheLookupEntry(cache
, &sessionKey
, sessionData
);
420 sslLogSessCacheDebug("sslGetSession(%d, %p): %d",
421 (int)sessionKey
.length
, sessionKey
.data
,
424 cachePrint(NULL
, &sessionKey
, sessionData
);
427 cachePrint(NULL
, &sessionKey
, NULL
);
432 pthread_mutex_unlock(&gSessionCacheLock
);
437 OSStatus
sslDeleteSession (
438 const SSLBuffer sessionKey
)
440 SessionCache
*cache
= SessionCacheGetLockedInstance();
443 serr
= errSSLSessionNotFound
;
446 serr
= SessionCacheDeleteEntry(cache
, &sessionKey
);
449 pthread_mutex_unlock(&gSessionCacheLock
);
453 /* cleanup up session cache, deleting stale entries. */
454 OSStatus
sslCleanupSession(void)
456 SessionCache
*cache
= SessionCacheGetLockedInstance();
457 OSStatus serr
= errSecSuccess
;
458 bool moreToGo
= false;
461 serr
= errSSLSessionNotFound
;
464 moreToGo
= SessionCacheCleanup(cache
);
466 /* Possible TBD: if moreToGo, schedule a timed callback to this function */
468 pthread_mutex_unlock(&gSessionCacheLock
);