]> git.saurik.com Git - apple/security.git/blob - libsecurity_ssl/lib/appleSession.c
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_ssl / lib / appleSession.c
1 /*
2 * Copyright (c) 1999-2001,2005-2008,2010-2012 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * appleSession.c - Session storage module, Apple CDSA version.
26 */
27
28 /*
29 * The current implementation stores sessions in a linked list, a member of a
30 * SessionCache object for which we keep a single global instance. It is
31 * expected that at a given time, only a small number of sessions will be
32 * cached, so the random insertion access provided by a map<> is unnecessary.
33 * New entries are placed in the head of the list, assuming a LIFO usage
34 * tendency.
35 *
36 * Entries in this cache have a time to live of SESSION_CACHE_TTL, currently
37 * ten minutes. Entries are tested for being stale upon lookup; also, the global
38 * sslCleanupSession() tests all entries in the cache, deleting entries which
39 * are stale. This function is currently called whenever an SSLContext is deleted.
40 * The current design does not provide any asynchronous timed callouts to perform
41 * further cache cleanup; it was decided that the thread overhead of this would
42 * outweight the benefits (again assuming a small number of entries in the
43 * cache).
44 *
45 * When a session is added via sslAddSession, and a cache entry already
46 * exists for the specifed key (sessionID), the sessionData for the existing
47 * cache entry is updated with the new sessionData. The entry's expiration
48 * time is unchanged (thus a given session entry can only be used for a finite
49 * time no mattter how often it is re-used),
50 */
51
52 #include "ssl.h"
53 #include "sslMemory.h"
54 #include "sslDebug.h"
55 #include "appleSession.h"
56
57 #include <CoreFoundation/CFDate.h>
58 #include <pthread.h>
59 #include <string.h>
60
61 /* default time-to-live in cache, in seconds */
62 #define QUICK_CACHE_TEST 0
63 #if QUICK_CACHE_TEST
64 #define SESSION_CACHE_TTL ((CFTimeInterval)5)
65 #else
66 #define SESSION_CACHE_TTL ((CFTimeInterval)(10 * 60))
67 #endif /* QUICK_CACHE_TEST */
68
69 #define CACHE_PRINT 0
70 #if CACHE_PRINT
71 #define DUMP_ALL_CACHE 0
72
73 static void cachePrint(
74 const void *entry,
75 const SSLBuffer *key,
76 const SSLBuffer *data)
77 {
78 printf("entry: %p ", entry);
79 unsigned char *kd = key->data;
80 if(data != NULL) {
81 unsigned char *dd = data->data;
82 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X"
83 " data: %02X%02X%02X%02X... (len %d)\n",
84 kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7],
85 dd[0],dd[1],dd[2],dd[3], (unsigned)data->length);
86 }
87 else {
88 /* just print key */
89 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n",
90 kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7]);
91 }
92 }
93 #else /* !CACHE_PRINT */
94 #define cachePrint(e, k, d)
95 #define DUMP_ALL_CACHE 0
96 #endif /* CACHE_PRINT */
97
98 #if DUMP_ALL_CACHE
99 static void dumpAllCache(void);
100 #else
101 #define dumpAllCache()
102 #endif
103
104 /*
105 * One entry (value) in SessionCache.
106 */
107 typedef struct SessionCacheEntry SessionCacheEntry;
108 struct SessionCacheEntry {
109 /* Linked list of SessionCacheEntries. */
110 SessionCacheEntry *next;
111
112 SSLBuffer mKey;
113 SSLBuffer mSessionData;
114
115 /* this entry to be removed from session map at this time */
116 CFAbsoluteTime mExpiration;
117 };
118
119 /*
120 * Note: the caller passes in the expiration time solely to accomodate the
121 * instantiation of a single const Time::Interval for use in calculating
122 * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache.
123 */
124 /*
125 * This constructor, the only one, allocs copies of the key and value
126 * SSLBuffers.
127 */
128 static SessionCacheEntry *SessionCacheEntryCreate(
129 const SSLBuffer *key,
130 const SSLBuffer *sessionData,
131 CFAbsoluteTime expirationTime)
132 {
133 OSStatus serr;
134
135 SessionCacheEntry *entry = sslMalloc(sizeof(SessionCacheEntry));
136 if (entry == NULL)
137 return NULL;
138
139 serr = SSLCopyBuffer(key, &entry->mKey);
140 if(serr) {
141 sslFree (entry);
142 return NULL;
143 }
144 serr = SSLCopyBuffer(sessionData, &entry->mSessionData);
145 if(serr) {
146 SSLFreeBuffer(&entry->mKey, NULL);
147 sslFree (entry);
148 return NULL;
149 }
150
151 sslLogSessCacheDebug("SessionCacheEntryCreate(buf,buf) %p", entry);
152 entry->mExpiration = expirationTime;
153
154 return entry;
155 }
156
157 static void SessionCacheEntryDelete(SessionCacheEntry *entry)
158 {
159 sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry);
160 SSLFreeBuffer(&entry->mKey, NULL); // no SSLContext
161 SSLFreeBuffer(&entry->mSessionData, NULL);
162 sslFree(entry);
163 }
164
165 /* basic lookup/match function */
166 static bool SessionCacheEntryMatchKey(SessionCacheEntry *entry,
167 const SSLBuffer *key)
168 {
169 if(key->length != entry->mKey.length) {
170 return false;
171 }
172 if((key->data == NULL) || (entry->mKey.data == NULL)) {
173 return false;
174 }
175 return (memcmp(key->data, entry->mKey.data, entry->mKey.length) == 0);
176 }
177
178 static bool SessionCacheEntryIsStale(SessionCacheEntry *entry,
179 CFAbsoluteTime now)
180 {
181 return now > entry->mExpiration;
182 }
183
184 /* has this expired? */
185 static bool SessionCacheEntryIsStaleNow(SessionCacheEntry *entry)
186 {
187 return SessionCacheEntryIsStale(entry, CFAbsoluteTimeGetCurrent());
188 }
189
190 /* replace existing mSessionData */
191 static OSStatus SessionCacheEntrySetSessionData(SessionCacheEntry *entry,
192 const SSLBuffer *data)
193 {
194 SSLFreeBuffer(&entry->mSessionData, NULL);
195 return SSLCopyBuffer(data, &entry->mSessionData);
196 }
197
198 /*
199 * Global list of sessions and associated state. We maintain a singleton of
200 * this.
201 */
202 typedef struct SessionCache {
203 SessionCacheEntry *head;
204 CFTimeInterval mTimeToLive; /* default time-to-live in seconds */
205 } SessionCache;
206
207 static pthread_mutex_t gSessionCacheLock = PTHREAD_MUTEX_INITIALIZER;
208 static SessionCache *gSessionCache = NULL;
209
210 static void SessionCacheInit(void) {
211 gSessionCache = sslMalloc(sizeof(SessionCache));
212 gSessionCache->head = NULL;
213 gSessionCache->mTimeToLive = SESSION_CACHE_TTL;
214 }
215
216 static SessionCache *SessionCacheGetLockedInstance(void) {
217 pthread_mutex_lock(&gSessionCacheLock);
218 if (!gSessionCache) {
219 /* We could use pthread_once, but we already have a mutex for other
220 reasons. */
221 SessionCacheInit();
222 }
223
224 return gSessionCache;
225 }
226
227 /* these three correspond to the C functions exported by this file */
228 static OSStatus SessionCacheAddEntry(
229 SessionCache *cache,
230 const SSLBuffer *sessionKey,
231 const SSLBuffer *sessionData,
232 uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */
233 {
234 SessionCacheEntry *entry = NULL;
235 SessionCacheEntry **current;
236 CFTimeInterval expireTime;
237
238 for (current = &(cache->head); *current; current = &((*current)->next)) {
239 entry = *current;
240 if (SessionCacheEntryMatchKey(entry, sessionKey)) {
241 /* cache hit - just update this entry's sessionData if necessary */
242 /* Note we leave expiration time and position in queue unchanged
243 - OK? */
244 /* What if the entry has already expired? */
245 if((entry->mSessionData.length == sessionData->length) &&
246 (memcmp(entry->mSessionData.data, sessionData->data,
247 sessionData->length) == 0)) {
248 /*
249 * These usually match, and a memcmp is a lot cheaper than
250 * a malloc and a free, hence this quick optimization.....
251 */
252 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
253 "entry = %p", entry);
254 return noErr;
255 }
256 else {
257 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
258 "entry = %p", entry);
259 return SessionCacheEntrySetSessionData(entry, sessionData);
260 }
261 }
262 }
263
264 expireTime = CFAbsoluteTimeGetCurrent();
265 if(timeToLive) {
266 /* caller-specified */
267 expireTime += (CFTimeInterval)timeToLive;
268 }
269 else {
270 /* default */
271 expireTime += cache->mTimeToLive;
272 }
273 /* this allocs new copy of incoming sessionKey and sessionData */
274 entry = SessionCacheEntryCreate(sessionKey, sessionData, expireTime);
275
276 sslLogSessCacheDebug("SessionCache::addEntry %p", entry);
277 cachePrint(entry, sessionKey, sessionData);
278 dumpAllCache();
279
280 /* add to head of queue for LIFO caching */
281 entry->next = cache->head;
282 cache->head = entry;
283
284 return noErr;
285 }
286
287 static OSStatus SessionCacheLookupEntry(
288 SessionCache *cache,
289 const SSLBuffer *sessionKey,
290 SSLBuffer *sessionData)
291 {
292 SessionCacheEntry *entry = NULL;
293 SessionCacheEntry **current;
294 for (current = &(cache->head); *current; current = &((*current)->next)) {
295 entry = *current;
296 if (SessionCacheEntryMatchKey(entry, sessionKey))
297 break;
298 }
299
300 if (*current == NULL)
301 return errSSLSessionNotFound;
302
303 if (SessionCacheEntryIsStaleNow(entry)) {
304 sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
305 "entry, deleting; current %p, entry->next %p",
306 entry, current, entry->next);
307 cachePrint(entry, sessionKey, &entry->mSessionData);
308 *current = entry->next;
309 SessionCacheEntryDelete(entry);
310 return errSSLSessionNotFound;
311 }
312
313 /* alloc/copy sessionData from existing entry (caller must free) */
314 return SSLCopyBuffer(&entry->mSessionData, sessionData);
315 }
316
317 static OSStatus SessionCacheDeleteEntry(
318 SessionCache *cache,
319 const SSLBuffer *sessionKey)
320 {
321 SessionCacheEntry **current;
322
323 for (current = &(cache->head); *current; current = &((*current)->next)) {
324 SessionCacheEntry *entry = *current;
325 if (SessionCacheEntryMatchKey(entry, sessionKey)) {
326 #ifndef DEBUG
327 sslLogSessCacheDebug("...SessionCacheDeleteEntry: deleting "
328 "cached session (%p)", entry);
329 cachePrint(entry, &entry->mKey, &entry->mSessionData);
330 #endif
331 *current = entry->next;
332 SessionCacheEntryDelete(entry);
333 return noErr;
334 }
335 }
336
337 return noErr;
338 }
339
340 /* cleanup, delete stale entries */
341 static bool SessionCacheCleanup(SessionCache *cache)
342 {
343 bool brtn = false;
344 CFAbsoluteTime rightNow = CFAbsoluteTimeGetCurrent();
345 SessionCacheEntry **current;
346
347 for (current = &(cache->head); *current;) {
348 SessionCacheEntry *entry = *current;
349 if(SessionCacheEntryIsStale(entry, rightNow)) {
350 #ifndef DEBUG
351 sslLogSessCacheDebug("...SessionCacheCleanup: deleting "
352 "cached session (%p)", entry);
353 cachePrint(entry, &entry->mKey, &entry->mSessionData);
354 #endif
355 *current = entry->next;
356 SessionCacheEntryDelete(entry);
357 }
358 else {
359 current = &((*current)->next);
360 /* we're leaving one in the map */
361 brtn = true;
362 }
363 }
364 return brtn;
365 }
366
367 #if DUMP_ALL_CACHE
368 static void dumpAllCache(void)
369 {
370 SessionCache *cache = gSessionCache;
371 SessionCacheEntry *entry;
372
373 printf("Contents of sessionCache:\n");
374 for(entry = cache->head; entry; entry = entry->next) {
375 cachePrint(entry, &entry->mKey, &entry->mSessionData);
376 }
377 }
378 #endif /* DUMP_ALL_CACHE */
379
380 /*
381 * Store opaque sessionData, associated with opaque sessionKey.
382 */
383 OSStatus sslAddSession (
384 const SSLBuffer sessionKey,
385 const SSLBuffer sessionData,
386 uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */
387 {
388 SessionCache *cache = SessionCacheGetLockedInstance();
389 OSStatus serr;
390 if (!cache)
391 serr = errSSLSessionNotFound;
392 else
393 {
394 serr = SessionCacheAddEntry(cache, &sessionKey, &sessionData, timeToLive);
395
396 dumpAllCache();
397 }
398
399 pthread_mutex_unlock(&gSessionCacheLock);
400 return serr;
401 }
402
403 /*
404 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
405 */
406 OSStatus sslGetSession (
407 const SSLBuffer sessionKey,
408 SSLBuffer *sessionData)
409 {
410 SessionCache *cache = SessionCacheGetLockedInstance();
411 OSStatus serr;
412 if (!cache)
413 serr = errSSLSessionNotFound;
414 else
415 {
416 serr = SessionCacheLookupEntry(cache, &sessionKey, sessionData);
417
418 sslLogSessCacheDebug("sslGetSession(%d, %p): %ld",
419 (int)sessionKey.length, sessionKey.data,
420 serr);
421 if(!serr) {
422 cachePrint(NULL, &sessionKey, sessionData);
423 }
424 else {
425 cachePrint(NULL, &sessionKey, NULL);
426 }
427 dumpAllCache();
428 }
429
430 pthread_mutex_unlock(&gSessionCacheLock);
431
432 return serr;
433 }
434
435 OSStatus sslDeleteSession (
436 const SSLBuffer sessionKey)
437 {
438 SessionCache *cache = SessionCacheGetLockedInstance();
439 OSStatus serr;
440 if (!cache)
441 serr = errSSLSessionNotFound;
442 else
443 {
444 serr = SessionCacheDeleteEntry(cache, &sessionKey);
445 }
446
447 pthread_mutex_unlock(&gSessionCacheLock);
448 return serr;
449 }
450
451 /* cleanup up session cache, deleting stale entries. */
452 OSStatus sslCleanupSession(void)
453 {
454 SessionCache *cache = SessionCacheGetLockedInstance();
455 OSStatus serr = noErr;
456 bool moreToGo = false;
457
458 if (!cache)
459 serr = errSSLSessionNotFound;
460 else
461 {
462 moreToGo = SessionCacheCleanup(cache);
463 }
464 /* Possible TBD: if moreToGo, schedule a timed callback to this function */
465
466 pthread_mutex_unlock(&gSessionCacheLock);
467 return serr;
468 }