#include "crlDb.h"
#include "attachCommon.h"
#include "crlRefresh.h"
#include <security_utilities/utilities.h>
#include <security_utilities/logging.h>
#include <security_utilities/globalizer.h>
#include <security_utilities/threading.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_cdsa_utils/cuDbUtils.h>
#include <security_cdsa_utilities/cssmerrors.h>
#include <security_ocspd/ocspdDebug.h>
#include <security_cdsa_client/keychainacl.h>
#include <security_cdsa_client/aclclient.h>
#include <sys/types.h>
#include <sys/stat.h>
#define CRL_CACHE_DB "/private/var/db/crls/crlcache.db"
#pragma mark ---- OCSPD database singleton ----
class CrlDatabase
{
NOCOPY(CrlDatabase)
public:
CrlDatabase();
~CrlDatabase();
bool lookup(
Allocator &alloc,
const CSSM_DATA *url,
const CSSM_DATA *issuer, const CSSM_DATA &verifyTime,
CSSM_DATA &crlData);
CSSM_RETURN add(
const CSSM_DATA &crlData, const CSSM_DATA &url);
void flush(
const CSSM_DATA &url);
void refresh(
unsigned staleDays,
unsigned expireOverlapSeconds,
bool purgeAll,
bool fullCryptoVerify,
bool doRefresh);
private:
CSSM_RETURN openDatabase(
const char *dbFileName,
CSSM_DB_HANDLE &dbHand, bool &didCreate);
void closeDatabase(
CSSM_DB_HANDLE dbHand);
CSSM_RETURN lookupPriv(
CSSM_DB_HANDLE dbHand,
const CSSM_DATA *url,
const CSSM_DATA *issuer,
const CSSM_DATA *verifyTime,
CSSM_HANDLE_PTR resultHandPtr,
CSSM_DB_UNIQUE_RECORD_PTR *recordPtr,
CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData,
CSSM_DATA *crlData);
Mutex mLock;
CSSM_DL_HANDLE mDlHand;
CSSM_CL_HANDLE mClHand;
};
CrlDatabase::CrlDatabase()
: mDlHand(0), mClHand(0)
{
mDlHand = attachCommon(&gGuidAppleCSPDL, CSSM_SERVICE_DL);
if(mDlHand == 0) {
Syslog::alert("Error loading AppleFileDL");
CssmError::throwMe(CSSMERR_CSSM_ADDIN_LOAD_FAILED);
}
mClHand = attachCommon(&gGuidAppleX509CL, CSSM_SERVICE_CL);
if(mClHand == 0) {
Syslog::alert("Error loading AppleX509CL");
CssmError::throwMe(CSSMERR_CSSM_ADDIN_LOAD_FAILED);
}
}
CrlDatabase::~CrlDatabase()
{
if(mDlHand != 0) {
detachCommon(&gGuidAppleCSPDL, mDlHand);
}
if(mClHand != 0) {
detachCommon(&gGuidAppleX509CL, mClHand);
}
}
CSSM_RETURN CrlDatabase::openDatabase(
const char *dbFileName,
CSSM_DB_HANDLE &dbHand, bool &didCreate) {
didCreate = false;
CSSM_RETURN crtn = CSSM_DL_DbOpen(mDlHand,
dbFileName,
NULL, CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
NULL, NULL, &dbHand);
switch(crtn) {
case CSSM_OK:
return CSSM_OK;
case CSSMERR_DL_DATASTORE_DOESNOT_EXIST:
break;
default:
ocspdErrorLog("CrlDatabase::openDatabase: CSSM_DL_DbOpen returned %u",
(unsigned)crtn);
return crtn;
}
CSSM_DBINFO dbInfo;
memset(&dbInfo, 0, sizeof(CSSM_DBINFO));
Security::Allocator &alloc = Security::Allocator::standard();
CssmClient::AclFactory::PasswordChangeCredentials pCreds((StringData(dbFileName)),
alloc);
const AccessCredentials* aa = pCreds;
TypedList subject(alloc, CSSM_ACL_SUBJECT_TYPE_ANY);
AclEntryPrototype protoType(subject);
AuthorizationGroup &authGroup = protoType.authorization();
CSSM_ACL_AUTHORIZATION_TAG tag = CSSM_ACL_AUTHORIZATION_ANY;
authGroup.NumberOfAuthTags = 1;
authGroup.AuthTags = &tag;
const ResourceControlContext rcc(protoType, const_cast<AccessCredentials *>(aa));
crtn = CSSM_DL_DbCreate(mDlHand,
dbFileName,
NULL, &dbInfo,
CSSM_DB_ACCESS_PRIVILEGED,
&rcc, NULL, &dbHand);
if(crtn) {
ocspdErrorLog("CrlDatabase::openDatabase: CSSM_DL_DbCreate returned %u", (unsigned)crtn);
return crtn;
}
else {
if(chmod(dbFileName, 0644)) {
ocspdErrorLog("CrlDatabase::openDatabase: chmod error");
crtn = CSSMERR_DL_DB_LOCKED;
}
else {
didCreate = true;
}
}
return crtn;
}
void CrlDatabase::closeDatabase(
CSSM_DB_HANDLE dbHand)
{
assert(mDlHand != 0);
if(dbHand != 0) {
CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand};
CSSM_DL_DbClose(dlDbHand);
}
}
CSSM_RETURN CrlDatabase::lookupPriv(
CSSM_DB_HANDLE dbHand,
const CSSM_DATA *url,
const CSSM_DATA *issuer,
const CSSM_DATA *verifyTime,
CSSM_HANDLE_PTR resultHandPtr,
CSSM_DB_UNIQUE_RECORD_PTR *recordPtr,
CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData,
CSSM_DATA *crlData)
{
CSSM_QUERY query;
CSSM_SELECTION_PREDICATE pred[4];
CSSM_SELECTION_PREDICATE *predPtr = pred;
CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand};
assert(mDlHand != 0);
assert(dbHand != 0);
assert(resultHandPtr != NULL);
assert(recordPtr != NULL);
if(url) {
predPtr->DbOperator = CSSM_DB_EQUAL;
predPtr->Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
predPtr->Attribute.Info.Label.AttributeName = (char*) "URI";
predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(url);
predPtr->Attribute.NumberOfValues = 1;
predPtr++;
}
if(issuer) {
predPtr->DbOperator = CSSM_DB_EQUAL;
predPtr->Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
predPtr->Attribute.Info.Label.AttributeName = (char*) "Issuer";
predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(issuer);
predPtr->Attribute.NumberOfValues = 1;
predPtr++;
}
if(verifyTime) {
predPtr->DbOperator = CSSM_DB_LESS_THAN;
predPtr->Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
predPtr->Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(verifyTime);
predPtr->Attribute.NumberOfValues = 1;
predPtr++;
predPtr->DbOperator = CSSM_DB_GREATER_THAN;
predPtr->Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
predPtr->Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
predPtr->Attribute.Value = const_cast<CSSM_DATA_PTR>(verifyTime);
predPtr->Attribute.NumberOfValues = 1;
predPtr++;
}
query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
query.Conjunctive = CSSM_DB_AND;
query.NumSelectionPredicates = predPtr - pred;
query.SelectionPredicate = pred;
query.QueryLimits.TimeLimit = 0; query.QueryLimits.SizeLimit = 1; query.QueryFlags = crlData ? CSSM_QUERY_RETURN_DATA : 0;
return CSSM_DL_DataGetFirst(dlDbHand,
&query,
resultHandPtr,
attrData,
crlData,
recordPtr);
}
bool CrlDatabase::lookup(
Allocator &alloc,
const CSSM_DATA *url,
const CSSM_DATA *issuer,
const CSSM_DATA &verifyTime,
CSSM_DATA &crlData) {
StLock<Mutex> _(mLock);
CSSM_DB_HANDLE dbHand;
bool didCreate;
crlData.Data = NULL;
crlData.Length = 0;
if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) {
ocspdErrorLog("CrlDatabase::lookup: no cache DB\n");
return false;
}
if(didCreate) {
ocspdCrlDebug("CrlDatabase::lookup: empty cache DB");
closeDatabase(dbHand);
return false;
}
CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
CSSM_DATA dbCrl = {0, NULL};
bool ourRtn = false;
CSSM_HANDLE resultHand = 0;
CSSM_RETURN crtn;
CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand};
crtn = lookupPriv(dbHand, url, issuer, &verifyTime, &resultHand, &record, NULL, &dbCrl);
if(resultHand) {
CSSM_DL_DataAbortQuery(dlDbHand, resultHand);
}
if(record != NULL) {
if(dbCrl.Data != NULL) {
crlData.Data = (uint8 *)alloc.malloc(dbCrl.Length);
crlData.Length = dbCrl.Length;
memmove(crlData.Data, dbCrl.Data, dbCrl.Length);
ourRtn = true;
APP_FREE(dbCrl.Data);
ocspdCrlDebug("CrlDatabase::lookup: cache HIT");
}
else {
ocspdCrlDebug("CrlDatabase::lookup: DB read succeeded, but no data");
}
CSSM_DL_FreeUniqueRecord(dlDbHand, record);
}
closeDatabase(dbHand);
return ourRtn;
}
CSSM_RETURN CrlDatabase::add(
const CSSM_DATA &crlData, const CSSM_DATA &url) {
StLock<Mutex> _(mLock);
bool didCreate;
CSSM_DB_HANDLE dbHand;
if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) {
ocspdErrorLog("CrlDatabase::add: no cache DB\n");
return false;
}
CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand};
CSSM_RETURN crtn = cuAddCrlToDb(dlDbHand, mClHand, &crlData, &url);
if(crtn) {
ocspdErrorLog("CrlDatabase::add: error %ld adding CRL to DB\n", (long)crtn);
}
else {
ocspdCrlDebug("CrlDatabase::add: CRL added to DB");
}
closeDatabase(dbHand);
return crtn;
}
void CrlDatabase::flush(
const CSSM_DATA &url)
{
StLock<Mutex> _(mLock);
bool didCreate;
CSSM_DB_HANDLE dbHand;
if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) {
ocspdErrorLog("CrlDatabase::flush: no cache DB\n");
return;
}
if(didCreate) {
ocspdCrlDebug("CrlDatabase::flush: empty cache DB");
closeDatabase(dbHand);
return;
}
CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
CSSM_HANDLE resultHand = 0;
CSSM_RETURN crtn;
CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand};
crtn = lookupPriv(dbHand,
&url,
NULL, NULL, &resultHand, &record,
NULL, NULL); if(crtn) {
ocspdCrlDebug("CrlDatabase::flush: no records found");
goto done;
}
ocspdCrlDebug("CrlDatabase::flush: deleting a CRL");
CSSM_DL_DataDelete(dlDbHand, record);
CSSM_DL_FreeUniqueRecord(dlDbHand, record);
do {
crtn = CSSM_DL_DataGetNext(dlDbHand, resultHand, NULL, NULL, &record);
if(crtn) {
break;
}
ocspdCrlDebug("CrlDatabase::flush: deleting a CRL");
CSSM_DL_DataDelete(dlDbHand, record);
CSSM_DL_FreeUniqueRecord(dlDbHand, record);
} while(crtn == CSSM_OK);
CSSM_DL_DataAbortQuery(dlDbHand, resultHand);
done:
closeDatabase(dbHand);
return;
}
void CrlDatabase::refresh(
unsigned staleDays,
unsigned expireOverlapSeconds,
bool purgeAll,
bool fullCryptoVerify,
bool doRefresh)
{
StLock<Mutex> _(mLock);
bool didCreate;
CSSM_DB_HANDLE dbHand;
if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) {
ocspdErrorLog("CrlDatabase::refresh: no cache DB\n");
return;
}
if(didCreate) {
ocspdCrlDebug("CrlDatabase::refresh: empty cache DB");
closeDatabase(dbHand);
return;
}
CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand};
ocspdCrlRefresh(dlDbHand, mClHand, staleDays, expireOverlapSeconds,
purgeAll, fullCryptoVerify, doRefresh);
closeDatabase(dbHand);
return;
}
static ModuleNexus<CrlDatabase> gCrlDatabase;
#pragma mark ----- Public API -----
bool crlCacheLookup(
Allocator &alloc,
const CSSM_DATA *url,
const CSSM_DATA *issuer,
const CSSM_DATA &verifyTime,
CSSM_DATA &crlData) {
return gCrlDatabase().lookup(alloc, url, issuer, verifyTime, crlData);
}
CSSM_RETURN crlCacheAdd(
const CSSM_DATA &crlData, const CSSM_DATA &url) {
return gCrlDatabase().add(crlData, url);
}
void crlCacheFlush(
const CSSM_DATA &url)
{
return gCrlDatabase().flush(url);
}
void crlCacheRefresh(
unsigned staleDays,
unsigned expireOverlapSeconds,
bool purgeAll,
bool fullCryptoVerify,
bool doRefresh)
{
gCrlDatabase().refresh(staleDays, expireOverlapSeconds, purgeAll, fullCryptoVerify, doRefresh);
}