1/* 2 * Copyright (c) 2004 Apple Computer, 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 * tpOcspCache.cpp - local OCSP response cache. 26 */ 27 28#include "tpOcspCache.h" 29#include "tpdebugging.h" 30#include "certGroupUtils.h" 31#include <security_utilities/globalizer.h> 32#include <security_utilities/threading.h> 33#include <security_ocspd/ocspdUtils.h> 34#include <assert.h> 35 36/* 37 * Set this flag nonzero to turn off this cache module. Generally used to debug 38 * the ocspd disk cache. 39 */ 40#ifndef NDEBUG 41#define TP_OCSP_CACHE_DISABLE 0 42#else 43/* cache always enabled in production build */ 44#define TP_OCSP_CACHE_DISABLE 0 45#endif 46 47#pragma mark ---- single cache entry ---- 48 49/* 50 * One cache entry, just a parsed OCSPResponse plus an optional URI and a 51 * "latest" nextUpdate time. An entry is stale when its nextUpdate time has 52 * come and gone. 53 */ 54class OcspCacheEntry : public OCSPResponse 55{ 56public: 57 OcspCacheEntry( 58 const CSSM_DATA derEncoded, 59 const CSSM_DATA *localResponder); // optional 60 ~OcspCacheEntry(); 61 62 /* a trusting environment, this module...all public */ 63 CSSM_DATA mLocalResponder; // we new[] 64}; 65 66OcspCacheEntry::OcspCacheEntry( 67 const CSSM_DATA derEncoded, 68 const CSSM_DATA *localResponder) // optional 69 : OCSPResponse(derEncoded, TP_OCSP_CACHE_TTL) 70{ 71 if(localResponder) { 72 mLocalResponder.Data = new uint8[localResponder->Length]; 73 mLocalResponder.Length = localResponder->Length; 74 memmove(mLocalResponder.Data, localResponder->Data, localResponder->Length); 75 } 76 else { 77 mLocalResponder.Data = NULL; 78 mLocalResponder.Length = 0; 79 } 80} 81 82OcspCacheEntry::~OcspCacheEntry() 83{ 84 delete[] mLocalResponder.Data; 85} 86 87#pragma mark ---- global cache object ---- 88 89/* 90 * The cache object; ModuleNexus provides each task with at most of of these. 91 * All ops which affect the contents of the cache hold the (essentially) global 92 * mCacheLock. 93 */ 94class OcspCache 95{ 96public: 97 OcspCache(); 98 ~OcspCache(); 99 100 /* The methods corresponding to this module's public interface */ 101 OCSPSingleResponse *lookup( 102 OCSPClientCertID &certID, 103 const CSSM_DATA *localResponderURI); // optional 104 void addResponse( 105 const CSSM_DATA &ocspResp, // we'll decode it 106 const CSSM_DATA *localResponderURI); // optional 107 void flush( 108 OCSPClientCertID &certID); 109 110private: 111 void removeEntry(unsigned dex); 112 void scanForStale(); 113 OCSPSingleResponse *lookupPriv( 114 OCSPClientCertID &certID, 115 const CSSM_DATA *localResponderURI, // optional 116 unsigned &rtnDex); // RETURNED on success 117 118 Mutex mCacheLock; 119 120 /* 121 * NOTE: I am aware that most folks would just use an array<> here, but 122 * gdb is so lame that it doesn't even let one examine the contents 123 * of an array<> (or just about anything else in the STL). I prefer 124 * debuggability over saving a few lines of trivial code. 125 */ 126 OcspCacheEntry **mEntries; // just an array of pointers 127 unsigned mNumEntries; // valid entries in mEntries 128 unsigned mSizeofEntries; // mallocd space in mEntries 129}; 130 131OcspCache::OcspCache() 132 : mEntries(NULL), mNumEntries(0), mSizeofEntries(0) 133{ 134 135} 136 137/* As of Tiger I believe that this code never runs */ 138OcspCache::~OcspCache() 139{ 140 for(unsigned dex=0; dex<mNumEntries; dex++) { 141 delete mEntries[dex]; 142 } 143 if(mEntries) { 144 free(mEntries); 145 } 146} 147 148/* 149 * Private routine, remove entry 'n' from cache. 150 * -- caller must hold mCacheLock 151 * -- if caller is traversing mEntries, they must start over because we 152 * manipulate it. 153 */ 154void OcspCache::removeEntry( 155 unsigned dex) 156{ 157 assert(dex <= (mNumEntries - 1)); 158 159 /* removed requested element and compact remaining array */ 160 delete mEntries[dex]; 161 for(unsigned i=dex; i<(mNumEntries - 1); i++) { 162 mEntries[i] = mEntries[i+1]; 163 } 164 mNumEntries--; 165} 166 167/* 168 * Private routine to scan cache, deleting stale entries. 169 * Caller must hold mCacheLock, and not be traversing mEntries. 170 */ 171void OcspCache::scanForStale() 172{ 173 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); 174 bool foundOne; 175 do { 176 /* restart every time we delete a stale entry */ 177 foundOne = false; 178 for(unsigned dex=0; dex<mNumEntries; dex++) { 179 OcspCacheEntry *entry = mEntries[dex]; 180 if(entry->expireTime() < now) { 181 tpOcspCacheDebug("OcspCache::scanForStale: deleting stale entry %p", 182 entry); 183 removeEntry(dex); 184 foundOne = true; 185 break; 186 } 187 } 188 } while(foundOne); 189} 190 191/* 192 * Private lookup routine. Caller holds mCacheLock. We return both an 193 * OCSPSingleResponse and the index into mEntries at which we found it. 194 */ 195 OCSPSingleResponse *OcspCache::lookupPriv( 196 OCSPClientCertID &certID, 197 const CSSM_DATA *localResponderURI, // optional 198 unsigned &rtnDex) // RETURNED on success 199{ 200 OCSPSingleResponse *resp = NULL; 201 for(unsigned dex=0; dex<mNumEntries; dex++) { 202 OcspCacheEntry *entry = mEntries[dex]; 203 if(localResponderURI) { 204 /* if caller specifies, it must match */ 205 if(entry->mLocalResponder.Data == NULL) { 206 /* came from somewhere else, skip it */ 207 tpOcspCacheDebug("OcspCache::lookup: uri mismatch (1) on entry %p", 208 entry); 209 continue; 210 } 211 if(!tpCompareCssmData(localResponderURI, &entry->mLocalResponder)) { 212 tpOcspCacheDebug("OcspCache::lookup: uri mismatch (2) on entry %p", 213 entry); 214 continue; 215 } 216 } 217 resp = entry->singleResponseFor(certID); 218 if(resp) { 219 tpOcspCacheDebug("OcspCache::lookupPriv: cache HIT on entry %p", entry); 220 rtnDex=dex; 221 return resp; 222 } 223 } 224 tpOcspCacheDebug("OcspCache::lookupPriv: cache MISS"); 225 return NULL; 226} 227 228OCSPSingleResponse *OcspCache::lookup( 229 OCSPClientCertID &certID, 230 const CSSM_DATA *localResponderURI) // optional 231{ 232 StLock<Mutex> _(mCacheLock); 233 234 /* take care of stale entries right away */ 235 scanForStale(); 236 237 unsigned rtnDex; 238 return lookupPriv(certID, localResponderURI, rtnDex); 239} 240 241void OcspCache::addResponse( 242 const CSSM_DATA &ocspResp, // we'll decode it 243 const CSSM_DATA *localResponderURI) // optional 244{ 245 StLock<Mutex> _(mCacheLock); 246 247 OcspCacheEntry *entry = new OcspCacheEntry(ocspResp, localResponderURI); 248 if(mNumEntries == mSizeofEntries) { 249 if(mSizeofEntries == 0) { 250 /* appending to empty array */ 251 mSizeofEntries = 1; 252 } 253 else { 254 mSizeofEntries *= 2; 255 } 256 mEntries = (OcspCacheEntry **)realloc(mEntries, 257 mSizeofEntries * sizeof(OcspCacheEntry *)); 258 } 259 mEntries[mNumEntries++] = entry; 260 tpOcspCacheDebug("OcspCache::addResponse: add entry %p", entry); 261} 262 263void OcspCache::flush( 264 OCSPClientCertID &certID) 265{ 266 StLock<Mutex> _(mCacheLock); 267 268 /* take care of all stale entries */ 269 scanForStale(); 270 271 unsigned rtnDex; 272 OCSPSingleResponse *resp; 273 do { 274 /* execute as until we find no more entries matching */ 275 resp = lookupPriv(certID, NULL, rtnDex); 276 if(resp) { 277 assert((rtnDex >= 0) && (rtnDex < mNumEntries)); 278 tpOcspCacheDebug("OcspCache::flush: deleting entry %p", mEntries[rtnDex]); 279 removeEntry(rtnDex); 280 } 281 } while(resp != NULL); 282} 283 284 285static ModuleNexus<OcspCache> tpOcspCache; 286 287#pragma mark ---- Public API ---- 288/* 289 * Lookup locally cached response. Caller must free the returned OCSPSingleResponse. 290 */ 291OCSPSingleResponse *tpOcspCacheLookup( 292 OCSPClientCertID &certID, 293 const CSSM_DATA *localResponderURI) // optional 294{ 295 return tpOcspCache().lookup(certID, localResponderURI); 296} 297 298/* 299 * Add a fully verified OCSP response to cache. 300 */ 301void tpOcspCacheAdd( 302 const CSSM_DATA &ocspResp, // we'll decode it, not keep a ref 303 const CSSM_DATA *localResponderURI) // optional 304{ 305 #if TP_OCSP_CACHE_DISABLE 306 return; 307 #endif 308 tpOcspCache().addResponse(ocspResp, localResponderURI); 309} 310 311/* 312 * Delete any entry associated with specified certID from cache. 313 */ 314void tpOcspCacheFlush( 315 OCSPClientCertID &certID) 316{ 317 tpOcspCache().flush(certID); 318} 319 320