]>
Commit | Line | Data |
---|---|---|
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 | ||
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 */ | |
29654253 A |
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 */ | |
5a719ac8 | 135 | OSStatus sessionData(const SSLBuffer &data); |
29654253 A |
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 | { | |
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 | ||
170 | SessionCacheEntry::~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 */ | |
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 */ | |
5a719ac8 | 206 | OSStatus 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 */ | |
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 */ | |
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 | ||
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 */ | |
5a719ac8 | 263 | OSStatus 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 | 307 | OSStatus 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 | 329 | OSStatus 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 */ | |
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)) { | |
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 */ | |
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; | |
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 */ | |
402 | SessionCacheIter 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 */ | |
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 | */ | |
5a719ac8 | 432 | OSStatus 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 | 450 | OSStatus 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 | 474 | OSStatus 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 | 488 | OSStatus 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 | } |