#include "ssl.h"
#include "sslMemory.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>
#define QUICK_CACHE_TEST 0
#if QUICK_CACHE_TEST
#define SESSION_CACHE_TTL ((int)5)
#else
#define SESSION_CACHE_TTL ((int)(10 * 60))
#endif
#define CACHE_PRINT 0
#if CACHE_PRINT
#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 {
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
#define cachePrint(k, d)
#define DUMP_ALL_CACHE 0
#endif
#if DUMP_ALL_CACHE
static void dumpAllCache();
#else
#define dumpAllCache()
#endif
class SessionCacheEntry {
public:
SessionCacheEntry(
const SSLBuffer &key,
const SSLBuffer &sessionData,
const Time::Absolute &expirationTime);
~SessionCacheEntry();
bool matchKey(const SSLBuffer &key) const;
bool isStale(); bool isStale(const Time::Absolute &now);
SSLBuffer &key() { return mKey; }
SSLBuffer &sessionData() { return mSessionData; }
OSStatus sessionData(const SSLBuffer &data);
private:
SSLBuffer mKey;
SSLBuffer mSessionData;
Time::Absolute mExpiration;
};
SessionCacheEntry::SessionCacheEntry(
const SSLBuffer &key,
const SSLBuffer &sessionData,
const Time::Absolute &expirationTime)
: mExpiration(expirationTime)
{
OSStatus serr;
serr = SSLCopyBuffer(key, mKey);
if(serr) {
throw runtime_error("memory error");
}
serr = SSLCopyBuffer(sessionData, mSessionData);
if(serr) {
throw runtime_error("memory error");
}
sslLogSessCacheDebug("SessionCacheEntry(buf,buf) this %p", this);
mExpiration += Time::Interval(SESSION_CACHE_TTL);
}
SessionCacheEntry::~SessionCacheEntry()
{
sslLogSessCacheDebug("~SessionCacheEntry() this %p", this);
SSLFreeBuffer(mKey, NULL); SSLFreeBuffer(mSessionData, NULL);
}
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);
}
bool SessionCacheEntry::isStale()
{
return isStale(Time::now());
}
bool SessionCacheEntry::isStale(const Time::Absolute &now)
{
if(now > mExpiration) {
return true;
}
else {
return false;
}
}
OSStatus SessionCacheEntry::sessionData(
const SSLBuffer &data)
{
SSLFreeBuffer(mSessionData, NULL);
return SSLCopyBuffer(data, mSessionData);
}
typedef std::deque<SessionCacheEntry *> SessionCacheType;
typedef SessionCacheType::iterator SessionCacheIter;
class SessionCache
{
public:
SessionCache()
: mTimeToLive(SESSION_CACHE_TTL) {}
~SessionCache();
OSStatus addEntry(
const SSLBuffer sessionKey,
const SSLBuffer sessionData);
OSStatus lookupEntry(
const SSLBuffer sessionKey,
SSLBuffer *sessionData);
OSStatus deleteEntry(
const SSLBuffer sessionKey);
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()
{
StLock<Mutex> _(mSessionLock);
for(SessionCacheIter iter = mSessionCache.begin(); iter != mSessionCache.end(); ) {
iter = deletePriv(iter);
}
}
OSStatus SessionCache::addEntry(
const SSLBuffer sessionKey,
const SSLBuffer sessionData)
{
StLock<Mutex> _(mSessionLock);
SessionCacheIter existIter = lookupPriv(&sessionKey);
if(existIter != mSessionCache.end()) {
SessionCacheEntry *existEntry = *existIter;
SSLBuffer &existBuf = existEntry->sessionData();
if((existBuf.length == sessionData.length) &&
(memcmp(existBuf.data, sessionData.data, sessionData.length) == 0)) {
sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT "
"entry = %p", existEntry);
return noErr;
}
else {
sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE "
"entry = %p", existEntry);
return existEntry->sessionData(sessionData);
}
}
SessionCacheEntry *entry = new SessionCacheEntry(sessionKey,
sessionData,
Time::now() + mTimeToLive);
sslLogSessCacheDebug("SessionCache::addEntry %p", entry);
cachePrint(&sessionKey, &sessionData);
dumpAllCache();
mSessionCache.push_front(entry);
assert(lookupPriv(&sessionKey) != mSessionCache.end());
return noErr;
}
OSStatus SessionCache::lookupEntry(
const SSLBuffer sessionKey,
SSLBuffer *sessionData)
{
StLock<Mutex> _(mSessionLock);
SessionCacheIter existIter = lookupPriv(&sessionKey);
if(existIter == mSessionCache.end()) {
return errSSLSessionNotFound;
}
SessionCacheEntry *entry = *existIter;
if(entry->isStale()) {
sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE "
"entry, deleting", entry);
cachePrint(&sessionKey, &entry->sessionData());
deletePriv(existIter);
return errSSLSessionNotFound;
}
return SSLCopyBuffer(entry->sessionData(), *sessionData);
}
OSStatus SessionCache::deleteEntry(
const SSLBuffer sessionKey)
{
StLock<Mutex> _(mSessionLock);
deletePriv(&sessionKey);
return noErr;
}
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)) {
#ifndef DEBUG
sslLogSessCacheDebug("...SessionCache::cleanup: deleting "
"cached session (%p)", entry);
cachePrint(&entry->key(), &entry->sessionData());
#endif
iter = deletePriv(iter);
}
else {
iter++;
brtn = true;
}
}
return brtn;
}
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;
}
}
return it;
}
void SessionCache::deletePriv(
const SSLBuffer *sessionKey)
{
SessionCacheIter iter = lookupPriv(sessionKey);
if(iter != mSessionCache.end()) {
#if CACHE_PRINT
SessionCacheEntry *entry = *iter;
sslLogSessCacheDebug("SessionCache::deletePriv %p", entry);
cachePrint(sessionKey, &entry->sessionData());
dumpAllCache();
#endif
deletePriv(iter);
}
assert(lookupPriv(sessionKey) == mSessionCache.end());
}
SessionCacheIter SessionCache::deletePriv(
SessionCacheIter iter)
{
assert(iter != mSessionCache.end());
SessionCacheEntry *entry = *iter;
SessionCacheIter nextIter = mSessionCache.erase(iter);
delete entry;
return nextIter;
}
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
OSStatus sslAddSession (
const SSLBuffer sessionKey,
const SSLBuffer sessionData)
{
OSStatus serr;
try {
serr = gSessionCache().addEntry(sessionKey, sessionData);
}
catch(...) {
serr = unimpErr;
}
dumpAllCache();
return serr;
}
OSStatus sslGetSession (
const SSLBuffer sessionKey,
SSLBuffer *sessionData)
{
OSStatus serr;
try {
serr = gSessionCache().lookupEntry(sessionKey, sessionData);
}
catch(...) {
serr = errSSLSessionNotFound;
}
sslLogSessCacheDebug("sslGetSession(%d, %p): %ld",
(int)sessionKey.length, sessionKey.data,
serr);
if(serr == noErr) {
cachePrint(&sessionKey, sessionData);
}
else {
cachePrint(&sessionKey, NULL);
}
dumpAllCache();
return serr;
}
OSStatus sslDeleteSession (
const SSLBuffer sessionKey)
{
OSStatus serr;
try {
serr = gSessionCache().deleteEntry(sessionKey);
}
catch(...) {
serr = errSSLSessionNotFound;
}
return serr;
}
OSStatus sslCleanupSession ()
{
OSStatus serr = noErr;
bool moreToGo = false;
try {
moreToGo = gSessionCache().cleanup();
}
catch(...) {
serr = errSSLSessionNotFound;
}
return serr;
}