2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 File: appleSession.cpp
22 Contains: Session storage module, Apple CDSA version.
24 Written by: Doug Mitchell
26 Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
31 * The current implementation stores sessions in a deque<>, a member of a
32 * SessionCache object for which we keep a ModuleNexus-ized instance. It is
33 * expected that at a given time, only a small number of sessions will be
34 * cached, so the random insertion access provided by a map<> is unnecessary.
35 * New entries are placed in the head of the queue, assuming a LIFO usage
38 * Entries in this cache have a time to live of SESSION_CACHE_TTL, currently
39 * ten minutes. Entries are tested for being stale upon lookup; also, the global
40 * sslCleanupSession() tests all entries in the cache, deleting entries which
41 * are stale. This function is currently called whenever an SSLContext is deleted.
42 * The current design does not provide any asynchronous timed callouts to perform
43 * further cache cleanup; it was decided that the thread overhead of this would
44 * outweight the benefits (again assuming a small number of entries in the
47 * When a session is added via sslAddSession, and a cache entry already
48 * exists for the specifed key (sessionID), the sessionData for the existing
49 * cache entry is updated with the new sessionData. The entry's expiration
50 * time is unchanged (thus a given session entry can only be used for a finite
51 * time no mattter how often it is re-used),
55 #include "sslMemory.h"
57 #include "appleSession.h"
59 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
63 #include <Security/threading.h>
64 #include <Security/globalizer.h>
65 #include <Security/timeflow.h>
67 /* time-to-live in cache, in seconds */
68 #define QUICK_CACHE_TEST 0
70 #define SESSION_CACHE_TTL ((int)5)
72 #define SESSION_CACHE_TTL ((int)(10 * 60))
73 #endif /* QUICK_CACHE_TEST */
77 #define DUMP_ALL_CACHE 0
79 static void cachePrint(
81 const SSLBuffer
*data
)
83 unsigned char *kd
= key
->data
;
85 unsigned char *dd
= data
->data
;
86 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X"
87 " data: %02X%02X%02X%02X... (len %d)\n",
88 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7],
89 dd
[0],dd
[1],dd
[2],dd
[3], (unsigned)data
->length
);
93 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n",
94 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7]);
97 #else /* !CACHE_PRINT */
98 #define cachePrint(k, d)
99 #define DUMP_ALL_CACHE 0
100 #endif /* CACHE_PRINT */
103 static void dumpAllCache();
105 #define dumpAllCache()
109 * One entry (value) in SessionCache.
111 class SessionCacheEntry
{
114 * This constructor, the only one, allocs copies of the key and value
118 const SSLBuffer
&key
,
119 const SSLBuffer
&sessionData
,
120 const Time::Absolute
&expirationTime
);
121 ~SessionCacheEntry();
123 /* basic lookup/match function */
124 bool matchKey(const SSLBuffer
&key
) const;
126 /* has this expired? */
127 bool isStale(); // calculates "now"
128 bool isStale(const Time::Absolute
&now
); // when you know it
130 /* key/data accessors */
131 SSLBuffer
&key() { return mKey
; }
132 SSLBuffer
&sessionData() { return mSessionData
; }
134 /* replace existing mSessionData */
135 OSStatus
sessionData(const SSLBuffer
&data
);
139 SSLBuffer mSessionData
;
141 /* this entry to be removed from session map at this time */
142 Time::Absolute mExpiration
;
146 * Note: the caller passes in the expiration time solely to accomodate the
147 * instantiation of a single const Time::Interval for use in calculating
148 * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache.
150 SessionCacheEntry::SessionCacheEntry(
151 const SSLBuffer
&key
,
152 const SSLBuffer
&sessionData
,
153 const Time::Absolute
&expirationTime
)
154 : mExpiration(expirationTime
)
158 serr
= SSLCopyBuffer(key
, mKey
);
160 throw runtime_error("memory error");
162 serr
= SSLCopyBuffer(sessionData
, mSessionData
);
164 throw runtime_error("memory error");
166 sslLogSessCacheDebug("SessionCacheEntry(buf,buf) this %p", this);
167 mExpiration
+= Time::Interval(SESSION_CACHE_TTL
);
170 SessionCacheEntry::~SessionCacheEntry()
172 sslLogSessCacheDebug("~SessionCacheEntry() this %p", this);
173 SSLFreeBuffer(mKey
, NULL
); // no SSLContext
174 SSLFreeBuffer(mSessionData
, NULL
);
177 /* basic lookup/match function */
178 bool SessionCacheEntry::matchKey(const SSLBuffer
&key
) const
180 if(key
.length
!= mKey
.length
) {
183 if((key
.data
== NULL
) || (mKey
.data
== NULL
)) {
186 return (memcmp(key
.data
, mKey
.data
, mKey
.length
) == 0);
189 /* has this expired? */
190 bool SessionCacheEntry::isStale()
192 return isStale(Time::now());
195 bool SessionCacheEntry::isStale(const Time::Absolute
&now
)
197 if(now
> mExpiration
) {
205 /* replace existing mSessionData */
206 OSStatus
SessionCacheEntry::sessionData(
207 const SSLBuffer
&data
)
209 SSLFreeBuffer(mSessionData
, NULL
);
210 return SSLCopyBuffer(data
, mSessionData
);
213 /* Types for the actual deque and its iterator */
214 typedef std::deque
<SessionCacheEntry
*> SessionCacheType
;
215 typedef SessionCacheType::iterator SessionCacheIter
;
218 * Global map and associated state. We maintain a singleton of this.
224 : mTimeToLive(SESSION_CACHE_TTL
) {}
227 /* these correspond to the C functions exported by this file */
229 const SSLBuffer sessionKey
,
230 const SSLBuffer sessionData
);
231 OSStatus
lookupEntry(
232 const SSLBuffer sessionKey
,
233 SSLBuffer
*sessionData
);
234 OSStatus
deleteEntry(
235 const SSLBuffer sessionKey
);
237 /* cleanup, delete stale entries */
239 SessionCacheType
&sessMap() { return mSessionCache
; }
242 SessionCacheIter
lookupPriv(
243 const SSLBuffer
*sessionKey
);
245 const SSLBuffer
*sessionKey
);
246 SessionCacheIter
deletePriv(
247 SessionCacheIter iter
);
248 SessionCacheType mSessionCache
;
250 const Time::Interval mTimeToLive
;
253 SessionCache::~SessionCache()
255 /* free all entries */
256 StLock
<Mutex
> _(mSessionLock
);
257 for(SessionCacheIter iter
= mSessionCache
.begin(); iter
!= mSessionCache
.end(); ) {
258 iter
= deletePriv(iter
);
262 /* these three correspond to the C functions exported by this file */
263 OSStatus
SessionCache::addEntry(
264 const SSLBuffer sessionKey
,
265 const SSLBuffer sessionData
)
267 StLock
<Mutex
> _(mSessionLock
);
269 SessionCacheIter existIter
= lookupPriv(&sessionKey
);
270 if(existIter
!= mSessionCache
.end()) {
271 /* cache hit - just update this entry's sessionData if necessary */
272 /* Note we leave expiration time and position in deque unchanged - OK? */
273 SessionCacheEntry
*existEntry
= *existIter
;
274 SSLBuffer
&existBuf
= existEntry
->sessionData();
275 if((existBuf
.length
== sessionData
.length
) &&
276 (memcmp(existBuf
.data
, sessionData
.data
, sessionData
.length
) == 0)) {
278 * These usually match, and a memcmp is a lot cheaper than
279 * a malloc and a free, hence this quick optimization.....
281 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
282 "entry = %p", existEntry
);
286 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
287 "entry = %p", existEntry
);
288 return existEntry
->sessionData(sessionData
);
292 /* this allocs new copy of incoming sessionKey and sessionData */
293 SessionCacheEntry
*entry
= new SessionCacheEntry(sessionKey
,
295 Time::now() + mTimeToLive
);
297 sslLogSessCacheDebug("SessionCache::addEntry %p", entry
);
298 cachePrint(&sessionKey
, &sessionData
);
301 /* add to head of queue for LIFO caching */
302 mSessionCache
.push_front(entry
);
303 assert(lookupPriv(&sessionKey
) != mSessionCache
.end());
307 OSStatus
SessionCache::lookupEntry(
308 const SSLBuffer sessionKey
,
309 SSLBuffer
*sessionData
)
311 StLock
<Mutex
> _(mSessionLock
);
313 SessionCacheIter existIter
= lookupPriv(&sessionKey
);
314 if(existIter
== mSessionCache
.end()) {
315 return errSSLSessionNotFound
;
317 SessionCacheEntry
*entry
= *existIter
;
318 if(entry
->isStale()) {
319 sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
320 "entry, deleting", entry
);
321 cachePrint(&sessionKey
, &entry
->sessionData());
322 deletePriv(existIter
);
323 return errSSLSessionNotFound
;
325 /* alloc/copy sessionData from existing entry (caller must free) */
326 return SSLCopyBuffer(entry
->sessionData(), *sessionData
);
329 OSStatus
SessionCache::deleteEntry(
330 const SSLBuffer sessionKey
)
332 StLock
<Mutex
> _(mSessionLock
);
333 deletePriv(&sessionKey
);
337 /* cleanup, delete stale entries */
338 bool SessionCache::cleanup()
340 StLock
<Mutex
> _(mSessionLock
);
342 Time::Absolute rightNow
= Time::now();
343 SessionCacheIter iter
;
345 for(iter
= mSessionCache
.begin(); iter
!= mSessionCache
.end(); ) {
346 SessionCacheEntry
*entry
= *iter
;
347 if(entry
->isStale(rightNow
)) {
349 sslLogSessCacheDebug("...SessionCache::cleanup: deleting "
350 "cached session (%p)", entry
);
351 cachePrint(&entry
->key(), &entry
->sessionData());
353 iter
= deletePriv(iter
);
357 /* we're leaving one in the map */
364 /* private methods, mSessionLock held on entry and exit */
365 SessionCacheIter
SessionCache::lookupPriv(
366 const SSLBuffer
*sessionKey
)
370 for(it
= mSessionCache
.begin(); it
!= mSessionCache
.end(); it
++) {
371 SessionCacheEntry
*entry
= *it
;
372 if(entry
->matchKey(*sessionKey
)) {
376 /* returning map.end() */
380 void SessionCache::deletePriv(
381 const SSLBuffer
*sessionKey
)
383 SessionCacheIter iter
= lookupPriv(sessionKey
);
384 if(iter
!= mSessionCache
.end()) {
387 * free underlying SSLBuffer.data pointers
388 * destruct the stored map entry
391 SessionCacheEntry
*entry
= *iter
;
392 sslLogSessCacheDebug("SessionCache::deletePriv %p", entry
);
393 cachePrint(sessionKey
, &entry
->sessionData());
398 assert(lookupPriv(sessionKey
) == mSessionCache
.end());
401 /* common erase, given a SessionCacheIter; returns next iter */
402 SessionCacheIter
SessionCache::deletePriv(
403 SessionCacheIter iter
)
405 assert(iter
!= mSessionCache
.end());
406 SessionCacheEntry
*entry
= *iter
;
407 SessionCacheIter nextIter
= mSessionCache
.erase(iter
);
412 /* the single global thing */
413 static ModuleNexus
<SessionCache
> gSessionCache
;
416 static void dumpAllCache()
419 SessionCacheType
&smap
= gSessionCache().sessMap();
421 printf("Contents of sessionCache:\n");
422 for(it
= smap
.begin(); it
!= smap
.end(); it
++) {
423 SessionCacheEntry
*entry
= *it
;
424 cachePrint(&entry
->key(), &entry
->sessionData());
427 #endif /* DUMP_ALL_CACHE */
430 * Store opaque sessionData, associated with opaque sessionKey.
432 OSStatus
sslAddSession (
433 const SSLBuffer sessionKey
,
434 const SSLBuffer sessionData
)
438 serr
= gSessionCache().addEntry(sessionKey
, sessionData
);
448 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
450 OSStatus
sslGetSession (
451 const SSLBuffer sessionKey
,
452 SSLBuffer
*sessionData
)
456 serr
= gSessionCache().lookupEntry(sessionKey
, sessionData
);
459 serr
= errSSLSessionNotFound
;
461 sslLogSessCacheDebug("sslGetSession(%d, %p): %ld",
462 (int)sessionKey
.length
, sessionKey
.data
,
465 cachePrint(&sessionKey
, sessionData
);
468 cachePrint(&sessionKey
, NULL
);
474 OSStatus
sslDeleteSession (
475 const SSLBuffer sessionKey
)
479 serr
= gSessionCache().deleteEntry(sessionKey
);
482 serr
= errSSLSessionNotFound
;
487 /* cleanup up session cache, deleting stale entries. */
488 OSStatus
sslCleanupSession ()
490 OSStatus serr
= noErr
;
491 bool moreToGo
= false;
493 moreToGo
= gSessionCache().cleanup();
496 serr
= errSSLSessionNotFound
;
498 /* Possible TBD: if moreToGo, schedule a timed callback to this function */