]> git.saurik.com Git - apple/security.git/blame - OSX/libsecurity_ssl/security_ssl/appleSession.c
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / libsecurity_ssl / security_ssl / appleSession.c
CommitLineData
b1ab9ed8 1/*
d8f41ccd 2 * Copyright (c) 1999-2001,2005-2008,2010-2014 Apple Inc. All Rights Reserved.
b1ab9ed8
A
3 *
4 * @APPLE_LICENSE_HEADER_START@
d8f41ccd 5 *
b1ab9ed8
A
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.
d8f41ccd 12 *
b1ab9ed8
A
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.
d8f41ccd 20 *
b1ab9ed8
A
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
427c49bc
A
61#include <utilities/SecIOFormat.h>
62
b1ab9ed8
A
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
75static 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
101static void dumpAllCache(void);
102#else
103#define dumpAllCache()
104#endif
105
106/*
107 * One entry (value) in SessionCache.
108 */
109typedef struct SessionCacheEntry SessionCacheEntry;
110struct 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 */
130static 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) {
427c49bc 148 SSLFreeBuffer(&entry->mKey);
b1ab9ed8
A
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
159static void SessionCacheEntryDelete(SessionCacheEntry *entry)
160{
161 sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry);
427c49bc
A
162 SSLFreeBuffer(&entry->mKey); // no SSLContext
163 SSLFreeBuffer(&entry->mSessionData);
b1ab9ed8
A
164 sslFree(entry);
165}
166
167/* basic lookup/match function */
168static 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
180static bool SessionCacheEntryIsStale(SessionCacheEntry *entry,
181 CFAbsoluteTime now)
182{
183 return now > entry->mExpiration;
184}
185
186/* has this expired? */
187static bool SessionCacheEntryIsStaleNow(SessionCacheEntry *entry)
188{
189 return SessionCacheEntryIsStale(entry, CFAbsoluteTimeGetCurrent());
190}
191
192/* replace existing mSessionData */
193static OSStatus SessionCacheEntrySetSessionData(SessionCacheEntry *entry,
194 const SSLBuffer *data)
195{
427c49bc 196 SSLFreeBuffer(&entry->mSessionData);
b1ab9ed8
A
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 */
204typedef struct SessionCache {
205 SessionCacheEntry *head;
206 CFTimeInterval mTimeToLive; /* default time-to-live in seconds */
207} SessionCache;
208
209static pthread_mutex_t gSessionCacheLock = PTHREAD_MUTEX_INITIALIZER;
210static SessionCache *gSessionCache = NULL;
211
212static void SessionCacheInit(void) {
213 gSessionCache = sslMalloc(sizeof(SessionCache));
214 gSessionCache->head = NULL;
215 gSessionCache->mTimeToLive = SESSION_CACHE_TTL;
216}
217
218static 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 */
230static 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);
427c49bc 256 return errSecSuccess;
b1ab9ed8
A
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
427c49bc 286 return errSecSuccess;
b1ab9ed8
A
287}
288
289static 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
d8f41ccd
A
315 /* alloc/copy sessionData from existing entry (caller must free) */
316 return SSLCopyBuffer(&entry->mSessionData, sessionData);
b1ab9ed8
A
317}
318
319static 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);
427c49bc 335 return errSecSuccess;
b1ab9ed8
A
336 }
337 }
338
427c49bc 339 return errSecSuccess;
b1ab9ed8
A
340}
341
342/* cleanup, delete stale entries */
343static 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
370static 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 */
385OSStatus 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 */
d8f41ccd 408OSStatus sslCopySession (
b1ab9ed8
A
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
427c49bc 420 sslLogSessCacheDebug("sslGetSession(%d, %p): %d",
b1ab9ed8 421 (int)sessionKey.length, sessionKey.data,
427c49bc 422 (int)serr);
b1ab9ed8
A
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
437OSStatus 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. */
454OSStatus sslCleanupSession(void)
455{
456 SessionCache *cache = SessionCacheGetLockedInstance();
427c49bc 457 OSStatus serr = errSecSuccess;
b1ab9ed8
A
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}