X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_ssl/lib/appleSession.c diff --git a/Security/libsecurity_ssl/lib/appleSession.c b/Security/libsecurity_ssl/lib/appleSession.c new file mode 100644 index 00000000..941a473b --- /dev/null +++ b/Security/libsecurity_ssl/lib/appleSession.c @@ -0,0 +1,470 @@ +/* + * Copyright (c) 1999-2001,2005-2008,2010-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * appleSession.c - Session storage module, Apple CDSA version. + */ + +/* + * The current implementation stores sessions in a linked list, a member of a + * SessionCache object for which we keep a single global instance. It is + * expected that at a given time, only a small number of sessions will be + * cached, so the random insertion access provided by a map<> is unnecessary. + * New entries are placed in the head of the list, assuming a LIFO usage + * tendency. + * + * Entries in this cache have a time to live of SESSION_CACHE_TTL, currently + * ten minutes. Entries are tested for being stale upon lookup; also, the global + * sslCleanupSession() tests all entries in the cache, deleting entries which + * are stale. This function is currently called whenever an SSLContext is deleted. + * The current design does not provide any asynchronous timed callouts to perform + * further cache cleanup; it was decided that the thread overhead of this would + * outweight the benefits (again assuming a small number of entries in the + * cache). + * + * When a session is added via sslAddSession, and a cache entry already + * exists for the specifed key (sessionID), the sessionData for the existing + * cache entry is updated with the new sessionData. The entry's expiration + * time is unchanged (thus a given session entry can only be used for a finite + * time no mattter how often it is re-used), + */ + +#include "ssl.h" +#include "sslMemory.h" +#include "sslDebug.h" +#include "appleSession.h" + +#include +#include +#include + +#include + +/* default time-to-live in cache, in seconds */ +#define QUICK_CACHE_TEST 0 +#if QUICK_CACHE_TEST +#define SESSION_CACHE_TTL ((CFTimeInterval)5) +#else +#define SESSION_CACHE_TTL ((CFTimeInterval)(10 * 60)) +#endif /* QUICK_CACHE_TEST */ + +#define CACHE_PRINT 0 +#if CACHE_PRINT +#define DUMP_ALL_CACHE 0 + +static void cachePrint( + const void *entry, + const SSLBuffer *key, + const SSLBuffer *data) +{ + printf("entry: %p ", entry); + unsigned char *kd = key->data; + if(data != NULL) { + unsigned char *dd = data->data; + printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X" + " data: %02X%02X%02X%02X... (len %d)\n", + kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7], + dd[0],dd[1],dd[2],dd[3], (unsigned)data->length); + } + else { + /* just print key */ + printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n", + kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7]); + } +} +#else /* !CACHE_PRINT */ +#define cachePrint(e, k, d) +#define DUMP_ALL_CACHE 0 +#endif /* CACHE_PRINT */ + +#if DUMP_ALL_CACHE +static void dumpAllCache(void); +#else +#define dumpAllCache() +#endif + +/* + * One entry (value) in SessionCache. + */ +typedef struct SessionCacheEntry SessionCacheEntry; +struct SessionCacheEntry { + /* Linked list of SessionCacheEntries. */ + SessionCacheEntry *next; + + SSLBuffer mKey; + SSLBuffer mSessionData; + + /* this entry to be removed from session map at this time */ + CFAbsoluteTime mExpiration; +}; + +/* + * Note: the caller passes in the expiration time solely to accomodate the + * instantiation of a single const Time::Interval for use in calculating + * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache. + */ +/* + * This constructor, the only one, allocs copies of the key and value + * SSLBuffers. + */ +static SessionCacheEntry *SessionCacheEntryCreate( + const SSLBuffer *key, + const SSLBuffer *sessionData, + CFAbsoluteTime expirationTime) +{ + OSStatus serr; + + SessionCacheEntry *entry = sslMalloc(sizeof(SessionCacheEntry)); + if (entry == NULL) + return NULL; + + serr = SSLCopyBuffer(key, &entry->mKey); + if(serr) { + sslFree (entry); + return NULL; + } + serr = SSLCopyBuffer(sessionData, &entry->mSessionData); + if(serr) { + SSLFreeBuffer(&entry->mKey); + sslFree (entry); + return NULL; + } + + sslLogSessCacheDebug("SessionCacheEntryCreate(buf,buf) %p", entry); + entry->mExpiration = expirationTime; + + return entry; +} + +static void SessionCacheEntryDelete(SessionCacheEntry *entry) +{ + sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry); + SSLFreeBuffer(&entry->mKey); // no SSLContext + SSLFreeBuffer(&entry->mSessionData); + sslFree(entry); +} + +/* basic lookup/match function */ +static bool SessionCacheEntryMatchKey(SessionCacheEntry *entry, + const SSLBuffer *key) +{ + if(key->length != entry->mKey.length) { + return false; + } + if((key->data == NULL) || (entry->mKey.data == NULL)) { + return false; + } + return (memcmp(key->data, entry->mKey.data, entry->mKey.length) == 0); +} + +static bool SessionCacheEntryIsStale(SessionCacheEntry *entry, + CFAbsoluteTime now) +{ + return now > entry->mExpiration; +} + +/* has this expired? */ +static bool SessionCacheEntryIsStaleNow(SessionCacheEntry *entry) +{ + return SessionCacheEntryIsStale(entry, CFAbsoluteTimeGetCurrent()); +} + +/* replace existing mSessionData */ +static OSStatus SessionCacheEntrySetSessionData(SessionCacheEntry *entry, + const SSLBuffer *data) +{ + SSLFreeBuffer(&entry->mSessionData); + return SSLCopyBuffer(data, &entry->mSessionData); +} + +/* + * Global list of sessions and associated state. We maintain a singleton of + * this. + */ +typedef struct SessionCache { + SessionCacheEntry *head; + CFTimeInterval mTimeToLive; /* default time-to-live in seconds */ +} SessionCache; + +static pthread_mutex_t gSessionCacheLock = PTHREAD_MUTEX_INITIALIZER; +static SessionCache *gSessionCache = NULL; + +static void SessionCacheInit(void) { + gSessionCache = sslMalloc(sizeof(SessionCache)); + gSessionCache->head = NULL; + gSessionCache->mTimeToLive = SESSION_CACHE_TTL; +} + +static SessionCache *SessionCacheGetLockedInstance(void) { + pthread_mutex_lock(&gSessionCacheLock); + if (!gSessionCache) { + /* We could use pthread_once, but we already have a mutex for other + reasons. */ + SessionCacheInit(); + } + + return gSessionCache; +} + +/* these three correspond to the C functions exported by this file */ +static OSStatus SessionCacheAddEntry( + SessionCache *cache, + const SSLBuffer *sessionKey, + const SSLBuffer *sessionData, + uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */ +{ + SessionCacheEntry *entry = NULL; + SessionCacheEntry **current; + CFTimeInterval expireTime; + + for (current = &(cache->head); *current; current = &((*current)->next)) { + entry = *current; + if (SessionCacheEntryMatchKey(entry, sessionKey)) { + /* cache hit - just update this entry's sessionData if necessary */ + /* Note we leave expiration time and position in queue unchanged + - OK? */ + /* What if the entry has already expired? */ + if((entry->mSessionData.length == sessionData->length) && + (memcmp(entry->mSessionData.data, sessionData->data, + sessionData->length) == 0)) { + /* + * These usually match, and a memcmp is a lot cheaper than + * a malloc and a free, hence this quick optimization..... + */ + sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT " + "entry = %p", entry); + return errSecSuccess; + } + else { + sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE " + "entry = %p", entry); + return SessionCacheEntrySetSessionData(entry, sessionData); + } + } + } + + expireTime = CFAbsoluteTimeGetCurrent(); + if(timeToLive) { + /* caller-specified */ + expireTime += (CFTimeInterval)timeToLive; + } + else { + /* default */ + expireTime += cache->mTimeToLive; + } + /* this allocs new copy of incoming sessionKey and sessionData */ + entry = SessionCacheEntryCreate(sessionKey, sessionData, expireTime); + + sslLogSessCacheDebug("SessionCache::addEntry %p", entry); + cachePrint(entry, sessionKey, sessionData); + dumpAllCache(); + + /* add to head of queue for LIFO caching */ + entry->next = cache->head; + cache->head = entry; + + return errSecSuccess; +} + +static OSStatus SessionCacheLookupEntry( + SessionCache *cache, + const SSLBuffer *sessionKey, + SSLBuffer *sessionData) +{ + SessionCacheEntry *entry = NULL; + SessionCacheEntry **current; + for (current = &(cache->head); *current; current = &((*current)->next)) { + entry = *current; + if (SessionCacheEntryMatchKey(entry, sessionKey)) + break; + } + + if (*current == NULL) + return errSSLSessionNotFound; + + if (SessionCacheEntryIsStaleNow(entry)) { + sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE " + "entry, deleting; current %p, entry->next %p", + entry, current, entry->next); + cachePrint(entry, sessionKey, &entry->mSessionData); + *current = entry->next; + SessionCacheEntryDelete(entry); + return errSSLSessionNotFound; + } + + /* alloc/copy sessionData from existing entry (caller must free) */ + return SSLCopyBuffer(&entry->mSessionData, sessionData); +} + +static OSStatus SessionCacheDeleteEntry( + SessionCache *cache, + const SSLBuffer *sessionKey) +{ + SessionCacheEntry **current; + + for (current = &(cache->head); *current; current = &((*current)->next)) { + SessionCacheEntry *entry = *current; + if (SessionCacheEntryMatchKey(entry, sessionKey)) { + #ifndef DEBUG + sslLogSessCacheDebug("...SessionCacheDeleteEntry: deleting " + "cached session (%p)", entry); + cachePrint(entry, &entry->mKey, &entry->mSessionData); + #endif + *current = entry->next; + SessionCacheEntryDelete(entry); + return errSecSuccess; + } + } + + return errSecSuccess; +} + +/* cleanup, delete stale entries */ +static bool SessionCacheCleanup(SessionCache *cache) +{ + bool brtn = false; + CFAbsoluteTime rightNow = CFAbsoluteTimeGetCurrent(); + SessionCacheEntry **current; + + for (current = &(cache->head); *current;) { + SessionCacheEntry *entry = *current; + if(SessionCacheEntryIsStale(entry, rightNow)) { + #ifndef DEBUG + sslLogSessCacheDebug("...SessionCacheCleanup: deleting " + "cached session (%p)", entry); + cachePrint(entry, &entry->mKey, &entry->mSessionData); + #endif + *current = entry->next; + SessionCacheEntryDelete(entry); + } + else { + current = &((*current)->next); + /* we're leaving one in the map */ + brtn = true; + } + } + return brtn; +} + +#if DUMP_ALL_CACHE +static void dumpAllCache(void) +{ + SessionCache *cache = gSessionCache; + SessionCacheEntry *entry; + + printf("Contents of sessionCache:\n"); + for(entry = cache->head; entry; entry = entry->next) { + cachePrint(entry, &entry->mKey, &entry->mSessionData); + } +} +#endif /* DUMP_ALL_CACHE */ + +/* + * Store opaque sessionData, associated with opaque sessionKey. + */ +OSStatus sslAddSession ( + const SSLBuffer sessionKey, + const SSLBuffer sessionData, + uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */ +{ + SessionCache *cache = SessionCacheGetLockedInstance(); + OSStatus serr; + if (!cache) + serr = errSSLSessionNotFound; + else + { + serr = SessionCacheAddEntry(cache, &sessionKey, &sessionData, timeToLive); + + dumpAllCache(); + } + + pthread_mutex_unlock(&gSessionCacheLock); + return serr; +} + +/* + * Given an opaque sessionKey, alloc & retrieve associated sessionData. + */ +OSStatus sslCopySession ( + const SSLBuffer sessionKey, + SSLBuffer *sessionData) +{ + SessionCache *cache = SessionCacheGetLockedInstance(); + OSStatus serr; + if (!cache) + serr = errSSLSessionNotFound; + else + { + serr = SessionCacheLookupEntry(cache, &sessionKey, sessionData); + + sslLogSessCacheDebug("sslGetSession(%d, %p): %d", + (int)sessionKey.length, sessionKey.data, + (int)serr); + if(!serr) { + cachePrint(NULL, &sessionKey, sessionData); + } + else { + cachePrint(NULL, &sessionKey, NULL); + } + dumpAllCache(); + } + + pthread_mutex_unlock(&gSessionCacheLock); + + return serr; +} + +OSStatus sslDeleteSession ( + const SSLBuffer sessionKey) +{ + SessionCache *cache = SessionCacheGetLockedInstance(); + OSStatus serr; + if (!cache) + serr = errSSLSessionNotFound; + else + { + serr = SessionCacheDeleteEntry(cache, &sessionKey); + } + + pthread_mutex_unlock(&gSessionCacheLock); + return serr; +} + +/* cleanup up session cache, deleting stale entries. */ +OSStatus sslCleanupSession(void) +{ + SessionCache *cache = SessionCacheGetLockedInstance(); + OSStatus serr = errSecSuccess; + bool moreToGo = false; + + if (!cache) + serr = errSSLSessionNotFound; + else + { + moreToGo = SessionCacheCleanup(cache); + } + /* Possible TBD: if moreToGo, schedule a timed callback to this function */ + + pthread_mutex_unlock(&gSessionCacheLock); + return serr; +}