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, based on Netscape SSLRef 3.0
26 Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
31 * This file replaces the caller-specified SSLAddSessionFunc,
32 * SSLGetSessionFunc, and SSLDeleteSessionFunc callbacks in the
33 * original SSLRef 3.0.
35 * The current implementation stores sessions in a deque<>, a member of a
36 * SessionCache object for which we keep a ModuleNexus-ized instance. It is
37 * expected that at a given time, only a small number of sessions will be
38 * cached, so the random insertion access provided by a map<> is unnecessary.
39 * New entries are placed in the head of the queue, assuming a LIFO usage
42 * Entries in this cache have a time to live of SESSION_CACHE_TTL, currently
43 * ten minutes. Entries are tested for being stale upon lookup; also, the global
44 * sslCleanupSession() tests all entries in the cache, deleting entries which
45 * are stale. This function is currently called whenever an SSLContext is deleted.
46 * The current design does not provide any asynchronous timed callouts to perform
47 * further cache cleanup; it was decided that the thread overhead of this would
48 * outweight the benefits (again assuming a small number of entries in the
51 * When a session is added via sslAddSession, and a cache entry already
52 * exists for the specifed key (sessionID), the sessionData for the existing
53 * cache entry is updated with the new sessionData. The entry's expiration
54 * time is unchanged (thus a given session entry can only be used for a finite
55 * time no mattter how often it is re-used),
61 #include "appleGlue.h"
63 #include "appleSession.h"
65 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
69 #include <Security/threading.h>
70 #include <Security/globalizer.h>
71 #include <Security/timeflow.h>
73 /* time-to-live in cache, in seconds */
74 #define QUICK_CACHE_TEST 0
76 #define SESSION_CACHE_TTL ((int)5)
78 #define SESSION_CACHE_TTL ((int)(10 * 60))
79 #endif /* QUICK_CACHE_TEST */
83 #define cprintf(s) printf s
84 #define DUMP_ALL_CACHE 0
86 static void cachePrint(
88 const SSLBuffer
*data
)
90 unsigned char *kd
= key
->data
;
92 unsigned char *dd
= data
->data
;
93 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X"
94 " data: %02X%02X%02X%02X... (len %d)\n",
95 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7],
96 dd
[0],dd
[1],dd
[2],dd
[3], (unsigned)data
->length
);
100 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n",
101 kd
[0],kd
[1],kd
[2],kd
[3], kd
[4],kd
[5],kd
[6],kd
[7]);
104 #else /* !CACHE_PRINT */
106 #define cachePrint(k, d)
107 #define DUMP_ALL_CACHE 0
108 #endif /* CACHE_PRINT */
111 static void dumpAllCache();
113 #define dumpAllCache()
117 * One entry (value) in SessionCache.
119 class SessionCacheEntry
{
122 * This constructor, the only one, allocs copies of the key and value
126 const SSLBuffer
&key
,
127 const SSLBuffer
&sessionData
,
128 const Time::Absolute
&expirationTime
);
129 ~SessionCacheEntry();
131 /* basic lookup/match function */
132 bool matchKey(const SSLBuffer
&key
) const;
134 /* has this expired? */
135 bool isStale(); // calculates "now"
136 bool isStale(const Time::Absolute
&now
); // when you know it
138 /* key/data accessors */
139 SSLBuffer
&key() { return mKey
; }
140 SSLBuffer
&sessionData() { return mSessionData
; }
142 /* replace existing mSessionData */
143 SSLErr
sessionData(const SSLBuffer
&data
);
147 SSLBuffer mSessionData
;
149 /* this entry to be removed from session map at this time */
150 Time::Absolute mExpiration
;
154 * Note: the caller passes in the expiration time solely to accomodate the
155 * instantiation of a single const Time::Interval for use in calculating
156 * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache.
158 SessionCacheEntry::SessionCacheEntry(
159 const SSLBuffer
&key
,
160 const SSLBuffer
&sessionData
,
161 const Time::Absolute
&expirationTime
)
162 : mExpiration(expirationTime
)
166 serr
= SSLCopyBuffer(&key
, &mKey
);
168 throw runtime_error("memory error");
170 serr
= SSLCopyBuffer(&sessionData
, &mSessionData
);
172 throw runtime_error("memory error");
174 cprintf(("SessionCacheEntry(buf,buf) this %p\n", this));
175 mExpiration
+= Time::Interval(SESSION_CACHE_TTL
);
178 SessionCacheEntry::~SessionCacheEntry()
180 cprintf(("~SessionCacheEntry() this %p\n", this));
181 SSLFreeBuffer(&mKey
, NULL
); // no SystemContext
182 SSLFreeBuffer(&mSessionData
, NULL
);
185 /* basic lookup/match function */
186 bool SessionCacheEntry::matchKey(const SSLBuffer
&key
) const
188 if(key
.length
!= mKey
.length
) {
191 if((key
.data
== NULL
) || (mKey
.data
== NULL
)) {
194 return (memcmp(key
.data
, mKey
.data
, mKey
.length
) == 0);
197 /* has this expired? */
198 bool SessionCacheEntry::isStale()
200 return isStale(Time::now());
203 bool SessionCacheEntry::isStale(const Time::Absolute
&now
)
205 if(now
> mExpiration
) {
213 /* replace existing mSessionData */
214 SSLErr
SessionCacheEntry::sessionData(
215 const SSLBuffer
&data
)
217 SSLFreeBuffer(&mSessionData
, NULL
);
218 return SSLCopyBuffer(&data
, &mSessionData
);
221 /* Types for the actual deque and its iterator */
222 typedef std::deque
<SessionCacheEntry
*> SessionCacheType
;
223 typedef SessionCacheType::iterator SessionCacheIter
;
226 * Global map and associated state. We maintain a singleton of this.
232 : mTimeToLive(SESSION_CACHE_TTL
) {}
235 /* these correspond to the C functions exported by this file */
237 const SSLBuffer sessionKey
,
238 const SSLBuffer sessionData
);
240 const SSLBuffer sessionKey
,
241 SSLBuffer
*sessionData
);
243 const SSLBuffer sessionKey
);
245 /* cleanup, delete stale entries */
247 SessionCacheType
&sessMap() { return mSessionCache
; }
250 SessionCacheIter
lookupPriv(
251 const SSLBuffer
*sessionKey
);
253 const SSLBuffer
*sessionKey
);
254 SessionCacheIter
deletePriv(
255 SessionCacheIter iter
);
256 SessionCacheType mSessionCache
;
258 const Time::Interval mTimeToLive
;
261 SessionCache::~SessionCache()
263 /* free all entries */
264 StLock
<Mutex
> _(mSessionLock
);
265 for(SessionCacheIter iter
= mSessionCache
.begin(); iter
!= mSessionCache
.end(); ) {
266 iter
= deletePriv(iter
);
270 /* these three correspond to the C functions exported by this file */
271 SSLErr
SessionCache::addEntry(
272 const SSLBuffer sessionKey
,
273 const SSLBuffer sessionData
)
275 StLock
<Mutex
> _(mSessionLock
);
277 SessionCacheIter existIter
= lookupPriv(&sessionKey
);
278 if(existIter
!= mSessionCache
.end()) {
279 /* cache hit - just update this entry's sessionData if necessary */
280 /* Note we leave expiration time and position in deque unchanged - OK? */
281 SessionCacheEntry
*existEntry
= *existIter
;
282 SSLBuffer
&existBuf
= existEntry
->sessionData();
283 if((existBuf
.length
== sessionData
.length
) &&
284 (memcmp(existBuf
.data
, sessionData
.data
, sessionData
.length
) == 0)) {
286 * These usually match, and a memcmp is a lot cheaper than
287 * a malloc and a free, hence this quick optimization.....
289 cprintf(("SessionCache::addEntry CACHE HIT entry = %p\n", existEntry
));
293 cprintf(("SessionCache::addEntry CACHE REPLACE entry = %p\n", existEntry
));
294 return existEntry
->sessionData(sessionData
);
298 /* this allocs new copy of incoming sessionKey and sessionData */
299 SessionCacheEntry
*entry
= new SessionCacheEntry(sessionKey
,
301 Time::now() + mTimeToLive
);
303 cprintf(("SessionCache::addEntry %p\n", entry
));
304 cachePrint(&sessionKey
, &sessionData
);
307 /* add to head of queue for LIFO caching */
308 mSessionCache
.push_front(entry
);
309 CASSERT(lookupPriv(&sessionKey
) != mSessionCache
.end());
313 SSLErr
SessionCache::lookupEntry(
314 const SSLBuffer sessionKey
,
315 SSLBuffer
*sessionData
)
317 StLock
<Mutex
> _(mSessionLock
);
319 SessionCacheIter existIter
= lookupPriv(&sessionKey
);
320 if(existIter
== mSessionCache
.end()) {
321 return SSLSessionNotFoundErr
;
323 SessionCacheEntry
*entry
= *existIter
;
324 if(entry
->isStale()) {
325 cprintf(("SessionCache::lookupEntry %p: STALE entry, deleting\n", entry
));
326 cachePrint(&sessionKey
, &entry
->sessionData());
327 deletePriv(existIter
);
328 return SSLSessionNotFoundErr
;
330 /* alloc/copy sessionData from existing entry (caller must free) */
331 return SSLCopyBuffer(&entry
->sessionData(), sessionData
);
334 SSLErr
SessionCache::deleteEntry(
335 const SSLBuffer sessionKey
)
337 StLock
<Mutex
> _(mSessionLock
);
338 deletePriv(&sessionKey
);
342 /* cleanup, delete stale entries */
343 bool SessionCache::cleanup()
345 StLock
<Mutex
> _(mSessionLock
);
347 Time::Absolute rightNow
= Time::now();
348 SessionCacheIter iter
;
350 for(iter
= mSessionCache
.begin(); iter
!= mSessionCache
.end(); ) {
351 SessionCacheEntry
*entry
= *iter
;
352 if(entry
->isStale(rightNow
)) {
354 SSLBuffer
*key
= &entry
->key();
355 cprintf(("...SessionCache::cleanup: deleting cached session (%p)\n",
357 cachePrint(key
, &entry
->sessionData());
359 iter
= deletePriv(iter
);
363 /* we're leaving one in the map */
370 /* private methods, mSessionLock held on entry and exit */
371 SessionCacheIter
SessionCache::lookupPriv(
372 const SSLBuffer
*sessionKey
)
376 for(it
= mSessionCache
.begin(); it
!= mSessionCache
.end(); it
++) {
377 SessionCacheEntry
*entry
= *it
;
378 if(entry
->matchKey(*sessionKey
)) {
382 /* returning map.end() */
386 void SessionCache::deletePriv(
387 const SSLBuffer
*sessionKey
)
389 SessionCacheIter iter
= lookupPriv(sessionKey
);
390 if(iter
!= mSessionCache
.end()) {
393 * free underlying SSLBuffer.data pointers
394 * destruct the stored map entry
397 SessionCacheEntry
*entry
= *iter
;
398 cprintf(("SessionCache::deletePriv %p\n", entry
));
399 cachePrint(sessionKey
, &entry
->sessionData());
404 CASSERT(lookupPriv(sessionKey
) == mSessionCache
.end());
407 /* common erase, given a SessionCacheIter; returns next iter */
408 SessionCacheIter
SessionCache::deletePriv(
409 SessionCacheIter iter
)
411 CASSERT(iter
!= mSessionCache
.end());
412 SessionCacheEntry
*entry
= *iter
;
413 SessionCacheIter nextIter
= mSessionCache
.erase(iter
);
418 /* the single global thing */
419 static ModuleNexus
<SessionCache
> gSessionCache
;
422 static void dumpAllCache()
425 SessionCacheType
&smap
= gSessionCache().sessMap();
427 printf("Contents of sessionCache:\n");
428 for(it
= smap
.begin(); it
!= smap
.end(); it
++) {
429 SessionCacheEntry
*entry
= *it
;
430 cachePrint(&entry
->key(), &entry
->sessionData());
433 #endif /* DUMP_ALL_CACHE */
436 * Store opaque sessionData, associated with opaque sessionKey.
438 SSLErr
sslAddSession (
439 const SSLBuffer sessionKey
,
440 const SSLBuffer sessionData
)
444 serr
= gSessionCache().addEntry(sessionKey
, sessionData
);
447 serr
= SSLUnsupportedErr
;
454 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
456 SSLErr
sslGetSession (
457 const SSLBuffer sessionKey
,
458 SSLBuffer
*sessionData
)
462 serr
= gSessionCache().lookupEntry(sessionKey
, sessionData
);
465 serr
= SSLSessionNotFoundErr
;
467 cprintf(("\nsslGetSession(%d, %p): %d\n", (int)sessionKey
.length
, sessionKey
.data
,
469 if(serr
== SSLNoErr
) {
470 cachePrint(&sessionKey
, sessionData
);
473 cachePrint(&sessionKey
, NULL
);
479 SSLErr
sslDeleteSession (
480 const SSLBuffer sessionKey
)
484 serr
= gSessionCache().deleteEntry(sessionKey
);
487 serr
= SSLSessionNotFoundErr
;
492 /* cleanup up session cache, deleting stale entries. */
493 SSLErr
sslCleanupSession ()
495 SSLErr serr
= SSLNoErr
;
496 bool moreToGo
= false;
498 moreToGo
= gSessionCache().cleanup();
501 serr
= SSLSessionNotFoundErr
;
503 /* Possible TBD: if moreToGo, schedule a timed callback to this function */