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 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) { 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 159static 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 */ 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{ 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 */ 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); 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 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 315 /* alloc/copy sessionData from existing entry (caller must free) */ 316 return SSLCopyBuffer(&entry->mSessionData, sessionData); 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); 335 return errSecSuccess; 336 } 337 } 338 339 return errSecSuccess; 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 */ 408OSStatus 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 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(); 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} 471