#include "crlRefresh.h"
#include "attachCommon.h"
#include "ocspdNetwork.h"
#include <security_ocspd/ocspdDebug.h>
#include <assert.h>
#include <Security/cssmapi.h>
#include <Security/SecTrust.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_cdsa_utils/cuDbUtils.h>
#include <security_cdsa_utils/cuTimeStr.h>
#include <security_utilities/logging.h>
#include <Security/TrustSettingsSchema.h>
#define DEFAULT_STALE_DAYS 10
#define DEFAULT_EXPIRE_OVERLAP_SECONDS 3600
#define SECONDS_PER_DAY (60 * 60 * 24)
#define CRL_CACHE_DB "/var/db/crls/crlcache.db"
#pragma mark ----- DB attribute stuff -----
#define DB_ATTRIBUTE(name, type) \
{ CSSM_DB_ATTRIBUTE_NAME_AS_STRING, \
{(char*) #name}, \
CSSM_DB_ATTRIBUTE_FORMAT_ ## type \
}
static const CSSM_DB_ATTRIBUTE_INFO x509CrlRecordAttrs[] = {
DB_ATTRIBUTE(CrlType, UINT32), DB_ATTRIBUTE(CrlEncoding, UINT32), DB_ATTRIBUTE(PrintName, BLOB), DB_ATTRIBUTE(Issuer, BLOB), DB_ATTRIBUTE(NextUpdate, BLOB), DB_ATTRIBUTE(URI, BLOB),
};
#define NUM_CRL_ATTRS \
(sizeof(x509CrlRecordAttrs) / sizeof(x509CrlRecordAttrs[0]))
#define ATTR_DEX_CRL_TYPE 0
#define ATTR_DEX_CRL_ENC 1
#define ATTR_DEX_PRINT_NAME 2
#define ATTR_DEX_ISSUER 3
#define ATTR_DEX_NEXT_UPDATE 4
#define ATTR_DEX_URI 5
static void freeAttrs(
CSSM_DB_ATTRIBUTE_DATA *attrs,
unsigned numAttrs)
{
unsigned i;
for(i=0; i<numAttrs; i++) {
CSSM_DB_ATTRIBUTE_DATA_PTR attrData = &attrs[i];
unsigned j;
for(j=0; j<attrData->NumberOfValues; j++) {
CSSM_DATA_PTR data = &attrData->Value[j];
if(data == NULL) {
ocspdErrorLog("***freeAttrs screwup: NULL data\n");
return;
}
APP_FREE(data->Data);
data->Data = NULL;
data->Length = 0;
}
APP_FREE(attrData->Value);
attrData->Value = NULL;
}
}
static int compareTimes(
const char *t1,
const char *t2)
{
for(unsigned dex=0; dex<CSSM_TIME_STRLEN; dex++, t1++, t2++) {
if(*t1 > *t2) {
return 1;
}
if(*t1 < *t2) {
return -1;
}
}
return 0;
}
#ifndef NDEBUG
static void printString(const char *prefixStr, const CSSM_DATA *val)
{
unsigned len = val->Length;
char *s = (char *)malloc(len + 1);
memmove(s, val->Data, len);
s[len] = '\0';
ocspdCrlDebug("%s: %s", prefixStr, s);
free(s);
}
#else
#define printString(p, s)
#endif
#pragma mark ----- CrlInfo class -----
class CrlInfo
{
public:
CrlInfo(
CSSM_DL_DB_HANDLE dlDbHand,
CSSM_DB_ATTRIBUTE_DATA *attrData, CSSM_DB_UNIQUE_RECORD_PTR record,
CSSM_DATA_PTR crlBlob); ~CrlInfo();
CSSM_DATA_PTR fetchValidAttr(
unsigned attrDex);
int fetchIntAttr(
unsigned dex,
uint32 &rtn);
bool isSameIssuer(
CrlInfo *other);
void printName(const char *prefixStr);
void validateTimes(
const char *updateTime,
const char *staleTime,
unsigned dex);
bool mIsBadlyFormed; bool mIsExpired; bool mIsStale; bool mRefreshed;
CSSM_DATA mCrlBlob;
CSSM_DL_DB_HANDLE dlDbHand() { return mDlDbHand; }
CSSM_DB_ATTRIBUTE_DATA_PTR attrData() { return &mAttrData[0]; }
CSSM_DB_UNIQUE_RECORD_PTR record() { return mRecord; };
private:
CSSM_DL_DB_HANDLE mDlDbHand;
CSSM_DB_ATTRIBUTE_DATA mAttrData[NUM_CRL_ATTRS];
CSSM_DB_UNIQUE_RECORD_PTR mRecord;
};
CrlInfo::CrlInfo(
CSSM_DL_DB_HANDLE dlDbHand,
CSSM_DB_ATTRIBUTE_DATA *attrData, CSSM_DB_UNIQUE_RECORD_PTR record,
CSSM_DATA_PTR crlBlob) : mIsBadlyFormed(false),
mIsExpired(false),
mIsStale(false),
mRefreshed(false),
mDlDbHand(dlDbHand),
mRecord(record)
{
if(crlBlob) {
mCrlBlob = *crlBlob;
}
else {
mCrlBlob.Data = NULL;
mCrlBlob.Length = 0;
}
memmove(mAttrData, attrData,
sizeof(CSSM_DB_ATTRIBUTE_DATA) * NUM_CRL_ATTRS);
}
CrlInfo::~CrlInfo()
{
freeAttrs(&mAttrData[0], NUM_CRL_ATTRS);
CSSM_DL_FreeUniqueRecord(mDlDbHand, mRecord);
if(mCrlBlob.Data) {
APP_FREE(mCrlBlob.Data);
}
}
CSSM_DATA_PTR CrlInfo::fetchValidAttr(
unsigned attrDex)
{
if(mAttrData[attrDex].NumberOfValues != 1) {
return NULL;
}
return mAttrData[attrDex].Value;
}
int CrlInfo::fetchIntAttr(
unsigned dex,
uint32 &rtn)
{
CSSM_DATA *val = fetchValidAttr(dex);
if((val == NULL) || (val->Length != sizeof(uint32))) {
ocspdErrorLog("CrlInfo::fetchIntAttr: Badly formed uint32 attr at dex %u\n",
dex);
mIsBadlyFormed = true;
return 1;
}
rtn = *(uint32 *)val->Data;
return 0;
}
bool CrlInfo::isSameIssuer(
CrlInfo *other)
{
CSSM_DATA_PTR thisIssuer = fetchValidAttr(ATTR_DEX_ISSUER);
if(thisIssuer == NULL) {
return false;
}
CSSM_DATA_PTR otherIssuer = other->fetchValidAttr(ATTR_DEX_ISSUER);
if(otherIssuer == NULL) {
return false;
}
return cuCompareCssmData(thisIssuer, otherIssuer) ? true : false;
}
void CrlInfo::printName(const char *prefixStr)
{
#ifndef NDEBUG
CSSM_DATA_PTR val = fetchValidAttr(ATTR_DEX_PRINT_NAME);
if(val == NULL) {
ocspdCrlDebug("%s: X509 CRL <no name>", prefixStr);
}
else {
printString(prefixStr, val);
}
#endif
}
void CrlInfo::validateTimes(
const char *updateTime, const char *staleTime, unsigned dex) {
CSSM_DATA *nextUpdateData = fetchValidAttr(ATTR_DEX_NEXT_UPDATE);
if((nextUpdateData == NULL) ||
(nextUpdateData->Length != CSSM_TIME_STRLEN)) {
ocspdErrorLog("CrlInfo::validateTimes: Badly formed NextUpdate attr on "
"CRL %u", dex);
mIsBadlyFormed = true;
return;
}
printString("NextUpdate ", nextUpdateData);
char *nextUpdate = (char *)nextUpdateData->Data;
if(compareTimes(nextUpdate, updateTime) < 0) {
ocspdCrlDebug("...CRL %u is expired", dex);
mIsExpired = true;
if(compareTimes(nextUpdate, staleTime) < 0) {
ocspdCrlDebug("...CRL %u is stale", dex);
mIsStale = true;
}
}
}
#pragma mark ----- private routines -----
static CSSM_RETURN fetchAllCrls(
CSSM_DL_DB_HANDLE dlDbHand,
bool fetchBlobs, CrlInfo **&rtnCrlInfo, unsigned &numCrls) {
CSSM_QUERY query;
CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
CSSM_RETURN crtn;
CSSM_HANDLE resultHand;
unsigned attrDex;
CSSM_DB_ATTRIBUTE_DATA attrData[NUM_CRL_ATTRS];
CSSM_DATA_PTR crlDataPtr = NULL;
CSSM_DATA crlData;
numCrls = 0;
rtnCrlInfo = NULL;
memset(attrData, 0, sizeof(CSSM_DB_ATTRIBUTE_DATA) * NUM_CRL_ATTRS);
for(attrDex=0; attrDex<NUM_CRL_ATTRS; attrDex++) {
attrData[attrDex].Info = x509CrlRecordAttrs[attrDex];
}
recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CRL;
recordAttrs.NumberOfAttributes = NUM_CRL_ATTRS;
recordAttrs.AttributeData = &attrData[0];
query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
query.Conjunctive = CSSM_DB_NONE;
query.NumSelectionPredicates = 0;
query.SelectionPredicate = NULL;
query.QueryLimits.TimeLimit = 0; query.QueryLimits.SizeLimit = 1; query.QueryFlags = 0;
if(fetchBlobs) {
crlDataPtr = &crlData;
}
crtn = CSSM_DL_DataGetFirst(dlDbHand,
&query,
&resultHand,
&recordAttrs,
crlDataPtr,
&record);
switch(crtn) {
case CSSM_OK:
break; case CSSMERR_DL_ENDOFDATA:
return CSSM_OK;
case CSSMERR_DL_INVALID_RECORDTYPE:
return CSSM_OK;
default:
ocspdErrorLog("fetchAllCrls: DataGetFirst returned %u", (unsigned)crtn);
return crtn;
}
CrlInfo *crlInfo = new CrlInfo(dlDbHand, &attrData[0], record, crlDataPtr);
rtnCrlInfo = (CrlInfo **)malloc(sizeof(CrlInfo*));
rtnCrlInfo[0] = crlInfo;
numCrls++;
for(;;) {
crtn = CSSM_DL_DataGetNext(dlDbHand,
resultHand,
&recordAttrs,
crlDataPtr,
&record);
switch(crtn) {
case CSSM_OK:
rtnCrlInfo = (CrlInfo **)realloc(rtnCrlInfo,
sizeof(CrlInfo *) * (numCrls + 1));
rtnCrlInfo[numCrls] = new CrlInfo(dlDbHand, &attrData[0], record,
crlDataPtr);
numCrls++;
break; case CSSMERR_DL_ENDOFDATA:
return CSSM_OK;
default:
ocspdErrorLog("fetchAllCrls: DataGetNext returned %u", (unsigned)crtn);
return crtn;
}
}
}
static void validateCrls(
CrlInfo **crlInfo,
unsigned numCrls)
{
CrlInfo *crl;
for(unsigned dex=0; dex<numCrls; dex++) {
crl = crlInfo[dex];
uint32 i;
if(crl->fetchIntAttr(ATTR_DEX_CRL_TYPE, i)) {
continue;
}
switch(i) {
case CSSM_CRL_TYPE_X_509v1:
case CSSM_CRL_TYPE_X_509v2:
break;
default:
ocspdErrorLog("validateCrls: bad CRL type (%u) on CRL %u\n",
(unsigned)i, dex);
crl->mIsBadlyFormed = true;
continue;
}
if(crl->fetchIntAttr(ATTR_DEX_CRL_ENC, i)) {
continue;
}
switch(i) {
case CSSM_CRL_ENCODING_BER:
case CSSM_CRL_ENCODING_DER:
break;
default:
ocspdErrorLog("validateCrls: bad CRL encoding (%u) on CRL %u\n",
(unsigned)i, dex);
crl->mIsBadlyFormed = true;
continue;
}
}
}
static void cryptoValidateCrls(
CrlInfo **crlInfo,
unsigned numCrls,
CSSM_TP_HANDLE tpHand,
CSSM_CSP_HANDLE cspHand,
CSSM_CL_HANDLE clHand,
CSSM_DL_HANDLE dlHand)
{
CrlInfo *crl;
CSSM_DL_DB_HANDLE certDb;
CSSM_DL_DB_HANDLE_PTR certDbPtr = NULL;
CSSM_RETURN crtn = CSSM_DL_DbOpen(dlHand,
SYSTEM_CERT_STORE_PATH,
NULL, CSSM_DB_ACCESS_READ,
NULL, NULL, &certDb.DBHandle);
if(crtn) {
ocspdErrorLog("cryptoValidateCrls: CSSM_DL_DbOpen returned %u", (unsigned)crtn);
}
else {
certDb.DLHandle = dlHand;
certDbPtr = &certDb;
}
for(unsigned dex=0; dex<numCrls; dex++) {
crl = crlInfo[dex];
crtn = cuCrlVerify(tpHand, clHand, cspHand,
&crl->mCrlBlob,
certDbPtr,
NULL, 0); switch(crtn) {
case CSSMERR_APPLETP_CRL_EXPIRED:
case CSSM_OK:
break;
default:
ocspdCrlDebug("...CRL %u FAILED crypto verify", dex);
crl->printName("FAILED crypto verify");
crl->mIsBadlyFormed = true;
break;
}
}
CSSM_DL_DbClose(certDb);
}
int calcCurrent(
CrlInfo **crlInfo,
unsigned numCrls,
int expireOverlapSeconds,
int staleTimeSeconds)
{
if(expireOverlapSeconds > staleTimeSeconds) {
ocspdErrorLog("calcCurrent: ExpireOverlap greater than StaleTime; aborting.\n");
return 1;
}
char *updateTime = cuTimeAtNowPlus(expireOverlapSeconds, TIME_CSSM);
char *staleTime = cuTimeAtNowPlus(-staleTimeSeconds, TIME_CSSM);
ocspdCrlDebug("updateTime : %s", updateTime);
ocspdCrlDebug("staleTime : %s", staleTime);
for(unsigned dex=0; dex<numCrls; dex++) {
crlInfo[dex]->validateTimes(updateTime, staleTime, dex);
}
APP_FREE(updateTime);
APP_FREE(staleTime);
return 0;
}
static void purgeAllCrls(
CrlInfo **crlInfo,
unsigned numCrls)
{
for(unsigned dex=0; dex<numCrls; dex++) {
CrlInfo *crl = crlInfo[dex];
crl->mIsExpired = true;
crl->mIsStale = true;
}
}
static void deleteBadCrls(
CrlInfo **crlInfo,
unsigned numCrls)
{
CrlInfo *crl;
for(unsigned dex=0; dex<numCrls; dex++) {
crl = crlInfo[dex];
if(crl->mIsBadlyFormed || crl->mIsStale) {
crl->printName("deleting");
CSSM_RETURN crtn = CSSM_DL_DataDelete(crl->dlDbHand(),
crl->record());
if(crtn) {
ocspdErrorLog("deleteBadCrls: CSSM_DL_DataDelete returned %u", (unsigned)crtn);
}
}
}
}
static void refreshExpiredCrls(
CrlInfo **crlInfo,
unsigned numCrls,
CSSM_CL_HANDLE clHand)
{
CrlInfo *crl;
bool haveCurrent;
CSSM_DATA newCrl;
for(unsigned dex=0; dex<numCrls; dex++) {
crl = crlInfo[dex];
if(!crl->mIsExpired || crl->mRefreshed) {
continue;
}
haveCurrent = false;
for(unsigned i=0; i<numCrls; i++) {
if(i == dex) {
continue;
}
CrlInfo *checkCrl = crlInfo[i];
if(checkCrl->mIsBadlyFormed) {
continue;
}
if(checkCrl->mIsExpired && !checkCrl->mRefreshed) {
continue;
}
if(crl->isSameIssuer(checkCrl)) {
ocspdCrlDebug("up-to-date CRL at dex %u matching expired CRL %u",
i, dex);
haveCurrent = true;
break;
}
}
if(haveCurrent) {
continue;
}
CSSM_DATA_PTR uri = crl->fetchValidAttr(ATTR_DEX_URI);
if(uri == NULL) {
ocspdCrlDebug("Expired CRL with no URI at dex %u", dex);
continue;
}
crl->printName("fetching new");
Allocator &alloc = Allocator::standard();
CSSM_RETURN crtn = ocspdNetFetch(alloc, *uri, LT_Crl, newCrl);
if(crtn) {
ocspdErrorLog("ocspdNetFetch returned %u", (unsigned)crtn);
continue;
}
crtn = cuAddCrlToDb(crl->dlDbHand(), clHand, &newCrl, uri);
alloc.free(newCrl.Data);
switch(crtn) {
case CSSM_OK:
ocspdCrlDebug("...refreshed CRL added to DB to account "
"for expired CRL %u", dex);
break;
case CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA:
ocspdCrlDebug("...refreshed CRL is a dup of CRL %u; skipping",
dex);
break;
default:
continue;
}
crl->mRefreshed = true;
}
}
#pragma mark ----- public interface -----
CSSM_RETURN ocspdCrlRefresh(
CSSM_DL_DB_HANDLE dlDbHand,
CSSM_CL_HANDLE clHand,
unsigned staleDays,
unsigned expireOverlapSeconds,
bool purgeAll,
bool fullCryptoVerify,
bool doRefresh) {
CSSM_TP_HANDLE tpHand = 0;
CSSM_CSP_HANDLE cspHand = 0;
CSSM_RETURN crtn = CSSM_OK;
assert((dlDbHand.DLHandle != 0) &&
(dlDbHand.DBHandle != 0) &&
(clHand != 0));
if(staleDays == 0) {
staleDays = DEFAULT_STALE_DAYS;
}
if(expireOverlapSeconds == 0) {
expireOverlapSeconds = DEFAULT_EXPIRE_OVERLAP_SECONDS;
}
if(fullCryptoVerify) {
cspHand = attachCommon(&gGuidAppleCSP, CSSM_SERVICE_CSP);
if(cspHand == 0) {
Syslog::alert("Error loading AppleCSP");
return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
}
tpHand = attachCommon(&gGuidAppleX509TP, CSSM_SERVICE_TP);
if(tpHand == 0) {
Syslog::alert("Error loading AppleX509TP");
crtn = CSSMERR_CSSM_ADDIN_LOAD_FAILED;
goto errOut;
}
}
CrlInfo **crlInfo;
unsigned numCrls;
crtn = fetchAllCrls(dlDbHand, fullCryptoVerify, crlInfo, numCrls);
if(crtn) {
ocspdErrorLog("ocspdCrlRefresh: Error reading CRLs.");
return crtn;
}
ocspdCrlDebug("ocspdCrlRefresh: %u CRLs found", numCrls);
validateCrls(crlInfo, numCrls);
if(fullCryptoVerify) {
cryptoValidateCrls(crlInfo, numCrls, tpHand, cspHand, clHand,
dlDbHand.DLHandle);
}
if(calcCurrent(crlInfo, numCrls, expireOverlapSeconds,
staleDays * SECONDS_PER_DAY)) {
ocspdErrorLog("ocspdCrlRefresh: Error calculating CRL times.");
crtn = CSSMERR_TP_INTERNAL_ERROR;
goto errOut;
}
if(purgeAll) {
purgeAllCrls(crlInfo, numCrls);
}
deleteBadCrls(crlInfo, numCrls);
if(doRefresh) {
refreshExpiredCrls(crlInfo, numCrls, clHand);
}
for(unsigned dex=0; dex<numCrls; dex++) {
delete crlInfo[dex];
}
free(crlInfo);
errOut:
if(cspHand) {
detachCommon(&gGuidAppleCSP, cspHand);
}
if(tpHand) {
detachCommon(&gGuidAppleX509TP, tpHand);
}
return crtn;
}