]> git.saurik.com Git - apple/security.git/blame - SecureTransport/appleSession.cpp
Security-176.tar.gz
[apple/security.git] / SecureTransport / appleSession.cpp
CommitLineData
29654253
A
1/*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
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
8 * using this file.
9 *
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.
16 */
17
18
19/*
20 File: appleSession.cpp
21
22 Contains: Session storage module, Apple CDSA version.
23
5a719ac8 24 Written by: Doug Mitchell
29654253
A
25
26 Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
27
28*/
29
30/*
29654253
A
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
36 * tendency.
37 *
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
45 * cache).
46 *
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),
52 */
53
54#include "ssl.h"
5a719ac8 55#include "sslMemory.h"
29654253
A
56#include "sslDebug.h"
57#include "appleSession.h"
58
59#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
60
61#include <deque>
62#include <stdexcept>
63#include <Security/threading.h>
64#include <Security/globalizer.h>
65#include <Security/timeflow.h>
66
67/* time-to-live in cache, in seconds */
68#define QUICK_CACHE_TEST 0
69#if QUICK_CACHE_TEST
70#define SESSION_CACHE_TTL ((int)5)
71#else
72#define SESSION_CACHE_TTL ((int)(10 * 60))
73#endif /* QUICK_CACHE_TEST */
74
75#define CACHE_PRINT 0
76#if CACHE_PRINT
29654253
A
77#define DUMP_ALL_CACHE 0
78
79static void cachePrint(
80 const SSLBuffer *key,
81 const SSLBuffer *data)
82{
83 unsigned char *kd = key->data;
84 if(data != NULL) {
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);
90 }
91 else {
92 /* just print key */
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]);
95 }
96}
97#else /* !CACHE_PRINT */
29654253
A
98#define cachePrint(k, d)
99#define DUMP_ALL_CACHE 0
100#endif /* CACHE_PRINT */
101
102#if DUMP_ALL_CACHE
103static void dumpAllCache();
104#else
105#define dumpAllCache()
106#endif
107
108/*
109 * One entry (value) in SessionCache.
110 */
111class SessionCacheEntry {
112public:
113 /*
114 * This constructor, the only one, allocs copies of the key and value
115 * SSLBuffers.
116 */
117 SessionCacheEntry(
118 const SSLBuffer &key,
119 const SSLBuffer &sessionData,
120 const Time::Absolute &expirationTime);
121 ~SessionCacheEntry();
122
123 /* basic lookup/match function */
124 bool matchKey(const SSLBuffer &key) const;
125
126 /* has this expired? */
127 bool isStale(); // calculates "now"
128 bool isStale(const Time::Absolute &now); // when you know it
129
130 /* key/data accessors */
131 SSLBuffer &key() { return mKey; }
132 SSLBuffer &sessionData() { return mSessionData; }
133
134 /* replace existing mSessionData */
5a719ac8 135 OSStatus sessionData(const SSLBuffer &data);
29654253
A
136
137private:
138 SSLBuffer mKey;
139 SSLBuffer mSessionData;
140
141 /* this entry to be removed from session map at this time */
142 Time::Absolute mExpiration;
143};
144
145/*
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.
149 */
150SessionCacheEntry::SessionCacheEntry(
151 const SSLBuffer &key,
152 const SSLBuffer &sessionData,
153 const Time::Absolute &expirationTime)
154 : mExpiration(expirationTime)
155{
5a719ac8 156 OSStatus serr;
29654253 157
5a719ac8 158 serr = SSLCopyBuffer(key, mKey);
29654253
A
159 if(serr) {
160 throw runtime_error("memory error");
161 }
5a719ac8 162 serr = SSLCopyBuffer(sessionData, mSessionData);
29654253
A
163 if(serr) {
164 throw runtime_error("memory error");
165 }
5a719ac8 166 sslLogSessCacheDebug("SessionCacheEntry(buf,buf) this %p", this);
29654253
A
167 mExpiration += Time::Interval(SESSION_CACHE_TTL);
168}
169
170SessionCacheEntry::~SessionCacheEntry()
171{
5a719ac8
A
172 sslLogSessCacheDebug("~SessionCacheEntry() this %p", this);
173 SSLFreeBuffer(mKey, NULL); // no SSLContext
174 SSLFreeBuffer(mSessionData, NULL);
29654253
A
175}
176
177/* basic lookup/match function */
178bool SessionCacheEntry::matchKey(const SSLBuffer &key) const
179{
180 if(key.length != mKey.length) {
181 return false;
182 }
183 if((key.data == NULL) || (mKey.data == NULL)) {
184 return false;
185 }
186 return (memcmp(key.data, mKey.data, mKey.length) == 0);
187}
188
189/* has this expired? */
190bool SessionCacheEntry::isStale()
191{
192 return isStale(Time::now());
193}
194
195bool SessionCacheEntry::isStale(const Time::Absolute &now)
196{
197 if(now > mExpiration) {
198 return true;
199 }
200 else {
201 return false;
202 }
203}
204
205/* replace existing mSessionData */
5a719ac8 206OSStatus SessionCacheEntry::sessionData(
29654253
A
207 const SSLBuffer &data)
208{
5a719ac8
A
209 SSLFreeBuffer(mSessionData, NULL);
210 return SSLCopyBuffer(data, mSessionData);
29654253
A
211}
212
213/* Types for the actual deque and its iterator */
214typedef std::deque<SessionCacheEntry *> SessionCacheType;
215typedef SessionCacheType::iterator SessionCacheIter;
216
217/*
218 * Global map and associated state. We maintain a singleton of this.
219 */
220class SessionCache
221{
222public:
223 SessionCache()
224 : mTimeToLive(SESSION_CACHE_TTL) {}
225 ~SessionCache();
226
227 /* these correspond to the C functions exported by this file */
5a719ac8 228 OSStatus addEntry(
29654253
A
229 const SSLBuffer sessionKey,
230 const SSLBuffer sessionData);
5a719ac8 231 OSStatus lookupEntry(
29654253
A
232 const SSLBuffer sessionKey,
233 SSLBuffer *sessionData);
5a719ac8 234 OSStatus deleteEntry(
29654253
A
235 const SSLBuffer sessionKey);
236
237 /* cleanup, delete stale entries */
238 bool cleanup();
239 SessionCacheType &sessMap() { return mSessionCache; }
240
241private:
242 SessionCacheIter lookupPriv(
243 const SSLBuffer *sessionKey);
244 void deletePriv(
245 const SSLBuffer *sessionKey);
246 SessionCacheIter deletePriv(
247 SessionCacheIter iter);
248 SessionCacheType mSessionCache;
249 Mutex mSessionLock;
250 const Time::Interval mTimeToLive;
251};
252
253SessionCache::~SessionCache()
254{
255 /* free all entries */
256 StLock<Mutex> _(mSessionLock);
257 for(SessionCacheIter iter = mSessionCache.begin(); iter != mSessionCache.end(); ) {
258 iter = deletePriv(iter);
259 }
260}
261
262/* these three correspond to the C functions exported by this file */
5a719ac8 263OSStatus SessionCache::addEntry(
29654253
A
264 const SSLBuffer sessionKey,
265 const SSLBuffer sessionData)
266{
267 StLock<Mutex> _(mSessionLock);
268
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)) {
277 /*
278 * These usually match, and a memcmp is a lot cheaper than
279 * a malloc and a free, hence this quick optimization.....
280 */
5a719ac8
A
281 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
282 "entry = %p", existEntry);
283 return noErr;
29654253
A
284 }
285 else {
5a719ac8
A
286 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
287 "entry = %p", existEntry);
29654253
A
288 return existEntry->sessionData(sessionData);
289 }
290 }
291
292 /* this allocs new copy of incoming sessionKey and sessionData */
293 SessionCacheEntry *entry = new SessionCacheEntry(sessionKey,
294 sessionData,
295 Time::now() + mTimeToLive);
296
5a719ac8 297 sslLogSessCacheDebug("SessionCache::addEntry %p", entry);
29654253
A
298 cachePrint(&sessionKey, &sessionData);
299 dumpAllCache();
300
301 /* add to head of queue for LIFO caching */
302 mSessionCache.push_front(entry);
5a719ac8
A
303 assert(lookupPriv(&sessionKey) != mSessionCache.end());
304 return noErr;
29654253
A
305}
306
5a719ac8 307OSStatus SessionCache::lookupEntry(
29654253
A
308 const SSLBuffer sessionKey,
309 SSLBuffer *sessionData)
310{
311 StLock<Mutex> _(mSessionLock);
312
313 SessionCacheIter existIter = lookupPriv(&sessionKey);
314 if(existIter == mSessionCache.end()) {
5a719ac8 315 return errSSLSessionNotFound;
29654253
A
316 }
317 SessionCacheEntry *entry = *existIter;
318 if(entry->isStale()) {
5a719ac8
A
319 sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
320 "entry, deleting", entry);
29654253
A
321 cachePrint(&sessionKey, &entry->sessionData());
322 deletePriv(existIter);
5a719ac8 323 return errSSLSessionNotFound;
29654253
A
324 }
325 /* alloc/copy sessionData from existing entry (caller must free) */
5a719ac8 326 return SSLCopyBuffer(entry->sessionData(), *sessionData);
29654253
A
327}
328
5a719ac8 329OSStatus SessionCache::deleteEntry(
29654253
A
330 const SSLBuffer sessionKey)
331{
332 StLock<Mutex> _(mSessionLock);
333 deletePriv(&sessionKey);
5a719ac8 334 return noErr;
29654253
A
335}
336
337/* cleanup, delete stale entries */
338bool SessionCache::cleanup()
339{
340 StLock<Mutex> _(mSessionLock);
341 bool brtn = false;
342 Time::Absolute rightNow = Time::now();
343 SessionCacheIter iter;
344
345 for(iter = mSessionCache.begin(); iter != mSessionCache.end(); ) {
346 SessionCacheEntry *entry = *iter;
347 if(entry->isStale(rightNow)) {
5a719ac8
A
348 #ifndef DEBUG
349 sslLogSessCacheDebug("...SessionCache::cleanup: deleting "
350 "cached session (%p)", entry);
351 cachePrint(&entry->key(), &entry->sessionData());
29654253
A
352 #endif
353 iter = deletePriv(iter);
354 }
355 else {
356 iter++;
357 /* we're leaving one in the map */
358 brtn = true;
359 }
360 }
361 return brtn;
362}
363
364/* private methods, mSessionLock held on entry and exit */
365SessionCacheIter SessionCache::lookupPriv(
366 const SSLBuffer *sessionKey)
367{
368 SessionCacheIter it;
369
370 for(it = mSessionCache.begin(); it != mSessionCache.end(); it++) {
371 SessionCacheEntry *entry = *it;
372 if(entry->matchKey(*sessionKey)) {
373 return it;
374 }
375 }
376 /* returning map.end() */
377 return it;
378}
379
380void SessionCache::deletePriv(
381 const SSLBuffer *sessionKey)
382{
383 SessionCacheIter iter = lookupPriv(sessionKey);
384 if(iter != mSessionCache.end()) {
385 /*
386 * delete from map
387 * free underlying SSLBuffer.data pointers
388 * destruct the stored map entry
389 */
390 #if CACHE_PRINT
391 SessionCacheEntry *entry = *iter;
5a719ac8 392 sslLogSessCacheDebug("SessionCache::deletePriv %p", entry);
29654253
A
393 cachePrint(sessionKey, &entry->sessionData());
394 dumpAllCache();
395 #endif
396 deletePriv(iter);
397 }
5a719ac8 398 assert(lookupPriv(sessionKey) == mSessionCache.end());
29654253
A
399}
400
401/* common erase, given a SessionCacheIter; returns next iter */
402SessionCacheIter SessionCache::deletePriv(
403 SessionCacheIter iter)
404{
5a719ac8 405 assert(iter != mSessionCache.end());
29654253
A
406 SessionCacheEntry *entry = *iter;
407 SessionCacheIter nextIter = mSessionCache.erase(iter);
408 delete entry;
409 return nextIter;
410}
411
412/* the single global thing */
413static ModuleNexus<SessionCache> gSessionCache;
414
415#if DUMP_ALL_CACHE
416static void dumpAllCache()
417{
418 SessionCacheIter it;
419 SessionCacheType &smap = gSessionCache().sessMap();
420
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());
425 }
426}
427#endif /* DUMP_ALL_CACHE */
428
429/*
430 * Store opaque sessionData, associated with opaque sessionKey.
431 */
5a719ac8 432OSStatus sslAddSession (
29654253
A
433 const SSLBuffer sessionKey,
434 const SSLBuffer sessionData)
435{
5a719ac8 436 OSStatus serr;
29654253
A
437 try {
438 serr = gSessionCache().addEntry(sessionKey, sessionData);
439 }
440 catch(...) {
5a719ac8 441 serr = unimpErr;
29654253
A
442 }
443 dumpAllCache();
444 return serr;
445}
446
447/*
448 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
449 */
5a719ac8 450OSStatus sslGetSession (
29654253
A
451 const SSLBuffer sessionKey,
452 SSLBuffer *sessionData)
453{
5a719ac8 454 OSStatus serr;
29654253
A
455 try {
456 serr = gSessionCache().lookupEntry(sessionKey, sessionData);
457 }
458 catch(...) {
5a719ac8 459 serr = errSSLSessionNotFound;
29654253 460 }
5a719ac8
A
461 sslLogSessCacheDebug("sslGetSession(%d, %p): %ld",
462 (int)sessionKey.length, sessionKey.data,
463 serr);
464 if(serr == noErr) {
29654253
A
465 cachePrint(&sessionKey, sessionData);
466 }
467 else {
468 cachePrint(&sessionKey, NULL);
469 }
470 dumpAllCache();
471 return serr;
472}
473
5a719ac8 474OSStatus sslDeleteSession (
29654253
A
475 const SSLBuffer sessionKey)
476{
5a719ac8 477 OSStatus serr;
29654253
A
478 try {
479 serr = gSessionCache().deleteEntry(sessionKey);
480 }
481 catch(...) {
5a719ac8 482 serr = errSSLSessionNotFound;
29654253
A
483 }
484 return serr;
485}
486
487/* cleanup up session cache, deleting stale entries. */
5a719ac8 488OSStatus sslCleanupSession ()
29654253 489{
5a719ac8 490 OSStatus serr = noErr;
29654253
A
491 bool moreToGo = false;
492 try {
493 moreToGo = gSessionCache().cleanup();
494 }
495 catch(...) {
5a719ac8 496 serr = errSSLSessionNotFound;
29654253
A
497 }
498 /* Possible TBD: if moreToGo, schedule a timed callback to this function */
499 return serr;
500}