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