]> git.saurik.com Git - apple/security.git/blob - SecureTransport/appleSession.cpp
Security-164.1.tar.gz
[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
25
26 Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
27
28 */
29
30 /*
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"
55 #include "sslMemory.h"
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
77 #define DUMP_ALL_CACHE 0
78
79 static 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 */
98 #define cachePrint(k, d)
99 #define DUMP_ALL_CACHE 0
100 #endif /* CACHE_PRINT */
101
102 #if DUMP_ALL_CACHE
103 static void dumpAllCache();
104 #else
105 #define dumpAllCache()
106 #endif
107
108 /*
109 * One entry (value) in SessionCache.
110 */
111 class SessionCacheEntry {
112 public:
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 */
135 OSStatus sessionData(const SSLBuffer &data);
136
137 private:
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 */
150 SessionCacheEntry::SessionCacheEntry(
151 const SSLBuffer &key,
152 const SSLBuffer &sessionData,
153 const Time::Absolute &expirationTime)
154 : mExpiration(expirationTime)
155 {
156 OSStatus serr;
157
158 serr = SSLCopyBuffer(key, mKey);
159 if(serr) {
160 throw runtime_error("memory error");
161 }
162 serr = SSLCopyBuffer(sessionData, mSessionData);
163 if(serr) {
164 throw runtime_error("memory error");
165 }
166 sslLogSessCacheDebug("SessionCacheEntry(buf,buf) this %p", this);
167 mExpiration += Time::Interval(SESSION_CACHE_TTL);
168 }
169
170 SessionCacheEntry::~SessionCacheEntry()
171 {
172 sslLogSessCacheDebug("~SessionCacheEntry() this %p", this);
173 SSLFreeBuffer(mKey, NULL); // no SSLContext
174 SSLFreeBuffer(mSessionData, NULL);
175 }
176
177 /* basic lookup/match function */
178 bool 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? */
190 bool SessionCacheEntry::isStale()
191 {
192 return isStale(Time::now());
193 }
194
195 bool 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 */
206 OSStatus SessionCacheEntry::sessionData(
207 const SSLBuffer &data)
208 {
209 SSLFreeBuffer(mSessionData, NULL);
210 return SSLCopyBuffer(data, mSessionData);
211 }
212
213 /* Types for the actual deque and its iterator */
214 typedef std::deque<SessionCacheEntry *> SessionCacheType;
215 typedef SessionCacheType::iterator SessionCacheIter;
216
217 /*
218 * Global map and associated state. We maintain a singleton of this.
219 */
220 class SessionCache
221 {
222 public:
223 SessionCache()
224 : mTimeToLive(SESSION_CACHE_TTL) {}
225 ~SessionCache();
226
227 /* these correspond to the C functions exported by this file */
228 OSStatus addEntry(
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);
236
237 /* cleanup, delete stale entries */
238 bool cleanup();
239 SessionCacheType &sessMap() { return mSessionCache; }
240
241 private:
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
253 SessionCache::~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 */
263 OSStatus SessionCache::addEntry(
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 */
281 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
282 "entry = %p", existEntry);
283 return noErr;
284 }
285 else {
286 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
287 "entry = %p", existEntry);
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
297 sslLogSessCacheDebug("SessionCache::addEntry %p", entry);
298 cachePrint(&sessionKey, &sessionData);
299 dumpAllCache();
300
301 /* add to head of queue for LIFO caching */
302 mSessionCache.push_front(entry);
303 assert(lookupPriv(&sessionKey) != mSessionCache.end());
304 return noErr;
305 }
306
307 OSStatus SessionCache::lookupEntry(
308 const SSLBuffer sessionKey,
309 SSLBuffer *sessionData)
310 {
311 StLock<Mutex> _(mSessionLock);
312
313 SessionCacheIter existIter = lookupPriv(&sessionKey);
314 if(existIter == mSessionCache.end()) {
315 return errSSLSessionNotFound;
316 }
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;
324 }
325 /* alloc/copy sessionData from existing entry (caller must free) */
326 return SSLCopyBuffer(entry->sessionData(), *sessionData);
327 }
328
329 OSStatus SessionCache::deleteEntry(
330 const SSLBuffer sessionKey)
331 {
332 StLock<Mutex> _(mSessionLock);
333 deletePriv(&sessionKey);
334 return noErr;
335 }
336
337 /* cleanup, delete stale entries */
338 bool 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)) {
348 #ifndef DEBUG
349 sslLogSessCacheDebug("...SessionCache::cleanup: deleting "
350 "cached session (%p)", entry);
351 cachePrint(&entry->key(), &entry->sessionData());
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 */
365 SessionCacheIter 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
380 void 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;
392 sslLogSessCacheDebug("SessionCache::deletePriv %p", entry);
393 cachePrint(sessionKey, &entry->sessionData());
394 dumpAllCache();
395 #endif
396 deletePriv(iter);
397 }
398 assert(lookupPriv(sessionKey) == mSessionCache.end());
399 }
400
401 /* common erase, given a SessionCacheIter; returns next iter */
402 SessionCacheIter SessionCache::deletePriv(
403 SessionCacheIter iter)
404 {
405 assert(iter != mSessionCache.end());
406 SessionCacheEntry *entry = *iter;
407 SessionCacheIter nextIter = mSessionCache.erase(iter);
408 delete entry;
409 return nextIter;
410 }
411
412 /* the single global thing */
413 static ModuleNexus<SessionCache> gSessionCache;
414
415 #if DUMP_ALL_CACHE
416 static 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 */
432 OSStatus sslAddSession (
433 const SSLBuffer sessionKey,
434 const SSLBuffer sessionData)
435 {
436 OSStatus serr;
437 try {
438 serr = gSessionCache().addEntry(sessionKey, sessionData);
439 }
440 catch(...) {
441 serr = unimpErr;
442 }
443 dumpAllCache();
444 return serr;
445 }
446
447 /*
448 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
449 */
450 OSStatus sslGetSession (
451 const SSLBuffer sessionKey,
452 SSLBuffer *sessionData)
453 {
454 OSStatus serr;
455 try {
456 serr = gSessionCache().lookupEntry(sessionKey, sessionData);
457 }
458 catch(...) {
459 serr = errSSLSessionNotFound;
460 }
461 sslLogSessCacheDebug("sslGetSession(%d, %p): %ld",
462 (int)sessionKey.length, sessionKey.data,
463 serr);
464 if(serr == noErr) {
465 cachePrint(&sessionKey, sessionData);
466 }
467 else {
468 cachePrint(&sessionKey, NULL);
469 }
470 dumpAllCache();
471 return serr;
472 }
473
474 OSStatus sslDeleteSession (
475 const SSLBuffer sessionKey)
476 {
477 OSStatus serr;
478 try {
479 serr = gSessionCache().deleteEntry(sessionKey);
480 }
481 catch(...) {
482 serr = errSSLSessionNotFound;
483 }
484 return serr;
485 }
486
487 /* cleanup up session cache, deleting stale entries. */
488 OSStatus sslCleanupSession ()
489 {
490 OSStatus serr = noErr;
491 bool moreToGo = false;
492 try {
493 moreToGo = gSessionCache().cleanup();
494 }
495 catch(...) {
496 serr = errSSLSessionNotFound;
497 }
498 /* Possible TBD: if moreToGo, schedule a timed callback to this function */
499 return serr;
500 }