#include "ocspdDb.h"
#include "attachCommon.h"
#include <security_ocspd/ocspdDbSchema.h>
#include <security_ocspd/ocspdDebug.h>
#include <security_ocspd/ocspResponse.h>
#include <security_ocspd/ocspdUtils.h>
#include <Security/Security.h>
#include <security_utilities/utilities.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_utilities/globalizer.h>
#include <security_utilities/threading.h>
#include <security_utilities/logging.h>
#include <vproc.h>
class TransactionLock
{
private:
vproc_transaction_t mHandle;
public:
TransactionLock() {mHandle = vproc_transaction_begin(NULL);}
~TransactionLock() {vproc_transaction_end(NULL, mHandle);}
};
#define OCSP_DB_FILE "/private/var/db/crls/ocspcache.db"
#define OCSPD_CACHE_TTL (60.0 * 60.0 * 24.0)
#pragma mark ---- OCSPD database singleton ----
class OcspdDatabase
{
NOCOPY(OcspdDatabase)
public:
OcspdDatabase();
~OcspdDatabase();
bool lookup(
SecAsn1CoderRef coder,
const CSSM_DATA &certID,
const CSSM_DATA *localResponder, CSSM_DATA &derResp);
void addResponse(
const CSSM_DATA &ocspResp, const CSSM_DATA &URI);
void flushCertID(
const CSSM_DATA &certID);
void flushStale();
private:
CSSM_RETURN dlAttach();
CSSM_RETURN dbCreate();
CSSM_RETURN dbOpen(bool doCreate);
bool validateRecord(
const CSSM_DATA &certID,
const CSSM_DATA &recordData, const CSSM_DATA &expireTime, CSSM_DB_UNIQUE_RECORD_PTR recordPtr);
CSSM_RETURN lookupPriv(
const CSSM_DATA *certID,
const CSSM_DATA *localResponder,
CSSM_HANDLE_PTR resultHand,
CSSM_DB_UNIQUE_RECORD_PTR *recordPtr,
CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData,
CSSM_DATA_PTR data);
Mutex mLock;
CSSM_DL_DB_HANDLE mDlDbHandle;
};
OcspdDatabase::OcspdDatabase()
{
mDlDbHandle.DLHandle = 0;
mDlDbHandle.DBHandle = 0;
}
OcspdDatabase::~OcspdDatabase()
{
if(mDlDbHandle.DBHandle != 0) {
CSSM_DL_DbClose(mDlDbHandle);
mDlDbHandle.DBHandle = 0;
}
if(mDlDbHandle.DLHandle != 0) {
CSSM_ModuleDetach(mDlDbHandle.DLHandle);
CSSM_ModuleUnload(&gGuidAppleFileDL, NULL, NULL);
mDlDbHandle.DLHandle = 0;
}
}
CSSM_RETURN OcspdDatabase::dlAttach()
{
if(mDlDbHandle.DLHandle != 0) {
return CSSM_OK;
}
ocspdDbDebug("ocspd: attaching to DL");
mDlDbHandle.DLHandle = attachCommon(&gGuidAppleFileDL, CSSM_SERVICE_DL);
if(mDlDbHandle.DLHandle == 0) {
Syslog::alert("Error loading AppleFileDL");
return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
}
return CSSM_OK;
}
CSSM_RETURN OcspdDatabase::dbCreate()
{
assert(mDlDbHandle.DLHandle != 0);
assert(mDlDbHandle.DBHandle == 0);
ocspdDbDebug("ocspd: creating DB");
CSSM_DBINFO dbInfo;
memset(&dbInfo, 0, sizeof(dbInfo));
dbInfo.NumberOfRecordTypes = 1;
dbInfo.IsLocal = CSSM_TRUE; dbInfo.AccessPath = NULL;
unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * kNumOcspDbRelations;
dbInfo.DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size);
memset(dbInfo.DefaultParsingModules, 0, size);
size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * kNumOcspDbRelations;
dbInfo.RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size);
memset(dbInfo.RecordAttributeNames, 0, size);
size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * kNumOcspDbRelations;
dbInfo.RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size);
memset(dbInfo.RecordIndexes, 0, size);
unsigned relation;
for(relation=0; relation<kNumOcspDbRelations; relation++) {
const OcspdDbRelationInfo *relp = &kOcspDbRelations[relation]; CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo =
&dbInfo.RecordAttributeNames[relation]; CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo =
&dbInfo.RecordIndexes[relation];
attrInfo->DataRecordType = relp->recordType;
attrInfo->NumberOfAttributes = relp->numberOfAttributes;
attrInfo->AttributeInfo =
const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(relp->attrInfo);
indexInfo->DataRecordType = relp->recordType;
indexInfo->NumberOfIndexes = relp->numIndexes;
indexInfo->IndexInfo = const_cast<CSSM_DB_INDEX_INFO_PTR>(relp->indexInfo);
}
CSSM_APPLEDL_OPEN_PARAMETERS openParams;
memset(&openParams, 0, sizeof(openParams));
openParams.length = sizeof(openParams);
openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION;
openParams.autoCommit = CSSM_TRUE;
openParams.mask = kCSSM_APPLEDL_MASK_MODE;
openParams.mode = 0644;
CSSM_RETURN crtn;
crtn = CSSM_DL_DbCreate(mDlDbHandle.DLHandle,
OCSP_DB_FILE, NULL, &dbInfo,
CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
NULL, &openParams,
&mDlDbHandle.DBHandle);
if(crtn) {
Syslog::alert("Error creating DB");
#ifndef NDEBUG
cssmPerror("CSSM_DL_DbCreate", crtn);
#endif
}
free(dbInfo.DefaultParsingModules);
free(dbInfo.RecordAttributeNames);
free(dbInfo.RecordIndexes);
return crtn;
}
CSSM_RETURN OcspdDatabase::dbOpen(bool doCreate)
{
CSSM_RETURN crtn = dlAttach();
if(crtn) {
return crtn;
}
if(mDlDbHandle.DBHandle != 0) {
return CSSM_OK;
}
crtn = CSSM_DL_DbOpen(mDlDbHandle.DLHandle,
OCSP_DB_FILE,
NULL, CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
NULL, NULL, &mDlDbHandle.DBHandle);
switch(crtn) {
case CSSM_OK:
return CSSM_OK;
case CSSMERR_DL_DATASTORE_DOESNOT_EXIST:
if(!doCreate) {
ocspdDbDebug("ocspd: read access, DB does not yet exist");
return crtn;
}
else {
return dbCreate();
}
default:
Syslog::alert("Error opening DB");
#ifndef NDEBUG
cssmPerror("CSSM_DL_DbOpen", crtn);
#endif
return crtn;
}
}
static void freeAttrData(
CSSM_DB_ATTRIBUTE_DATA &attrData)
{
if(attrData.Value == NULL) {
return;
}
for(unsigned dex=0; dex<attrData.NumberOfValues; dex++) {
CSSM_DATA_PTR d = &attrData.Value[dex];
if(d->Data) {
APP_FREE(d->Data);
}
}
APP_FREE(attrData.Value);
}
bool OcspdDatabase::validateRecord(
const CSSM_DATA &certID,
const CSSM_DATA &recordData, const CSSM_DATA &expireTime, CSSM_DB_UNIQUE_RECORD_PTR recordPtr)
{
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime cfExpireTime = genTimeToCFAbsTime(&expireTime);
if(now >= cfExpireTime) {
ocspdDbDebug("OcspdDatabase::validateRecord: record EXPIRED");
CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
return false;
}
OCSPResponse *ocspResp = NULL;
bool ourRtn = false;
try {
ocspResp = new OCSPResponse(recordData, OCSPD_CACHE_TTL);
}
catch(...) {
Syslog::alert("Error parsing stored record");
return false;
}
OCSPSingleResponse *singleResp = ocspResp->singleResponseFor(certID);
if(singleResp != NULL) {
CFAbsoluteTime nextUpdate = singleResp->nextUpdate();
if((nextUpdate != NULL_TIME) && (now > nextUpdate)) {
ocspdDbDebug("OcspdDatabase::validateRecord: SingleResponse EXPIRED");
}
else {
ourRtn = true;
}
delete singleResp;
}
else {
ocspdDbDebug("OcspdDatabase::validateRecord: SingleResponse NOT FOUND");
}
delete ocspResp;
ocspdDbDebug("OcspdDatabase::validateRecord returning %s",
ourRtn ? "TRUE" : "FALSE");
return ourRtn;
}
CSSM_RETURN OcspdDatabase::lookupPriv(
const CSSM_DATA *certID,
const CSSM_DATA *URI,
CSSM_HANDLE_PTR resultHandPtr,
CSSM_DB_UNIQUE_RECORD_PTR *recordPtr,
CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData,
CSSM_DATA_PTR data)
{
CSSM_QUERY query;
CSSM_SELECTION_PREDICATE predicate[2];
CSSM_SELECTION_PREDICATE *predPtr = predicate;
CSSM_DB_ATTRIBUTE_INFO certIDAttr = OCSPD_DBATTR_CERT_ID;
CSSM_DB_ATTRIBUTE_INFO uriAttr = OCSPD_DBATTR_URI;
assert(resultHandPtr != NULL);
assert(recordPtr != NULL);
assert(mDlDbHandle.DBHandle != 0);
memset(&query, 0, sizeof(query));
query.RecordType = OCSPD_DB_RECORDTYPE;
query.Conjunctive = CSSM_DB_NONE;
if(certID) {
predPtr->DbOperator = CSSM_DB_EQUAL;
predPtr->Attribute.Info = certIDAttr;
predPtr->Attribute.NumberOfValues = 1;
predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(certID);
query.NumSelectionPredicates++;
query.SelectionPredicate = predicate;
predPtr++;
}
if(URI) {
predPtr->DbOperator = CSSM_DB_EQUAL;
predPtr->Attribute.Info = uriAttr;
predPtr->Attribute.NumberOfValues = 1;
predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(URI);
query.NumSelectionPredicates++;
query.SelectionPredicate = predicate;
}
if(data) {
query.QueryFlags = CSSM_QUERY_RETURN_DATA; }
if(query.NumSelectionPredicates > 1) {
query.Conjunctive = CSSM_DB_AND;
}
return CSSM_DL_DataGetFirst(mDlDbHandle, &query, resultHandPtr,
attrData, data, recordPtr);
}
bool OcspdDatabase::lookup(
SecAsn1CoderRef coder,
const CSSM_DATA &certID,
const CSSM_DATA *URI, CSSM_DATA &derResp) {
TransactionLock tLock;
StLock<Mutex> _(mLock);
if(dbOpen(false)) {
return false;
}
CSSM_RETURN crtn;
CSSM_HANDLE resultHand;
CSSM_DB_UNIQUE_RECORD_PTR recordPtr;
CSSM_DATA resultData; bool foundIt = false;
CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrData;
memset(&recordAttrData, 0, sizeof(recordAttrData));
CSSM_DB_ATTRIBUTE_DATA attrData;
memset(&attrData, 0, sizeof(attrData));
CSSM_DB_ATTRIBUTE_INFO expireInfo = OCSPD_DBATTR_EXPIRATION;
attrData.Info = expireInfo;
recordAttrData.DataRecordType = OCSPD_DB_RECORDTYPE;
recordAttrData.NumberOfAttributes = 1;
recordAttrData.AttributeData = &attrData;
crtn = lookupPriv(&certID, URI, &resultHand, &recordPtr,
&recordAttrData, &resultData);
if(crtn) {
ocspdDbDebug("OcspdDatabase::lookup: MISS");
return false;
}
foundIt = validateRecord(certID, resultData, *attrData.Value, recordPtr);
freeAttrData(attrData);
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
if(!foundIt) {
ocspdDbDebug("OcspdDatabase::lookup: invalid record (1)");
if(resultData.Data) {
APP_FREE(resultData.Data);
resultData.Data = NULL;
resultData.Length = 0;
}
do {
crtn = CSSM_DL_DataGetNext(mDlDbHandle, resultHand,
&recordAttrData, &resultData, &recordPtr);
if(crtn) {
break;
}
foundIt = validateRecord(certID, resultData, *attrData.Value, recordPtr);
ocspdDbDebug("OcspdDatabase::lookup: %s",
foundIt ? "HIT (2)" : "invalid record (2)");
freeAttrData(attrData);
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
if(!foundIt && (resultData.Data != NULL)) {
APP_FREE(resultData.Data);
resultData.Data = NULL;
resultData.Length = 0;
}
} while(!foundIt && (crtn == CSSM_OK));
}
else {
ocspdDbDebug("OcspdDatabase::lookup: HIT (1)");
}
CSSM_DL_DataAbortQuery(mDlDbHandle, resultHand);
if(foundIt) {
assert(resultData.Data != NULL);
SecAsn1AllocCopyItem(coder, &resultData, &derResp);
APP_FREE(resultData.Data);
}
return foundIt;
}
void OcspdDatabase::addResponse(
const CSSM_DATA &ocspResp, const CSSM_DATA &URI)
{
TransactionLock tLock;
StLock<Mutex> _(mLock);
ocspdDbDebug("addResponse: top");
if(dbOpen(true)) {
return;
}
OCSPResponse *resp = NULL;
try {
resp = new OCSPResponse(ocspResp, OCSPD_CACHE_TTL);
}
catch(...) {
ocspdDbDebug("addResponse: error parsing response");
return;
}
if(resp->responseStatus() != RS_Success) {
ocspdDbDebug("addResponse: responseStatus %d, aborting", (int)resp->responseStatus());
delete resp;
return;
}
CFAbsoluteTime expireTime = resp->expireTime();
char expireStr[GENERAL_TIME_STRLEN+1];
cfAbsTimeToGgenTime(expireTime, expireStr);
CSSM_DATA expireData = {GENERAL_TIME_STRLEN, (uint8 *)expireStr};
CSSM_RETURN crtn;
CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
CSSM_DB_ATTRIBUTE_DATA attrData[OCSPD_NUM_DB_ATTRS];
CSSM_DB_UNIQUE_RECORD_PTR recordPtr = NULL;
memset(&recordAttrs, 0, sizeof(recordAttrs));
recordAttrs.DataRecordType = OCSPD_DB_RECORDTYPE;
recordAttrs.SemanticInformation = 0; recordAttrs.NumberOfAttributes = OCSPD_NUM_DB_ATTRS;
recordAttrs.AttributeData = attrData;
SecAsn1CoderRef coder; SecAsn1CoderCreate(&coder);
const SecAsn1OCSPResponseData &respData = resp->responseData();
unsigned numSingleResps = ocspdArraySize((const void **)respData.responses);
CSSM_DB_ATTRIBUTE_INFO oneAttr = OCSPD_DBATTR_CERT_ID;
CSSM_DB_ATTRIBUTE_INFO twoAttr = OCSPD_DBATTR_URI;
CSSM_DB_ATTRIBUTE_INFO threeAttr = OCSPD_DBATTR_EXPIRATION;
attrData[0].Info = oneAttr;
attrData[0].NumberOfValues = numSingleResps;
#ifndef NDEBUG
if(numSingleResps > 1) {
ocspdDbDebug("addResponse: MULTIPLE SINGLE RESPONSES (%u)", numSingleResps);
}
#endif
attrData[0].Value = (CSSM_DATA_PTR)SecAsn1Malloc(coder,
numSingleResps * sizeof(CSSM_DATA));
memset(attrData[0].Value, 0, numSingleResps * sizeof(CSSM_DATA));
for(unsigned dex=0; dex<numSingleResps; dex++) {
SecAsn1OCSPSingleResponse *resp = respData.responses[dex];
SecAsn1OCSPCertID &certID = resp->certID;
if(!ocspdCompareCssmData(&certID.algId.algorithm, &CSSMOID_SHA1)) {
ocspdDbDebug("addResponse: SKIPPING resp due to nonstandard hash alg");
attrData[0].NumberOfValues--;
continue;
}
if(SecAsn1EncodeItem(coder, &certID, kSecAsn1OCSPCertIDTemplate,
&attrData[0].Value[dex])) {
ocspdErrorLog("OcspdDatabase::addResponse: encode error\n");
crtn = CSSMERR_TP_INTERNAL_ERROR;
goto errOut;
}
}
attrData[1].Info = twoAttr;
attrData[1].NumberOfValues = 1;
attrData[1].Value = const_cast<CSSM_DATA_PTR>(&URI);
attrData[2].Info = threeAttr;
attrData[2].NumberOfValues = 1;
attrData[2].Value = &expireData;
crtn = CSSM_DL_DataInsert(mDlDbHandle,
OCSPD_DB_RECORDTYPE,
&recordAttrs,
&ocspResp,
&recordPtr);
if(crtn) {
Syslog::alert("Error writing to DB");
#ifndef NDEBUG
cssmPerror("CSSM_DL_DbOpen", crtn);
#endif
}
else {
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
}
errOut:
delete resp;
SecAsn1CoderRelease(coder);
}
void OcspdDatabase::flushCertID(
const CSSM_DATA &certID)
{
TransactionLock tLock;
StLock<Mutex> _(mLock);
if(dbOpen(false)) {
return;
}
CSSM_RETURN crtn;
CSSM_HANDLE resultHand;
CSSM_DB_UNIQUE_RECORD_PTR recordPtr;
crtn = lookupPriv(&certID, NULL, &resultHand, &recordPtr, NULL, NULL);
if(crtn) {
ocspdDbDebug("OcspdDatabase::flushCertID: no such record");
return;
}
try {
ocspdDbDebug("OcspdDatabase::flushCertID: deleting (1)");
CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
do {
crtn = CSSM_DL_DataGetNext(mDlDbHandle, resultHand, NULL, NULL, &recordPtr);
if(crtn) {
break;
}
ocspdDbDebug("OcspdDatabase::flushCertID: deleting (2)");
CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
} while(crtn == CSSM_OK);
CSSM_DL_DataAbortQuery(mDlDbHandle, resultHand);
}
catch (...) {}; return;
}
void OcspdDatabase::flushStale()
{
TransactionLock tLock;
StLock<Mutex> _(mLock);
if(dbOpen(false)) {
return;
}
CSSM_RETURN crtn;
CSSM_HANDLE resultHand;
CSSM_DB_UNIQUE_RECORD_PTR recordPtr;
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrData;
memset(&recordAttrData, 0, sizeof(recordAttrData));
CSSM_DB_ATTRIBUTE_DATA attrData;
memset(&attrData, 0, sizeof(attrData));
CSSM_DB_ATTRIBUTE_INFO expireInfo = OCSPD_DBATTR_EXPIRATION;
attrData.Info = expireInfo;
recordAttrData.DataRecordType = OCSPD_DB_RECORDTYPE;
recordAttrData.NumberOfAttributes = 1;
recordAttrData.AttributeData = &attrData;
crtn = lookupPriv(NULL, NULL, &resultHand, &recordPtr, &recordAttrData, NULL);
if(crtn) {
ocspdDbDebug("OcspdDatabase::flushStale: no records found");
return;
}
try {
CFAbsoluteTime cfExpireTime = genTimeToCFAbsTime(attrData.Value);
if(now >= cfExpireTime) {
ocspdDbDebug("OcspdDatabase::flushStale: record EXPIRED");
CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
}
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
do {
crtn = CSSM_DL_DataGetNext(mDlDbHandle, resultHand, NULL, NULL, &recordPtr);
if(crtn) {
break;
}
cfExpireTime = genTimeToCFAbsTime(attrData.Value);
if(now >= cfExpireTime) {
ocspdDbDebug("OcspdDatabase::flushStale: record EXPIRED");
CSSM_DL_DataDelete(mDlDbHandle, recordPtr);
}
CSSM_DL_FreeUniqueRecord(mDlDbHandle, recordPtr);
} while(crtn == CSSM_OK);
CSSM_DL_DataAbortQuery(mDlDbHandle, resultHand);
}
catch (...) {}; return;
}
static ModuleNexus<OcspdDatabase> gOcspdDatabase;
#pragma mark ---- Public API ----
bool ocspdDbCacheLookup(
SecAsn1CoderRef coder,
const CSSM_DATA &certID,
const CSSM_DATA *localResponder, CSSM_DATA &derResp) {
return gOcspdDatabase().lookup(coder, certID, localResponder, derResp);
}
void ocspdDbCacheAdd(
const CSSM_DATA &ocspResp, const CSSM_DATA &URI) {
gOcspdDatabase().addResponse(ocspResp, URI);
}
void ocspdDbCacheFlush(
const CSSM_DATA &certID)
{
gOcspdDatabase().flushCertID(certID);
}
void ocspdDbCacheFlushStale()
{
gOcspdDatabase().flushStale();
}