]> git.saurik.com Git - apple/security.git/blobdiff - SecureTransport/appleSession.cpp
Security-54.tar.gz
[apple/security.git] / SecureTransport / appleSession.cpp
diff --git a/SecureTransport/appleSession.cpp b/SecureTransport/appleSession.cpp
new file mode 100644 (file)
index 0000000..721fcfd
--- /dev/null
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
+ * 
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ * 
+ * This 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.
+ */
+
+
+/*
+       File:           appleSession.cpp
+
+       Contains:       Session storage module, Apple CDSA version. 
+
+       Written by:     Doug Mitchell, based on Netscape SSLRef 3.0
+
+       Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
+
+*/
+
+/* 
+ * This file replaces the caller-specified SSLAddSessionFunc,
+ * SSLGetSessionFunc, and SSLDeleteSessionFunc callbacks in the 
+ * original SSLRef 3.0.
+ *
+ * The current implementation stores sessions in a deque<>, a member of a 
+ * SessionCache object for which we keep a ModuleNexus-ized 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 queue, 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 "sslctx.h"
+#include "sslalloc.h"
+#include "appleGlue.h"
+#include "sslDebug.h"
+#include "appleSession.h"
+
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+
+#include <deque>
+#include <stdexcept>
+#include <Security/threading.h>
+#include <Security/globalizer.h>
+#include <Security/timeflow.h>
+
+/* time-to-live in cache, in seconds */
+#define QUICK_CACHE_TEST       0
+#if            QUICK_CACHE_TEST
+#define SESSION_CACHE_TTL      ((int)5)
+#else
+#define SESSION_CACHE_TTL      ((int)(10 * 60))
+#endif /* QUICK_CACHE_TEST */
+
+#define CACHE_PRINT                    0
+#if            CACHE_PRINT
+#define cprintf(s)                     printf s
+#define DUMP_ALL_CACHE         0
+
+static void cachePrint(
+       const SSLBuffer *key, 
+       const SSLBuffer *data)
+{
+       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 cprintf(s)
+#define cachePrint(k, d)
+#define DUMP_ALL_CACHE 0
+#endif /* CACHE_PRINT */
+
+#if    DUMP_ALL_CACHE
+static void dumpAllCache();
+#else
+#define dumpAllCache()
+#endif
+
+/*
+ * One entry (value) in SessionCache.  
+ */
+class SessionCacheEntry {
+public:
+       /*
+        * This constructor, the only one, allocs copies of the key and value
+        * SSLBuffers.
+        */
+       SessionCacheEntry(
+               const SSLBuffer &key, 
+               const SSLBuffer &sessionData,
+               const Time::Absolute &expirationTime);
+       ~SessionCacheEntry();
+               
+       /* basic lookup/match function */
+       bool                    matchKey(const SSLBuffer &key) const;
+       
+       /* has this expired? */
+       bool                    isStale();                                                      // calculates "now" 
+       bool                    isStale(const Time::Absolute &now);     // when you know it
+       
+       /* key/data accessors */
+       SSLBuffer               &key()                  { return mKey; }
+       SSLBuffer               &sessionData()  { return mSessionData; }
+       
+       /* replace existing mSessionData */
+       SSLErr                  sessionData(const SSLBuffer &data);
+       
+private:
+       SSLBuffer               mKey;
+       SSLBuffer               mSessionData;
+
+       /* this entry to be removed from session map at this time */
+       Time::Absolute  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.
+ */
+SessionCacheEntry::SessionCacheEntry(
+       const SSLBuffer &key, 
+       const SSLBuffer &sessionData,
+       const Time::Absolute &expirationTime)
+               : mExpiration(expirationTime)
+{
+       SSLErr serr;
+       
+       serr = SSLCopyBuffer(&key, &mKey);
+       if(serr) {
+               throw runtime_error("memory error");
+       }
+       serr = SSLCopyBuffer(&sessionData, &mSessionData);
+       if(serr) {
+               throw runtime_error("memory error");
+       }
+       cprintf(("SessionCacheEntry(buf,buf) this %p\n", this));
+       mExpiration += Time::Interval(SESSION_CACHE_TTL);
+}
+
+SessionCacheEntry::~SessionCacheEntry()
+{
+       cprintf(("~SessionCacheEntry() this %p\n", this));
+       SSLFreeBuffer(&mKey, NULL);             // no SystemContext
+       SSLFreeBuffer(&mSessionData, NULL);
+}
+
+/* basic lookup/match function */
+bool SessionCacheEntry::matchKey(const SSLBuffer &key) const
+{
+       if(key.length != mKey.length) {
+               return false;
+       }
+       if((key.data == NULL) || (mKey.data == NULL)) {
+               return false;
+       }
+       return (memcmp(key.data, mKey.data, mKey.length) == 0);
+}
+       
+/* has this expired? */
+bool SessionCacheEntry::isStale()
+{
+       return isStale(Time::now());
+}
+
+bool SessionCacheEntry::isStale(const Time::Absolute &now)
+{
+       if(now > mExpiration) {
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/* replace existing mSessionData */
+SSLErr SessionCacheEntry::sessionData(
+       const SSLBuffer &data)
+{
+       SSLFreeBuffer(&mSessionData, NULL);
+       return SSLCopyBuffer(&data, &mSessionData);
+}
+
+/* Types for the actual deque and its iterator */
+typedef std::deque<SessionCacheEntry *> SessionCacheType;
+typedef SessionCacheType::iterator SessionCacheIter;
+
+/* 
+ * Global map and associated state. We maintain a singleton of this.
+ */
+class SessionCache
+{
+public:
+       SessionCache()
+         : mTimeToLive(SESSION_CACHE_TTL) {}
+       ~SessionCache();
+       
+       /* these correspond to the C functions exported by this file */
+       SSLErr addEntry(
+               const SSLBuffer sessionKey, 
+               const SSLBuffer sessionData);
+       SSLErr lookupEntry(
+               const SSLBuffer sessionKey, 
+               SSLBuffer *sessionData); 
+       SSLErr deleteEntry(
+               const SSLBuffer sessionKey);
+               
+       /* cleanup, delete stale entries */
+       bool cleanup();
+       SessionCacheType                &sessMap() { return mSessionCache; }
+       
+private:
+       SessionCacheIter lookupPriv(
+               const SSLBuffer *sessionKey);
+       void deletePriv(
+               const SSLBuffer *sessionKey);
+       SessionCacheIter deletePriv(
+               SessionCacheIter iter);
+       SessionCacheType                mSessionCache;
+       Mutex                                   mSessionLock;
+       const Time::Interval    mTimeToLive;
+};
+
+SessionCache::~SessionCache()
+{
+       /* free all entries */
+       StLock<Mutex> _(mSessionLock);
+       for(SessionCacheIter iter = mSessionCache.begin(); iter != mSessionCache.end(); ) {
+               iter = deletePriv(iter);
+       }
+}
+
+/* these three correspond to the C functions exported by this file */
+SSLErr SessionCache::addEntry(
+       const SSLBuffer sessionKey, 
+       const SSLBuffer sessionData)
+{
+       StLock<Mutex> _(mSessionLock);
+       
+       SessionCacheIter existIter = lookupPriv(&sessionKey);
+       if(existIter != mSessionCache.end()) {
+               /* cache hit - just update this entry's sessionData if necessary */
+               /* Note we leave expiration time and position in deque unchanged - OK? */
+               SessionCacheEntry *existEntry = *existIter;
+               SSLBuffer &existBuf = existEntry->sessionData();
+               if((existBuf.length == sessionData.length) &&
+                  (memcmp(existBuf.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.....
+                        */
+                       cprintf(("SessionCache::addEntry CACHE HIT entry = %p\n", existEntry));
+                       return SSLNoErr;
+               }
+               else {
+                       cprintf(("SessionCache::addEntry CACHE REPLACE entry = %p\n", existEntry));
+                       return existEntry->sessionData(sessionData);
+               }
+       }
+       
+       /* this allocs new copy of incoming sessionKey and sessionData */
+       SessionCacheEntry *entry = new SessionCacheEntry(sessionKey, 
+               sessionData,
+               Time::now() + mTimeToLive);
+
+       cprintf(("SessionCache::addEntry %p\n", entry));
+       cachePrint(&sessionKey, &sessionData);
+       dumpAllCache();
+
+       /* add to head of queue for LIFO caching */
+       mSessionCache.push_front(entry);
+       CASSERT(lookupPriv(&sessionKey) != mSessionCache.end());
+       return SSLNoErr;
+}
+
+SSLErr SessionCache::lookupEntry(
+       const SSLBuffer sessionKey, 
+       SSLBuffer *sessionData)
+{
+       StLock<Mutex> _(mSessionLock);
+       
+       SessionCacheIter existIter = lookupPriv(&sessionKey);
+       if(existIter == mSessionCache.end()) {
+               return SSLSessionNotFoundErr;
+       }
+       SessionCacheEntry *entry = *existIter;
+       if(entry->isStale()) {
+               cprintf(("SessionCache::lookupEntry %p: STALE entry, deleting\n", entry));
+               cachePrint(&sessionKey, &entry->sessionData());
+               deletePriv(existIter);
+               return SSLSessionNotFoundErr;
+       }
+       /* alloc/copy sessionData from existing entry (caller must free) */
+       return SSLCopyBuffer(&entry->sessionData(), sessionData);
+}
+
+SSLErr SessionCache::deleteEntry(
+       const SSLBuffer sessionKey)
+{
+       StLock<Mutex> _(mSessionLock);
+       deletePriv(&sessionKey);
+       return SSLNoErr;
+}
+       
+/* cleanup, delete stale entries */
+bool SessionCache::cleanup()
+{
+       StLock<Mutex> _(mSessionLock);
+       bool brtn = false;
+       Time::Absolute rightNow = Time::now();
+       SessionCacheIter iter;
+       
+       for(iter = mSessionCache.begin(); iter != mSessionCache.end(); ) {
+               SessionCacheEntry *entry = *iter;
+               if(entry->isStale(rightNow)) {
+                       #if CACHE_PRINT
+                       SSLBuffer *key = &entry->key();
+                       cprintf(("...SessionCache::cleanup: deleting cached session (%p)\n", 
+                               entry));
+                       cachePrint(key, &entry->sessionData());
+                       #endif
+                       iter = deletePriv(iter);
+               }
+               else {
+                       iter++;
+                       /* we're leaving one in the map */
+                       brtn = true;
+               }
+       }
+       return brtn;
+}
+
+/* private methods, mSessionLock held on entry and exit */
+SessionCacheIter SessionCache::lookupPriv(
+       const SSLBuffer *sessionKey)
+{
+       SessionCacheIter it;
+       
+       for(it = mSessionCache.begin(); it != mSessionCache.end(); it++) {
+               SessionCacheEntry *entry = *it;
+               if(entry->matchKey(*sessionKey)) {
+                       return it;
+               }
+       }
+       /* returning map.end() */
+       return it;
+}
+
+void SessionCache::deletePriv(
+       const SSLBuffer *sessionKey)
+{
+       SessionCacheIter iter = lookupPriv(sessionKey);
+       if(iter != mSessionCache.end()) {
+               /* 
+                * delete from map 
+                * free underlying SSLBuffer.data pointers
+                * destruct the stored map entry 
+                */
+               #if     CACHE_PRINT
+               SessionCacheEntry *entry = *iter;
+               cprintf(("SessionCache::deletePriv %p\n", entry));
+               cachePrint(sessionKey, &entry->sessionData());
+               dumpAllCache();
+               #endif
+               deletePriv(iter);
+       }
+       CASSERT(lookupPriv(sessionKey) == mSessionCache.end());
+}
+
+/* common erase, given a SessionCacheIter; returns next iter */
+SessionCacheIter SessionCache::deletePriv(
+       SessionCacheIter iter)
+{
+       CASSERT(iter != mSessionCache.end());
+       SessionCacheEntry *entry = *iter;
+       SessionCacheIter nextIter = mSessionCache.erase(iter);
+       delete entry;
+       return nextIter;
+}
+
+/* the single global thing */
+static ModuleNexus<SessionCache> gSessionCache;
+
+#if            DUMP_ALL_CACHE
+static void dumpAllCache()
+{
+       SessionCacheIter it;
+       SessionCacheType &smap = gSessionCache().sessMap();
+       
+       printf("Contents of sessionCache:\n");
+       for(it = smap.begin(); it != smap.end(); it++) {
+               SessionCacheEntry *entry = *it;
+               cachePrint(&entry->key(), &entry->sessionData());
+       }
+}
+#endif /* DUMP_ALL_CACHE */
+
+/*
+ * Store opaque sessionData, associated with opaque sessionKey.
+ */
+SSLErr sslAddSession (
+       const SSLBuffer sessionKey, 
+       const SSLBuffer sessionData)
+{
+       SSLErr serr;
+       try {
+               serr = gSessionCache().addEntry(sessionKey, sessionData);
+       }
+       catch(...) {
+               serr = SSLUnsupportedErr;
+       }
+       dumpAllCache();
+       return serr;
+}
+
+/*
+ * Given an opaque sessionKey, alloc & retrieve associated sessionData.
+ */
+SSLErr sslGetSession (
+       const SSLBuffer sessionKey, 
+       SSLBuffer *sessionData)
+{
+       SSLErr serr;
+       try {
+               serr = gSessionCache().lookupEntry(sessionKey, sessionData);
+       }
+       catch(...) {
+               serr = SSLSessionNotFoundErr;
+       }
+       cprintf(("\nsslGetSession(%d, %p): %d\n", (int)sessionKey.length, sessionKey.data,
+               serr));
+       if(serr == SSLNoErr) {
+               cachePrint(&sessionKey, sessionData);
+       }
+       else {
+               cachePrint(&sessionKey, NULL);
+       }
+       dumpAllCache();
+       return serr;
+}
+
+SSLErr sslDeleteSession (
+       const SSLBuffer sessionKey)
+{
+       SSLErr serr;
+       try {
+               serr = gSessionCache().deleteEntry(sessionKey);
+       }
+       catch(...) {
+               serr = SSLSessionNotFoundErr;
+       }
+       return serr;
+}
+
+/* cleanup up session cache, deleting stale entries. */
+SSLErr sslCleanupSession ()
+{
+       SSLErr serr = SSLNoErr;
+       bool moreToGo = false;
+       try {
+               moreToGo = gSessionCache().cleanup();
+       }
+       catch(...) {
+               serr = SSLSessionNotFoundErr;
+       }
+       /* Possible TBD: if moreToGo, schedule a timed callback to this function */
+       return serr;
+}