/* * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * crlDb.cpp - CRL cache * * Created 30 September 2004 by dmitch at Apple */ #include "crlDb.h" #include "attachCommon.h" #include "crlRefresh.h" #include #include #include #include #include #include #include #include #include #include #include #include #define CRL_CACHE_DB "/private/var/db/crls/crlcache.db" #pragma mark ---- OCSPD database singleton ---- class CrlDatabase { NOCOPY(CrlDatabase) public: CrlDatabase(); ~CrlDatabase(); /* methods associated with public API of this module */ bool lookup( Allocator &alloc, const CSSM_DATA &url, const CSSM_DATA &verifyTime, CSSM_DATA &crlData); // allocd in alloc space and RETURNED CSSM_RETURN add( const CSSM_DATA &crlData, // as it came from the server const CSSM_DATA &url); // where it came from void flush( const CSSM_DATA &url); void refresh( unsigned staleDays, unsigned expireOverlapSeconds, bool purgeAll, bool fullCryptoVerify); private: CSSM_RETURN openDatabase( const char *dbFileName, CSSM_DB_HANDLE &dbHand, // RETURNED bool &didCreate); // RETURNED void closeDatabase( CSSM_DB_HANDLE dbHand); /* see implementation for comments */ CSSM_RETURN lookupPriv( CSSM_DB_HANDLE dbHand, const CSSM_DATA *url, const CSSM_DATA *verifyTime, CSSM_HANDLE_PTR resultHandPtr, CSSM_DB_UNIQUE_RECORD_PTR *recordPtr, CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData, CSSM_DATA *crlData); /* everything this module does is protected by this global lock */ Mutex mLock; /* * We maintain open handles to these two modules, but we do NOT maintain * a handle to an open DB - we open and close that as needed for robustness. */ CSSM_DL_HANDLE mDlHand; CSSM_CL_HANDLE mClHand; }; CrlDatabase::CrlDatabase() : mDlHand(0), mClHand(0) { /* Attach to DL side of, CL, and CSP */ 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); } } /* * I believe that this code actually never executes due to ModuleNExus limitations, * but it may run someday. */ CrlDatabase::~CrlDatabase() { if(mDlHand != 0) { detachCommon(&gGuidAppleCSPDL, mDlHand); } if(mClHand != 0) { detachCommon(&gGuidAppleX509CL, mClHand); } } /* * Common code to open or create our DB. If it doesn't exist we'll create it. * We never have to unlock it; its password is the same as the filename. */ CSSM_RETURN CrlDatabase::openDatabase( const char *dbFileName, CSSM_DB_HANDLE &dbHand, // RETURNED bool &didCreate) // RETURNED { didCreate = false; /* try to open existing DB */ CSSM_RETURN crtn = CSSM_DL_DbOpen(mDlHand, dbFileName, NULL, // DbLocation CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE, NULL, // CSSM_ACCESS_CREDENTIALS *AccessCred NULL, // void *OpenParameters &dbHand); switch(crtn) { case CSSM_OK: return CSSM_OK; case CSSMERR_DL_DATASTORE_DOESNOT_EXIST: /* proceed to create it */ break; default: ocspdErrorLog("CrlDatabase::openDatabase: CSSM_DL_DbOpen returned %lu", crtn); return crtn; } /* create new one */ CSSM_DBINFO dbInfo; memset(&dbInfo, 0, sizeof(CSSM_DBINFO)); /* password = fileName */ Security::Allocator &alloc = Security::Allocator::standard(); CssmClient::AclFactory::PasswordChangeCredentials pCreds((StringData(dbFileName)), alloc); const AccessCredentials* aa = pCreds; /* weird code copied from old crlRefresh. I do not pretend to understand this. */ 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(aa)); crtn = CSSM_DL_DbCreate(mDlHand, dbFileName, NULL, // DbLocation &dbInfo, CSSM_DB_ACCESS_PRIVILEGED, &rcc, // CredAndAclEntry NULL, // OpenParameters &dbHand); if(crtn) { ocspdErrorLog("CrlDatabase::openDatabase: CSSM_DL_DbCreate returned %lu", crtn); return crtn; } else { /* one more thing: make it world readable, only writable by us */ 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); } } /* * Common code to initiate a DataGetFirst op. Query still active on successful * return; caller must both CSSM_DL_FreeUniqueRecord() and eventually * CSSM_DL_DataAbortQuery. */ CSSM_RETURN CrlDatabase::lookupPriv( CSSM_DB_HANDLE dbHand, /* both predicate attributes optional */ const CSSM_DATA *url, const CSSM_DATA *verifyTime, /* these two always returned on success */ CSSM_HANDLE_PTR resultHandPtr, CSSM_DB_UNIQUE_RECORD_PTR *recordPtr, /* * Optionally returned, in/out (in to specify which attrs, out as the * attrs fetched) */ CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR attrData, /* optionally returned - the CRL itself */ CSSM_DATA *crlData) { CSSM_QUERY query; CSSM_SELECTION_PREDICATE pred[3]; CSSM_SELECTION_PREDICATE *predPtr = pred; CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand}; assert(mDlHand != 0); assert(dbHand != 0); assert(resultHandPtr != NULL); assert(recordPtr != NULL); /* zero, one, or three predicates...first, the URI */ if(url) { predPtr->DbOperator = CSSM_DB_EQUAL; predPtr->Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; predPtr->Attribute.Info.Label.AttributeName = "URI"; predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; predPtr->Attribute.Value = const_cast(url); predPtr->Attribute.NumberOfValues = 1; predPtr++; } if(verifyTime) { /* * Before/after: ask for a CRL which is/was valid at verifyTime. * Caller MUST give us a properly formatted CSSM_TIMESTRING or this will fail! */ predPtr->DbOperator = CSSM_DB_LESS_THAN; predPtr->Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; predPtr->Attribute.Info.Label.AttributeName = "NextUpdate"; predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; predPtr->Attribute.Value = const_cast(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 = "ThisUpdate"; predPtr->Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; predPtr->Attribute.Value = const_cast(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; // FIXME - meaningful? query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? query.QueryFlags = crlData ? CSSM_QUERY_RETURN_DATA : 0; return CSSM_DL_DataGetFirst(dlDbHand, &query, resultHandPtr, attrData, crlData, recordPtr); } /* methods associated with public API of this module */ bool CrlDatabase::lookup( Allocator &alloc, const CSSM_DATA &url, const CSSM_DATA &verifyTime, CSSM_DATA &crlData) // allocd in alloc space and RETURNED { StLock _(mLock); CSSM_DB_HANDLE dbHand; bool didCreate; crlData.Data = NULL; crlData.Length = 0; if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) { /* error: no DB, we're done */ ocspdErrorLog("CrlDatabase::lookup: no cache DB\n"); return false; } if(didCreate) { /* we just created empty DB, we're done */ 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, &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, // as it came from the server const CSSM_DATA &url) // where it came from { StLock _(mLock); bool didCreate; CSSM_DB_HANDLE dbHand; if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) { /* no DB, we're done */ ocspdErrorLog("CrlDatabase::add: no cache DB\n"); return false; } CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand}; /* this routine takes care of all the attributes, gleaning them from * the contents of the CRL itself */ CSSM_RETURN crtn = cuAddCrlToDb(dlDbHand, mClHand, &crlData, &url); if(crtn) { ocspdErrorLog("CrlDatabase::add: error %lu adding CRL to DB", crtn); } else { ocspdCrlDebug("CrlDatabase::add: CRL added to DB"); } closeDatabase(dbHand); return crtn; } /* * Flush CRLs associated with specified URL. Called by TP when it detects a * bad CRL. */ void CrlDatabase::flush( const CSSM_DATA &url) { StLock _(mLock); bool didCreate; CSSM_DB_HANDLE dbHand; if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) { /* error: no DB, we're done */ ocspdErrorLog("CrlDatabase::flush: no cache DB\n"); return; } if(didCreate) { /* we just created empty DB, we're done */ 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, // verify time &resultHand, &record, NULL, // attrs NULL); // data 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); /* any more? */ do { crtn = CSSM_DL_DataGetNext(dlDbHand, resultHand, NULL, NULL, &record); if(crtn) { /* done, not found */ 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) { StLock _(mLock); bool didCreate; CSSM_DB_HANDLE dbHand; if(openDatabase(CRL_CACHE_DB, dbHand, didCreate)) { /* error: no DB, we're done */ ocspdErrorLog("CrlDatabase::refresh: no cache DB\n"); return; } if(didCreate) { /* we just created empty DB, we're done */ ocspdCrlDebug("CrlDatabase::refresh: empty cache DB"); closeDatabase(dbHand); return; } CSSM_DL_DB_HANDLE dlDbHand = {mDlHand, dbHand}; ocspdCrlRefresh(dlDbHand, mClHand, staleDays, expireOverlapSeconds, purgeAll, fullCryptoVerify); closeDatabase(dbHand); return; } static ModuleNexus gCrlDatabase; #pragma mark ----- Public API ----- /* * Lookup cached CRL by URL and verifyTime. * Just a boolean returned; we found it, or not. */ bool crlCacheLookup( Allocator &alloc, const CSSM_DATA &url, const CSSM_DATA &verifyTime, CSSM_DATA &crlData) // allocd in alloc space and RETURNED { return gCrlDatabase().lookup(alloc, url, verifyTime, crlData); } /* * Add a CRL response to cache. Incoming response is completely unverified; * we just verify that we can parse it. */ CSSM_RETURN crlCacheAdd( const CSSM_DATA &crlData, // as it came from the server const CSSM_DATA &url) // where it came from { return gCrlDatabase().add(crlData, url); } /* * Delete any CRL associated with specified URL from cache. */ void crlCacheFlush( const CSSM_DATA &url) { return gCrlDatabase().flush(url); } /* * Refresh the CRL cache. */ void crlCacheRefresh( unsigned staleDays, unsigned expireOverlapSeconds, bool purgeAll, bool fullCryptoVerify) { gCrlDatabase().refresh(staleDays, expireOverlapSeconds, purgeAll, fullCryptoVerify); }