1/*
2 * Copyright (c) 2004-2012 Apple 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 * ocspdDb.cpp - OCSP daemon database
26 */
27
28#if OCSP_DEBUG
29#define OCSP_USE_SYSLOG	1
30#endif
31#include "ocspdDb.h"
32#include "attachCommon.h"
33#include <security_ocspd/ocspdDbSchema.h>
34#include <security_ocspd/ocspdDebug.h>
35#include <security_ocspd/ocspResponse.h>
36#include <security_ocspd/ocspdUtils.h>
37#include <Security/Security.h>
38#include <security_utilities/utilities.h>
39#include <security_cdsa_utils/cuCdsaUtils.h>
40#include <security_utilities/globalizer.h>
41#include <security_utilities/threading.h>
42#include <security_utilities/logging.h>
43#include <vproc.h>
44
45class TransactionLock
46{
47private:
48	vproc_transaction_t mHandle;
49
50public:
51	TransactionLock() {mHandle = vproc_transaction_begin(NULL);}
52	~TransactionLock() {vproc_transaction_end(NULL, mHandle);}
53};
54
55
56
57#define OCSP_DB_FILE		"/private/var/db/crls/ocspcache.db"
58
59/* max default TTL currently 24 hours */
60#define OCSPD_CACHE_TTL		(60.0 * 60.0 * 24.0)
61
62#pragma mark ---- OCSPD database singleton ----
63
64class OcspdDatabase
65{
66	NOCOPY(OcspdDatabase)
67public:
68	OcspdDatabase();
69	~OcspdDatabase();
70
71	/* methods associated with public API of this module */
72	bool lookup(
73		SecAsn1CoderRef		coder,
74		const CSSM_DATA		&certID,
75		const CSSM_DATA		*localResponder,		// optional
76		CSSM_DATA			&derResp);				// RETURNED
77
78	void addResponse(
79		const CSSM_DATA		&ocspResp,				// DER encoded SecAsn1OCSPResponse
80		const CSSM_DATA		&URI);					// where it came from
81
82	void flushCertID(
83		const CSSM_DATA 	&certID);
84
85	void flushStale();
86
87private:
88	CSSM_RETURN dlAttach();
89	CSSM_RETURN dbCreate();
90	CSSM_RETURN dbOpen(bool doCreate);
91
92	/* see implementations for comments */
93	bool validateRecord(
94		const CSSM_DATA				&certID,
95		const CSSM_DATA				&recordData,	// raw OCSP response
96		const CSSM_DATA				&expireTime,	// the attribute data
97		CSSM_DB_UNIQUE_RECORD_PTR	recordPtr);
98
99	CSSM_RETURN lookupPriv(
100		/* search predicates, both optional */
101		const CSSM_DATA				*certID,
102		const CSSM_DATA				*localResponder,
103
104		/* always returned on success */
105		CSSM_HANDLE_PTR				resultHand,
106		CSSM_DB_UNIQUE_RECORD_PTR	*recordPtr,
107
108		/* optionally returned */
109		CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData,
110		CSSM_DATA_PTR				data);		// i.e., an encoded OCSP response
111
112
113	/* everything this module does is protected by this global lock */
114	Mutex					mLock;
115	CSSM_DL_DB_HANDLE		mDlDbHandle;
116};
117
118OcspdDatabase::OcspdDatabase()
119{
120	mDlDbHandle.DLHandle = 0;
121	mDlDbHandle.DBHandle = 0;
122}
123
124/* I believe that this code actually never executes */
125OcspdDatabase::~OcspdDatabase()
126{
127	if(mDlDbHandle.DBHandle != 0) {
128		CSSM_DL_DbClose(mDlDbHandle);
129		mDlDbHandle.DBHandle = 0;
130	}
131	if(mDlDbHandle.DLHandle != 0) {
132		CSSM_ModuleDetach(mDlDbHandle.DLHandle);
133		CSSM_ModuleUnload(&gGuidAppleFileDL, NULL, NULL);
134		mDlDbHandle.DLHandle = 0;
135	}
136}
137
138/*
139 * Ensure we're attached to AppleFileDL. Caller must hold mLock.
140 */
141CSSM_RETURN OcspdDatabase::dlAttach()
142{
143	if(mDlDbHandle.DLHandle != 0) {
144		return CSSM_OK;
145	}
146	ocspdDbDebug("OcspdDatabase: attaching to DL");
147	mDlDbHandle.DLHandle = attachCommon(&gGuidAppleFileDL, CSSM_SERVICE_DL);
148	if(mDlDbHandle.DLHandle == 0) {
149		Syslog::alert("Error loading AppleFileDL");
150		return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
151	}
152	return CSSM_OK;
153}
154
155/*
156 * Create the database, which caller has determined does not exist.
157 * Caller must hold mLock.
158 */
159CSSM_RETURN OcspdDatabase::dbCreate()
160{
161	assert(mDlDbHandle.DLHandle != 0);
162	assert(mDlDbHandle.DBHandle == 0);
163
164	ocspdDbDebug("OcspdDatabase: creating DB");
165	CSSM_DBINFO dbInfo;
166	memset(&dbInfo, 0, sizeof(dbInfo));
167	dbInfo.NumberOfRecordTypes = 1;
168	dbInfo.IsLocal = CSSM_TRUE;		// TBD - what does this mean?
169	dbInfo.AccessPath = NULL;		// TBD
170
171	/*
172	 * Alloc kNumOcspDbRelations elements for parsingModule, recordAttr,
173	 * and recordIndex info arrays
174	 */
175	unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * kNumOcspDbRelations;
176	dbInfo.DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size);
177	memset(dbInfo.DefaultParsingModules, 0, size);
178	size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * kNumOcspDbRelations;
179	dbInfo.RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size);
180	memset(dbInfo.RecordAttributeNames, 0, size);
181	size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * kNumOcspDbRelations;
182	dbInfo.RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size);
183	memset(dbInfo.RecordIndexes, 0, size);
184
185	/* cook up attribute and index info for each relation */
186	unsigned relation;
187	for(relation=0; relation<kNumOcspDbRelations; relation++) {
188		const OcspdDbRelationInfo *relp = &kOcspDbRelations[relation];	// source
189		CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo =
190			&dbInfo.RecordAttributeNames[relation];						// dest 1
191		CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo =
192			&dbInfo.RecordIndexes[relation];							// dest 2
193
194		attrInfo->DataRecordType = relp->recordType;
195		attrInfo->NumberOfAttributes = relp->numberOfAttributes;
196		attrInfo->AttributeInfo =
197			const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(relp->attrInfo);
198
199		indexInfo->DataRecordType = relp->recordType;
200		indexInfo->NumberOfIndexes = relp->numIndexes;
201		indexInfo->IndexInfo = const_cast<CSSM_DB_INDEX_INFO_PTR>(relp->indexInfo);
202	}
203
204	/* autocommit and mode */
205	CSSM_APPLEDL_OPEN_PARAMETERS openParams;
206	memset(&openParams, 0, sizeof(openParams));
207	openParams.length = sizeof(openParams);
208	openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION;
209	openParams.autoCommit = CSSM_TRUE;
210	/* ensure mode 644 */
211	openParams.mask = kCSSM_APPLEDL_MASK_MODE;
212	openParams.mode = 0644;
213
214	CSSM_RETURN crtn;
215	crtn = CSSM_DL_DbCreate(mDlDbHandle.DLHandle,
216		OCSP_DB_FILE,		// DbName is the same as file path
217		NULL,				// DbLocation
218		&dbInfo,
219		CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
220		NULL,				// CredAndAclEntry
221		&openParams,
222		&mDlDbHandle.DBHandle);
223	if(crtn) {
224		Syslog::alert("Error creating DB (%d)", crtn);
225	}
226	free(dbInfo.DefaultParsingModules);
227	free(dbInfo.RecordAttributeNames);
228	free(dbInfo.RecordIndexes);
229	return crtn;
230}
231
232/*
233 * Open, optionally creating. If !doCreate and DB doesn't exist,
234 * CSSMERR_DL_DATASTORE_DOESNOT_EXIST is returned. Any other error is
235 * a gross failure.
236 *
237 * Caller must hold mLock.
238 */
239CSSM_RETURN OcspdDatabase::dbOpen(bool doCreate)
240{
241	/* first ensure we're attached to the DL */
242	CSSM_RETURN crtn = dlAttach();
243	if(crtn) {
244		return crtn;
245	}
246	if(mDlDbHandle.DBHandle != 0) {
247		return CSSM_OK;
248	}
249	crtn = CSSM_DL_DbOpen(mDlDbHandle.DLHandle,
250		OCSP_DB_FILE,
251		NULL,			// DbLocation
252		CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
253		NULL, 			// CSSM_ACCESS_CREDENTIALS *AccessCred
254		NULL,			// void *OpenParameters
255		&mDlDbHandle.DBHandle);
256	switch(crtn) {
257		case CSSM_OK:
258			return CSSM_OK;
259		case CSSMERR_DL_DATASTORE_DOESNOT_EXIST:
260			if(!doCreate) {
261				/* just trying to read, doesn't exist */
262				ocspdDbDebug("OcspdDatabase: read access, DB does not yet exist");
263				return crtn;
264			}
265			else {
266				return dbCreate();
267			}
268		case CSSMERR_DL_DATABASE_CORRUPT:
269			/* corrupt database; delete and recreate it */
270			if(unlink(OCSP_DB_FILE)) {
271				Syslog::alert("Failed to remove OCSP cache (%d)", errno);
272			}
273			else {
274				Syslog::alert("Error opening OCSP cache (%d), recovering", crtn);
275				crtn = dbCreate();
276			}
277			return crtn;
278		default:
279			Syslog::alert("Error opening OCSP cache (%d)", crtn);
280			return crtn;
281	}
282}
283
284/*
285 * Free CSSM_DB_ATTRIBUTE_DATA
286 */
287static void freeAttrData(
288	CSSM_DB_ATTRIBUTE_DATA &attrData)
289{
290	if(attrData.Value == NULL) {
291		return;
292	}
293	for(unsigned dex=0; dex<attrData.NumberOfValues; dex++) {
294		CSSM_DATA_PTR d = &attrData.Value[dex];
295		if(d->Data) {
296			APP_FREE(d->Data);
297		}
298	}
299	APP_FREE(attrData.Value);
300}
301
302/*
303 * Validate a record found in the DB. Returns true if record is good to go.
304 * We delete the record if it's stale.
305 */
306bool OcspdDatabase::validateRecord(
307	const CSSM_DATA				&certID,
308	const CSSM_DATA				&recordData,	// raw OCSP response
309	const CSSM_DATA				&expireTime,	// the attribute data
310	CSSM_DB_UNIQUE_RECORD_PTR	recordPtr)
311{
312	/*
313	 * First off, if the entire record is stale, we're done.
314	 */
315	CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
316	CFAbsoluteTime cfExpireTime = genTimeToCFAbsTime(&expireTime);
317	if(now >= cfExpireTime) {
318		ocspdDbDebug("OcspdDatabase::validateRecord: record EXPIRED");
319		CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
320		return false;
321	}
322
323	/*
324	 * Parse: this should never fail since we did this when we first got the
325	 * response, and we just made sure it isn't stale.
326	 */
327	OCSPResponse *ocspResp = NULL;
328	bool ourRtn = false;
329	try {
330		ocspResp = new OCSPResponse(recordData, OCSPD_CACHE_TTL);
331	}
332	catch(...) {
333		Syslog::alert("Error parsing stored record");
334		return false;
335	}
336
337	/*
338	 * Find the singleResponse for this certID.
339	 */
340	OCSPSingleResponse *singleResp = ocspResp->singleResponseFor(certID);
341	if(singleResp != NULL) {
342		CFAbsoluteTime nextUpdate = singleResp->nextUpdate();
343		/* SingleResponses with no nextUpdate goes stale with the response as a whole */
344		if((nextUpdate != NULL_TIME) && (now > nextUpdate)) {
345			ocspdDbDebug("OcspdDatabase::validateRecord: SingleResponse EXPIRED");
346		}
347		else {
348			ourRtn = true;
349		}
350		delete singleResp;
351	}
352	else {
353		/* Not sure how this could happen */
354		ocspdDbDebug("OcspdDatabase::validateRecord: SingleResponse NOT FOUND");
355	}
356	delete ocspResp;
357	ocspdDbDebug("OcspdDatabase::validateRecord returning %s",
358		ourRtn ? "TRUE" : "FALSE");
359	return ourRtn;
360}
361
362/*
363 * Basic private lookup. Key on any or all attrs. On success, always returns a
364 * CSSM_DB_UNIQUE_RECORD_PTR (which caller must either use to delete the
365 * record or free with CSSM_DL_FreeUniqueRecord()) *AND* a CSSM_HANDLE_PTR
366 * ResultHandle(which caller must free with CSSM_DL_DataAbortQuery, possibly
367 * after doing some CSSM_DL_DataGetNext() ops with it). On success, also
368 * optionally returns CSSM_DB_RECORD_ATTRIBUTE_DATA which caller must free
369 * via APP_FREE().
370 *
371 * Caller holds mLock and has ensured the the DB is open.
372 */
373CSSM_RETURN OcspdDatabase::lookupPriv(
374	/* search predicates, both optional */
375	const CSSM_DATA		*certID,
376	const CSSM_DATA		*URI,
377
378	/* these two always returned on success */
379	CSSM_HANDLE_PTR				resultHandPtr,
380	CSSM_DB_UNIQUE_RECORD_PTR	*recordPtr,
381
382	/*
383	 * Optionally returned, in/out (in to specify which attrs, out as the
384	 * attrs fetched)
385	 */
386	CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData,
387
388	/* optionally returned - an encoded OCSP response */
389	CSSM_DATA_PTR				data)
390{
391	CSSM_QUERY						query;
392	CSSM_SELECTION_PREDICATE		predicate[2];
393	CSSM_SELECTION_PREDICATE		*predPtr = predicate;
394	CSSM_DB_ATTRIBUTE_INFO			certIDAttr = OCSPD_DBATTR_CERT_ID;
395	CSSM_DB_ATTRIBUTE_INFO			uriAttr = OCSPD_DBATTR_URI;
396
397	assert(resultHandPtr != NULL);
398	assert(recordPtr != NULL);
399	assert(mDlDbHandle.DBHandle != 0);
400
401	memset(&query, 0, sizeof(query));
402	query.RecordType = OCSPD_DB_RECORDTYPE;
403	query.Conjunctive = CSSM_DB_NONE;
404
405	if(certID) {
406		predPtr->DbOperator = CSSM_DB_EQUAL;
407		predPtr->Attribute.Info = certIDAttr;
408		predPtr->Attribute.NumberOfValues = 1;
409		predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(certID);
410		query.NumSelectionPredicates++;
411		query.SelectionPredicate = predicate;
412		predPtr++;
413	}
414	if(URI) {
415		predPtr->DbOperator = CSSM_DB_EQUAL;
416		predPtr->Attribute.Info = uriAttr;
417		predPtr->Attribute.NumberOfValues = 1;
418		predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(URI);
419		query.NumSelectionPredicates++;
420		query.SelectionPredicate = predicate;
421	}
422	if(data) {
423		query.QueryFlags = CSSM_QUERY_RETURN_DATA;	// FIXME - used?
424	}
425	if(query.NumSelectionPredicates > 1) {
426		query.Conjunctive = CSSM_DB_AND;
427	}
428	return CSSM_DL_DataGetFirst(mDlDbHandle, &query, resultHandPtr,
429		attrData, data, recordPtr);
430}
431
432/* methods associated with public API of this module */
433bool OcspdDatabase::lookup(
434	SecAsn1CoderRef		coder,
435	const CSSM_DATA		&certID,
436	const CSSM_DATA		*URI,			// optional
437	CSSM_DATA			&derResp)		// RETURNED
438{
439	TransactionLock tLock;
440	StLock<Mutex> _(mLock);
441	if(dbOpen(false)) {
442		return false;
443	}
444
445	CSSM_RETURN crtn;
446	CSSM_HANDLE resultHand;
447	CSSM_DB_UNIQUE_RECORD_PTR recordPtr;
448	CSSM_DATA resultData;			// if found, free via APP_FREE()
449	bool foundIt = false;
450
451	/* set up to retrieve just the expiration time */
452	CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrData;
453	memset(&recordAttrData, 0, sizeof(recordAttrData));
454	CSSM_DB_ATTRIBUTE_DATA attrData;
455	memset(&attrData, 0, sizeof(attrData));
456	CSSM_DB_ATTRIBUTE_INFO expireInfo = OCSPD_DBATTR_EXPIRATION;
457	attrData.Info = expireInfo;
458	recordAttrData.DataRecordType = OCSPD_DB_RECORDTYPE;
459	recordAttrData.NumberOfAttributes = 1;
460	recordAttrData.AttributeData = &attrData;
461
462	crtn = lookupPriv(&certID, URI, &resultHand, &recordPtr,
463		&recordAttrData, &resultData);
464	if(crtn) {
465		ocspdDbDebug("OcspdDatabase::lookup: MISS");
466		return false;
467	}
468	foundIt = validateRecord(certID, resultData, *attrData.Value, recordPtr);
469	/* done with attrs and the record itself regardless.... */
470	freeAttrData(attrData);
471	CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
472
473	if(!foundIt) {
474		/* no good. free what we just got and try again */
475		ocspdDbDebug("OcspdDatabase::lookup: invalid record (1)");
476		if(resultData.Data) {
477			APP_FREE(resultData.Data);
478			resultData.Data = NULL;
479			resultData.Length = 0;
480		}
481		do {
482			crtn = CSSM_DL_DataGetNext(mDlDbHandle, resultHand,
483				&recordAttrData, &resultData, &recordPtr);
484			if(crtn) {
485				/* done, not found */
486				break;
487			}
488			foundIt = validateRecord(certID, resultData, *attrData.Value, recordPtr);
489			ocspdDbDebug("OcspdDatabase::lookup: %s",
490				foundIt ? "HIT (2)" : "invalid record (2)");
491			freeAttrData(attrData);
492			CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
493			if(!foundIt && (resultData.Data != NULL)) {
494				APP_FREE(resultData.Data);
495				resultData.Data = NULL;
496				resultData.Length = 0;
497			}
498		/* break on full success, or end of DB search */
499		} while(!foundIt && (crtn == CSSM_OK));
500	}
501	else {
502		ocspdDbDebug("OcspdDatabase::lookup: HIT (1)");
503	}
504	CSSM_DL_DataAbortQuery(mDlDbHandle, resultHand);
505	if(foundIt) {
506		assert(resultData.Data != NULL);
507		SecAsn1AllocCopyItem(coder, &resultData, &derResp);
508		APP_FREE(resultData.Data);
509	}
510	return foundIt;
511}
512
513void OcspdDatabase::addResponse(
514	const CSSM_DATA		&ocspResp,				// DER encoded SecAsn1OCSPResponse
515	const CSSM_DATA		&URI)
516{
517	TransactionLock tLock;
518	StLock<Mutex> _(mLock);
519	ocspdDbDebug("addResponse: top");
520	if(dbOpen(true)) {
521		return;
522	}
523
524	/* open it up... */
525	OCSPResponse *resp = NULL;
526	try {
527		resp = new OCSPResponse(ocspResp, OCSPD_CACHE_TTL);
528	}
529	catch(...) {
530		ocspdDbDebug("addResponse: error parsing response");
531		return;
532	}
533	if(resp->responseStatus() != RS_Success) {
534		/* e.g., RS_Unauthorized */
535		ocspdDbDebug("addResponse: responseStatus %d, aborting", (int)resp->responseStatus());
536		delete resp;
537		return;
538	}
539
540	/*
541	 * Get expiration date in the form of the latest of all of the enclosed
542	 * SingleResponse nextUpdate fields.
543	 */
544	CFAbsoluteTime expireTime = resp->expireTime();
545	char expireStr[GENERAL_TIME_STRLEN+1];
546	cfAbsTimeToGgenTime(expireTime, expireStr);
547	CSSM_DATA expireData = {GENERAL_TIME_STRLEN, (uint8 *)expireStr};
548	CSSM_RETURN crtn;
549
550	CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
551	CSSM_DB_ATTRIBUTE_DATA attrData[OCSPD_NUM_DB_ATTRS];
552	CSSM_DB_UNIQUE_RECORD_PTR recordPtr = NULL;
553	memset(&recordAttrs, 0, sizeof(recordAttrs));
554
555	recordAttrs.DataRecordType = OCSPD_DB_RECORDTYPE;
556	recordAttrs.SemanticInformation = 0;			// what's this for?
557	recordAttrs.NumberOfAttributes = OCSPD_NUM_DB_ATTRS;
558	recordAttrs.AttributeData = attrData;
559
560	/*
561	 * Now fill in the attributes. CertID is unusual in that it can contain multiple
562	 * values, one for each SingleResponse.
563	 */
564	SecAsn1CoderRef coder;			// for tons of mallocs
565	SecAsn1CoderCreate(&coder);
566	const SecAsn1OCSPResponseData &respData = resp->responseData();
567	unsigned numSingleResps = ocspdArraySize((const void **)respData.responses);
568	CSSM_DB_ATTRIBUTE_INFO oneAttr = OCSPD_DBATTR_CERT_ID;
569	CSSM_DB_ATTRIBUTE_INFO twoAttr = OCSPD_DBATTR_URI;
570	CSSM_DB_ATTRIBUTE_INFO threeAttr = OCSPD_DBATTR_EXPIRATION;
571	attrData[0].Info = oneAttr;
572	attrData[0].NumberOfValues = numSingleResps;
573	#ifndef	NDEBUG
574	if(numSingleResps > 1) {
575		ocspdDbDebug("addResponse: MULTIPLE SINGLE RESPONSES (%u)", numSingleResps);
576	}
577	#endif
578	attrData[0].Value = (CSSM_DATA_PTR)SecAsn1Malloc(coder,
579		numSingleResps * sizeof(CSSM_DATA));
580	memset(attrData[0].Value, 0, numSingleResps * sizeof(CSSM_DATA));
581	for(unsigned dex=0; dex<numSingleResps; dex++) {
582		/*
583		 * Get this single response. SKIP IT if the hash algorithm is not
584		 * SHA1 since we do lookups in the DB by encoded value assuming SHA1
585		 * hash. Incoming responses with other hash values would never be found.
586		 */
587		SecAsn1OCSPSingleResponse *resp = respData.responses[dex];
588		SecAsn1OCSPCertID &certID = resp->certID;
589		if(!ocspdCompareCssmData(&certID.algId.algorithm, &CSSMOID_SHA1)) {
590			ocspdDbDebug("addResponse: SKIPPING resp due to nonstandard hash alg");
591			attrData[0].NumberOfValues--;
592			continue;
593		}
594		/* encode this certID as attr[0]value[dex] */
595		if(SecAsn1EncodeItem(coder, &certID, kSecAsn1OCSPCertIDTemplate,
596				&attrData[0].Value[dex])) {
597			ocspdErrorLog("OcspdDatabase::addResponse: encode error\n");
598			crtn = CSSMERR_TP_INTERNAL_ERROR;
599			goto errOut;
600		}
601	}
602
603	attrData[1].Info = twoAttr;
604	attrData[1].NumberOfValues = 1;
605	attrData[1].Value = const_cast<CSSM_DATA_PTR>(&URI);
606
607	attrData[2].Info = threeAttr;
608	attrData[2].NumberOfValues = 1;
609	attrData[2].Value = &expireData;
610
611	crtn = CSSM_DL_DataInsert(mDlDbHandle,
612		OCSPD_DB_RECORDTYPE,
613		&recordAttrs,
614		&ocspResp,
615		&recordPtr);
616	if(crtn) {
617		/* delete and recreate our cache if there is any error */
618		if(unlink(OCSP_DB_FILE)) {
619			Syslog::alert("Writing to OCSP cache failed (%d)", errno);
620		}
621		else {
622			Syslog::alert("Writing to OCSP cache failed (%d), recovering", crtn);
623			crtn = dbCreate();
624		}
625	}
626	else {
627		CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
628	}
629errOut:
630    if(crtn) {
631        ocspdDbDebug("addResponse: error %d", crtn);
632    }
633	delete resp;
634	SecAsn1CoderRelease(coder);
635}
636
637void OcspdDatabase::flushCertID(
638	const CSSM_DATA 	&certID)
639{
640	TransactionLock tLock;
641	StLock<Mutex> _(mLock);
642	if(dbOpen(false)) {
643		return;
644	}
645
646	CSSM_RETURN crtn;
647	CSSM_HANDLE resultHand;
648	CSSM_DB_UNIQUE_RECORD_PTR recordPtr;
649
650	/* just retrieve the record, no attrs, no data */
651	crtn = lookupPriv(&certID, NULL, &resultHand, &recordPtr, NULL, NULL);
652	if(crtn) {
653		ocspdDbDebug("OcspdDatabase::flushCertID: no such record");
654		return;
655	}
656	try {
657		ocspdDbDebug("OcspdDatabase::flushCertID: deleting (1)");
658		CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
659		CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
660
661		/* any more? */
662		do {
663			crtn = CSSM_DL_DataGetNext(mDlDbHandle, resultHand,	NULL, NULL, &recordPtr);
664			if(crtn) {
665				/* done, not found */
666				break;
667			}
668			ocspdDbDebug("OcspdDatabase::flushCertID: deleting (2)");
669			CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
670			CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
671		} while(crtn == CSSM_OK);
672		CSSM_DL_DataAbortQuery(mDlDbHandle, resultHand);
673	}
674	catch (...) {}; // <rdar://8833413>
675	return;
676}
677
678void OcspdDatabase::flushStale()
679{
680	TransactionLock tLock;
681	StLock<Mutex> _(mLock);
682	if(dbOpen(false)) {
683		return;
684	}
685
686	CSSM_RETURN crtn;
687	CSSM_HANDLE resultHand;
688	CSSM_DB_UNIQUE_RECORD_PTR recordPtr;
689
690	CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
691
692	/* retrieve all records, one attr (expiration time), no data */
693	CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrData;
694	memset(&recordAttrData, 0, sizeof(recordAttrData));
695	CSSM_DB_ATTRIBUTE_DATA attrData;
696	memset(&attrData, 0, sizeof(attrData));
697	CSSM_DB_ATTRIBUTE_INFO expireInfo = OCSPD_DBATTR_EXPIRATION;
698	attrData.Info = expireInfo;
699	recordAttrData.DataRecordType = OCSPD_DB_RECORDTYPE;
700	recordAttrData.NumberOfAttributes = 1;
701	recordAttrData.AttributeData = &attrData;
702
703	crtn = lookupPriv(NULL, NULL, &resultHand, &recordPtr, &recordAttrData, NULL);
704	if(crtn) {
705		ocspdDbDebug("OcspdDatabase::flushStale: no records found");
706		return;
707	}
708	try {
709		CFAbsoluteTime cfExpireTime = genTimeToCFAbsTime(attrData.Value);
710		if(now >= cfExpireTime) {
711			ocspdDbDebug("OcspdDatabase::flushStale: record EXPIRED");
712			CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
713		}
714		CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
715
716		/* any more? */
717		do {
718			crtn = CSSM_DL_DataGetNext(mDlDbHandle, resultHand,	NULL, NULL, &recordPtr);
719			if(crtn) {
720				/* done, not found */
721				break;
722			}
723			cfExpireTime = genTimeToCFAbsTime(attrData.Value);
724			if(now >= cfExpireTime) {
725				ocspdDbDebug("OcspdDatabase::flushStale: record EXPIRED");
726				CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
727			}
728			CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
729		} while(crtn == CSSM_OK);
730		CSSM_DL_DataAbortQuery(mDlDbHandle, resultHand);
731	}
732	catch (...) {}; // <rdar://8833413>
733	return;
734}
735
736static ModuleNexus<OcspdDatabase> gOcspdDatabase;
737
738
739#pragma mark ---- Public API ----
740
741/*
742 * Lookup cached response. Result is a DER-encoded OCSP response,t he same bits
743 * originally obtained from the net. Result is allocated in specified
744 * SecAsn1CoderRef's memory. Never returns a stale entry; we always check the
745 * enclosed SingleResponse for temporal validity.
746 *
747 * Just a boolean returned; we found it, or not.
748 */
749bool ocspdDbCacheLookup(
750	SecAsn1CoderRef		coder,
751	const CSSM_DATA		&certID,
752	const CSSM_DATA		*localResponder,		// optional
753	CSSM_DATA			&derResp)				// RETURNED
754{
755	return gOcspdDatabase().lookup(coder, certID, localResponder, derResp);
756}
757
758/*
759 * Add a OCSP response to cache. Incoming response is completely unverified;
760 * we just verify that we can parse it and is has at least one SingleResponse
761 * which is temporally valid.
762 */
763void ocspdDbCacheAdd(
764	const CSSM_DATA		&ocspResp,			// DER encoded SecAsn1OCSPResponse
765	const CSSM_DATA		&URI)				// where it came from
766{
767	gOcspdDatabase().addResponse(ocspResp, URI);
768}
769
770/*
771 * Delete any entry associated with specified certID from cache.
772 */
773void ocspdDbCacheFlush(
774	const CSSM_DATA 	&certID)
775{
776	gOcspdDatabase().flushCertID(certID);
777}
778
779/*
780 * Flush stale entries from cache.
781 */
782void ocspdDbCacheFlushStale()
783{
784	gOcspdDatabase().flushStale();
785}
786
787
788