]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_ssl/lib/appleSession.c
Security-57336.10.29.tar.gz
[apple/security.git] / OSX / libsecurity_ssl / lib / appleSession.c
1 /*
2 * Copyright (c) 1999-2001,2005-2008,2010-2014 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 #include <utilities/SecIOFormat.h>
62
63 /* default time-to-live in cache, in seconds */
64 #define QUICK_CACHE_TEST 0
65 #if QUICK_CACHE_TEST
66 #define SESSION_CACHE_TTL ((CFTimeInterval)5)
67 #else
68 #define SESSION_CACHE_TTL ((CFTimeInterval)(10 * 60))
69 #endif /* QUICK_CACHE_TEST */
70
71 #define CACHE_PRINT 0
72 #if CACHE_PRINT
73 #define DUMP_ALL_CACHE 0
74
75 static void cachePrint(
76 const void *entry,
77 const SSLBuffer *key,
78 const SSLBuffer *data)
79 {
80 printf("entry: %p ", entry);
81 unsigned char *kd = key->data;
82 if(data != NULL) {
83 unsigned char *dd = data->data;
84 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X"
85 " data: %02X%02X%02X%02X... (len %d)\n",
86 kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7],
87 dd[0],dd[1],dd[2],dd[3], (unsigned)data->length);
88 }
89 else {
90 /* just print key */
91 printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n",
92 kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7]);
93 }
94 }
95 #else /* !CACHE_PRINT */
96 #define cachePrint(e, k, d)
97 #define DUMP_ALL_CACHE 0
98 #endif /* CACHE_PRINT */
99
100 #if DUMP_ALL_CACHE
101 static void dumpAllCache(void);
102 #else
103 #define dumpAllCache()
104 #endif
105
106 /*
107 * One entry (value) in SessionCache.
108 */
109 typedef struct SessionCacheEntry SessionCacheEntry;
110 struct SessionCacheEntry {
111 /* Linked list of SessionCacheEntries. */
112 SessionCacheEntry *next;
113
114 SSLBuffer mKey;
115 SSLBuffer mSessionData;
116
117 /* this entry to be removed from session map at this time */
118 CFAbsoluteTime mExpiration;
119 };
120
121 /*
122 * Note: the caller passes in the expiration time solely to accomodate the
123 * instantiation of a single const Time::Interval for use in calculating
124 * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache.
125 */
126 /*
127 * This constructor, the only one, allocs copies of the key and value
128 * SSLBuffers.
129 */
130 static SessionCacheEntry *SessionCacheEntryCreate(
131 const SSLBuffer *key,
132 const SSLBuffer *sessionData,
133 CFAbsoluteTime expirationTime)
134 {
135 OSStatus serr;
136
137 SessionCacheEntry *entry = sslMalloc(sizeof(SessionCacheEntry));
138 if (entry == NULL)
139 return NULL;
140
141 serr = SSLCopyBuffer(key, &entry->mKey);
142 if(serr) {
143 sslFree (entry);
144 return NULL;
145 }
146 serr = SSLCopyBuffer(sessionData, &entry->mSessionData);
147 if(serr) {
148 SSLFreeBuffer(&entry->mKey);
149 sslFree (entry);
150 return NULL;
151 }
152
153 sslLogSessCacheDebug("SessionCacheEntryCreate(buf,buf) %p", entry);
154 entry->mExpiration = expirationTime;
155
156 return entry;
157 }
158
159 static void SessionCacheEntryDelete(SessionCacheEntry *entry)
160 {
161 sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry);
162 SSLFreeBuffer(&entry->mKey); // no SSLContext
163 SSLFreeBuffer(&entry->mSessionData);
164 sslFree(entry);
165 }
166
167 /* basic lookup/match function */
168 static bool SessionCacheEntryMatchKey(SessionCacheEntry *entry,
169 const SSLBuffer *key)
170 {
171 if(key->length != entry->mKey.length) {
172 return false;
173 }
174 if((key->data == NULL) || (entry->mKey.data == NULL)) {
175 return false;
176 }
177 return (memcmp(key->data, entry->mKey.data, entry->mKey.length) == 0);
178 }
179
180 static bool SessionCacheEntryIsStale(SessionCacheEntry *entry,
181 CFAbsoluteTime now)
182 {
183 return now > entry->mExpiration;
184 }
185
186 /* has this expired? */
187 static bool SessionCacheEntryIsStaleNow(SessionCacheEntry *entry)
188 {
189 return SessionCacheEntryIsStale(entry, CFAbsoluteTimeGetCurrent());
190 }
191
192 /* replace existing mSessionData */
193 static OSStatus SessionCacheEntrySetSessionData(SessionCacheEntry *entry,
194 const SSLBuffer *data)
195 {
196 SSLFreeBuffer(&entry->mSessionData);
197 return SSLCopyBuffer(data, &entry->mSessionData);
198 }
199
200 /*
201 * Global list of sessions and associated state. We maintain a singleton of
202 * this.
203 */
204 typedef struct SessionCache {
205 SessionCacheEntry *head;
206 CFTimeInterval mTimeToLive; /* default time-to-live in seconds */
207 } SessionCache;
208
209 static pthread_mutex_t gSessionCacheLock = PTHREAD_MUTEX_INITIALIZER;
210 static SessionCache *gSessionCache = NULL;
211
212 static void SessionCacheInit(void) {
213 gSessionCache = sslMalloc(sizeof(SessionCache));
214 gSessionCache->head = NULL;
215 gSessionCache->mTimeToLive = SESSION_CACHE_TTL;
216 }
217
218 static SessionCache *SessionCacheGetLockedInstance(void) {
219 pthread_mutex_lock(&gSessionCacheLock);
220 if (!gSessionCache) {
221 /* We could use pthread_once, but we already have a mutex for other
222 reasons. */
223 SessionCacheInit();
224 }
225
226 return gSessionCache;
227 }
228
229 /* these three correspond to the C functions exported by this file */
230 static OSStatus SessionCacheAddEntry(
231 SessionCache *cache,
232 const SSLBuffer *sessionKey,
233 const SSLBuffer *sessionData,
234 uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */
235 {
236 SessionCacheEntry *entry = NULL;
237 SessionCacheEntry **current;
238 CFTimeInterval expireTime;
239
240 for (current = &(cache->head); *current; current = &((*current)->next)) {
241 entry = *current;
242 if (SessionCacheEntryMatchKey(entry, sessionKey)) {
243 /* cache hit - just update this entry's sessionData if necessary */
244 /* Note we leave expiration time and position in queue unchanged
245 - OK? */
246 /* What if the entry has already expired? */
247 if((entry->mSessionData.length == sessionData->length) &&
248 (memcmp(entry->mSessionData.data, sessionData->data,
249 sessionData->length) == 0)) {
250 /*
251 * These usually match, and a memcmp is a lot cheaper than
252 * a malloc and a free, hence this quick optimization.....
253 */
254 sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
255 "entry = %p", entry);
256 return errSecSuccess;
257 }
258 else {
259 sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
260 "entry = %p", entry);
261 return SessionCacheEntrySetSessionData(entry, sessionData);
262 }
263 }
264 }
265
266 expireTime = CFAbsoluteTimeGetCurrent();
267 if(timeToLive) {
268 /* caller-specified */
269 expireTime += (CFTimeInterval)timeToLive;
270 }
271 else {
272 /* default */
273 expireTime += cache->mTimeToLive;
274 }
275 /* this allocs new copy of incoming sessionKey and sessionData */
276 entry = SessionCacheEntryCreate(sessionKey, sessionData, expireTime);
277
278 sslLogSessCacheDebug("SessionCache::addEntry %p", entry);
279 cachePrint(entry, sessionKey, sessionData);
280 dumpAllCache();
281
282 /* add to head of queue for LIFO caching */
283 entry->next = cache->head;
284 cache->head = entry;
285
286 return errSecSuccess;
287 }
288
289 static OSStatus SessionCacheLookupEntry(
290 SessionCache *cache,
291 const SSLBuffer *sessionKey,
292 SSLBuffer *sessionData)
293 {
294 SessionCacheEntry *entry = NULL;
295 SessionCacheEntry **current;
296 for (current = &(cache->head); *current; current = &((*current)->next)) {
297 entry = *current;
298 if (SessionCacheEntryMatchKey(entry, sessionKey))
299 break;
300 }
301
302 if (*current == NULL)
303 return errSSLSessionNotFound;
304
305 if (SessionCacheEntryIsStaleNow(entry)) {
306 sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
307 "entry, deleting; current %p, entry->next %p",
308 entry, current, entry->next);
309 cachePrint(entry, sessionKey, &entry->mSessionData);
310 *current = entry->next;
311 SessionCacheEntryDelete(entry);
312 return errSSLSessionNotFound;
313 }
314
315 /* alloc/copy sessionData from existing entry (caller must free) */
316 return SSLCopyBuffer(&entry->mSessionData, sessionData);
317 }
318
319 static OSStatus SessionCacheDeleteEntry(
320 SessionCache *cache,
321 const SSLBuffer *sessionKey)
322 {
323 SessionCacheEntry **current;
324
325 for (current = &(cache->head); *current; current = &((*current)->next)) {
326 SessionCacheEntry *entry = *current;
327 if (SessionCacheEntryMatchKey(entry, sessionKey)) {
328 #ifndef DEBUG
329 sslLogSessCacheDebug("...SessionCacheDeleteEntry: deleting "
330 "cached session (%p)", entry);
331 cachePrint(entry, &entry->mKey, &entry->mSessionData);
332 #endif
333 *current = entry->next;
334 SessionCacheEntryDelete(entry);
335 return errSecSuccess;
336 }
337 }
338
339 return errSecSuccess;
340 }
341
342 /* cleanup, delete stale entries */
343 static bool SessionCacheCleanup(SessionCache *cache)
344 {
345 bool brtn = false;
346 CFAbsoluteTime rightNow = CFAbsoluteTimeGetCurrent();
347 SessionCacheEntry **current;
348
349 for (current = &(cache->head); *current;) {
350 SessionCacheEntry *entry = *current;
351 if(SessionCacheEntryIsStale(entry, rightNow)) {
352 #ifndef DEBUG
353 sslLogSessCacheDebug("...SessionCacheCleanup: deleting "
354 "cached session (%p)", entry);
355 cachePrint(entry, &entry->mKey, &entry->mSessionData);
356 #endif
357 *current = entry->next;
358 SessionCacheEntryDelete(entry);
359 }
360 else {
361 current = &((*current)->next);
362 /* we're leaving one in the map */
363 brtn = true;
364 }
365 }
366 return brtn;
367 }
368
369 #if DUMP_ALL_CACHE
370 static void dumpAllCache(void)
371 {
372 SessionCache *cache = gSessionCache;
373 SessionCacheEntry *entry;
374
375 printf("Contents of sessionCache:\n");
376 for(entry = cache->head; entry; entry = entry->next) {
377 cachePrint(entry, &entry->mKey, &entry->mSessionData);
378 }
379 }
380 #endif /* DUMP_ALL_CACHE */
381
382 /*
383 * Store opaque sessionData, associated with opaque sessionKey.
384 */
385 OSStatus sslAddSession (
386 const SSLBuffer sessionKey,
387 const SSLBuffer sessionData,
388 uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */
389 {
390 SessionCache *cache = SessionCacheGetLockedInstance();
391 OSStatus serr;
392 if (!cache)
393 serr = errSSLSessionNotFound;
394 else
395 {
396 serr = SessionCacheAddEntry(cache, &sessionKey, &sessionData, timeToLive);
397
398 dumpAllCache();
399 }
400
401 pthread_mutex_unlock(&gSessionCacheLock);
402 return serr;
403 }
404
405 /*
406 * Given an opaque sessionKey, alloc & retrieve associated sessionData.
407 */
408 OSStatus sslCopySession (
409 const SSLBuffer sessionKey,
410 SSLBuffer *sessionData)
411 {
412 SessionCache *cache = SessionCacheGetLockedInstance();
413 OSStatus serr;
414 if (!cache)
415 serr = errSSLSessionNotFound;
416 else
417 {
418 serr = SessionCacheLookupEntry(cache, &sessionKey, sessionData);
419
420 sslLogSessCacheDebug("sslGetSession(%d, %p): %d",
421 (int)sessionKey.length, sessionKey.data,
422 (int)serr);
423 if(!serr) {
424 cachePrint(NULL, &sessionKey, sessionData);
425 }
426 else {
427 cachePrint(NULL, &sessionKey, NULL);
428 }
429 dumpAllCache();
430 }
431
432 pthread_mutex_unlock(&gSessionCacheLock);
433
434 return serr;
435 }
436
437 OSStatus sslDeleteSession (
438 const SSLBuffer sessionKey)
439 {
440 SessionCache *cache = SessionCacheGetLockedInstance();
441 OSStatus serr;
442 if (!cache)
443 serr = errSSLSessionNotFound;
444 else
445 {
446 serr = SessionCacheDeleteEntry(cache, &sessionKey);
447 }
448
449 pthread_mutex_unlock(&gSessionCacheLock);
450 return serr;
451 }
452
453 /* cleanup up session cache, deleting stale entries. */
454 OSStatus sslCleanupSession(void)
455 {
456 SessionCache *cache = SessionCacheGetLockedInstance();
457 OSStatus serr = errSecSuccess;
458 bool moreToGo = false;
459
460 if (!cache)
461 serr = errSSLSessionNotFound;
462 else
463 {
464 moreToGo = SessionCacheCleanup(cache);
465 }
466 /* Possible TBD: if moreToGo, schedule a timed callback to this function */
467
468 pthread_mutex_unlock(&gSessionCacheLock);
469 return serr;
470 }