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