TPCrlInfo *TPCRLCache::search(
	TPCertInfo 			&cert,
	TPVerifyContext		&vfyCtx)
{
	StLock<Mutex> _(mLock);
	TPCrlInfo *crl = findCrlForCert(cert);
	if(crl) {
		/* reevaluate validity */
		crl->calculateCurrent(vfyCtx.verifyTime);
		crl->mRefCount++;
		tpCrlDebug("TPCRLCache hit");
	}
	else {
		tpCrlDebug("TPCRLCache miss");
	}
	return crl;
}
/*
 * Perform CRL verification on a cert group.
 * The cert group has already passed basic issuer/subject and signature
 * verification. The status of the incoming CRLs is completely unknown. 
 * 
 * FIXME - No mechanism to get CRLs from net with non-NULL verifyTime.
 * How are we supposed to get the CRL which was valid at a specified 
 * time in the past?
 */
CSSM_RETURN tpVerifyCertGroupWithCrls(
	TPVerifyContext					&vfyCtx,
	TPCertGroup 					&certGroup)		// to be verified 
{
	CSSM_RETURN 	crtn;
	CSSM_RETURN		ourRtn = CSSM_OK;

	assert(vfyCtx.clHand != 0);
	assert(vfyCtx.policy == kRevokeCrlBasic);
	tpCrlDebug("tpVerifyCertGroupWithCrls numCerts %u", certGroup.numCerts());
	CSSM_DATA issuers = { 0, NULL };
	CSSM_APPLE_TP_CRL_OPT_FLAGS optFlags = 0;
	if(vfyCtx.crlOpts != NULL) {
		optFlags = vfyCtx.crlOpts->CrlFlags;
	}
	
	/* found & verified CRLs we need to release */
	TPCrlGroup foundCrls(vfyCtx.alloc, TGO_Caller);
	
	try {
		
		unsigned certDex;
		TPCrlInfo *crl = NULL;
		
		/* get issuers as PEM-encoded data blob; we need to release */
		certGroup.encodeIssuers(issuers);

		/* main loop, verify each cert */
		for(certDex=0; certDex<certGroup.numCerts(); certDex++) {
			TPCertInfo *cert = certGroup.certAtIndex(certDex);

			tpCrlDebug("...verifying %s cert %u", 
				cert->isAnchor() ? "anchor " : "", cert->index());
			if(cert->isSelfSigned() || cert->trustSettingsFound()) {
				/* CRL meaningless for a root or trusted cert */
				continue;
			}
			if(cert->revokeCheckComplete()) {
				/* Another revocation policy claimed that this cert is good to go */
				tpCrlDebug("   ...cert at index %u revokeCheckComplete; skipping", 
					cert->index());
				continue;
			}
			crl = NULL;
			do {
				/* first, see if we have CRL status available for this cert */
				crtn = tpGetCrlStatusForCert(*cert, issuers);
				tpCrlDebug("...tpGetCrlStatusForCert: %u", crtn);
				if(crtn == CSSM_OK) {
					tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u verified by local .crl\n",
								cert->index());
					cert->revokeCheckGood(true);
					if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
						/* no more revocation checking necessary for this cert */
						cert->revokeCheckComplete(true);
					}
					break;
				}
				if(crtn == CSSMERR_TP_CERT_REVOKED) {
					tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u revoked in local .crl\n",
								cert->index());
					cert->addStatusCode(crtn);
					break;
				}
				if(crtn == CSSMERR_APPLETP_NETWORK_FAILURE) {
					/* crl is being fetched from net, but we don't have it yet */
					if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) &&
								tpCertHasCrlDistPt(*cert)) {
						/* crl is required; we don't have it yet, so we fail */
						tpCrlDebug("   ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
								cert->index());
						break;
					}
					/* "Best Attempt" case, so give the cert a pass for now */
					tpCrlDebug("   ...cert %u: no CRL; tolerating", cert->index());
					crtn = CSSM_OK;
					break;
				}
				/* all other CRL status results: try to fetch the CRL */

				/* find a CRL for this cert by hook or crook */
				crtn = tpFindCrlForCert(*cert, crl, vfyCtx);
				if(crtn) {
					/* tpFindCrlForCert may have simply caused ocspd to start
					 * downloading a CRL asynchronously; depending on the speed
					 * of the network and the CRL size, this may return 0 bytes
					 * of data with a CSSMERR_APPLETP_NETWORK_FAILURE result.
					 * We won't know the actual revocation result until the
					 * next time we call tpGetCrlStatusForCert after the full
					 * CRL has been downloaded successfully.
					 */
					if(optFlags & CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT) {
						tpCrlDebug("   ...cert %u: REQUIRE_CRL_PER_CERT abort",
								cert->index());
						break;
					}
					if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) && 
								tpCertHasCrlDistPt(*cert)) {
						tpCrlDebug("   ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
								cert->index());
						break;
					}
					/* 
					 * This is the only place where "Best Attempt" tolerates an error
					 */
					tpCrlDebug("   ...cert %u: no CRL; tolerating", cert->index());
					crtn = CSSM_OK;
					assert(crl == NULL);
					break;
				}
				
				/* Keep track; we'll release all when done. */
				assert(crl != NULL);
				foundCrls.appendCrl(*crl);
				
				/* revoked? */
				crtn = crl->isCertRevoked(*cert, vfyCtx.verifyTime);
				if(crtn) {
					break;
				}
				tpCrlDebug("   ...cert %u VERIFIED by CRL", cert->index());
				cert->revokeCheckGood(true);
				if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
					/* no more revocation checking necessary for this cert */
					cert->revokeCheckComplete(true);
				}
			} while(0);
			
			/* done processing one cert */
			if(crtn) {
				tpCrlDebug("   ...cert at index %u FAILED crl vfy", 
					cert->index());
				if(ourRtn == CSSM_OK) {
					ourRtn = crtn;
				}
				/* continue on to next cert */
			}	/* error on one cert */
		}		/* for each cert */
	}
	catch(const CssmError &cerr) {
		if(ourRtn == CSSM_OK) {
			ourRtn = cerr.error;
		}
	}
	/* other exceptions fatal */

	/* release all found CRLs */
	for(unsigned dex=0; dex<foundCrls.numCrls(); dex++) {
		TPCrlInfo *crl = foundCrls.crlAtIndex(dex);
		assert(crl != NULL);
		tpDisposeCrl(*crl, vfyCtx);
	}
	/* release issuers */
	if(issuers.Data) {
		free(issuers.Data);
	}
	return ourRtn;
}
Example #3
0
static CSSM_RETURN tpCrlViaNet(
	const CSSM_DATA &url,
	const CSSM_DATA *issuer,	// optional, only if cert and CRL have same issuer
	TPVerifyContext &vfyCtx,
	TPCertInfo &forCert,		// for verifyWithContext
	TPCrlInfo *&rtnCrl)
{
	TPCrlInfo *crl = NULL;
	CSSM_DATA crlData;
	CSSM_RETURN crtn;
	Allocator &alloc = Allocator::standard();
	char cssmTime[CSSM_TIME_STRLEN+1];
	
	rtnCrl = NULL;
	
	/* verifyTime: we want a CRL that's valid right now. */
	{
		StLock<Mutex> _(tpTimeLock());
		timeAtNowPlus(0, TIME_CSSM, cssmTime);
	}

	crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData);
	if(crtn) {
		return crtn;
	}
	try {
		crl = new TPCrlInfo(vfyCtx.clHand,
			vfyCtx.cspHand,
			&crlData,
			TIC_CopyData,
			NULL); 			// verifyTime = Now
	}
	catch(...) {
		alloc.free(crlData.Data);
		
		/* 
		 * There is a slight possibility of recovering from this error. In case
		 * the CRL came from disk cache, flush the cache and try to get the CRL
		 * from the net.
		 */
		tpDebug("   bad CRL; flushing from cache and retrying"); 
		ocspdCRLFlush(url);
		crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData);
		if(crtn == CSSM_OK) {
			try {
				crl = new TPCrlInfo(vfyCtx.clHand,
					vfyCtx.cspHand,
					&crlData,
					TIC_CopyData,
					NULL);	
				tpDebug("   RECOVERY: good CRL obtained from net"); 
			}
			catch(...) {
				alloc.free(crlData.Data);
				tpDebug("   bad CRL; recovery FAILED (1)"); 
				return CSSMERR_APPLETP_CRL_NOT_FOUND;
			}
		}
		else {
			/* it was in cache but we can't find it on the net */
			tpDebug("   bad CRL; recovery FAILED (2)"); 
			return CSSMERR_APPLETP_CRL_NOT_FOUND;
		}
	}
	alloc.free(crlData.Data);
	
	/* 
 	 * Full CRL verify.
 	 * The verify time in the TPVerifyContext is the time at which various
	 * entities (CRL and its own cert chain) are to be verified; that's
	 * NULL for "right now". The current vfyCtx.verifyTime is the time at
	 * which the cert's revocation status to be determined; this call to 
	 * verifyWithContextNow() doesn't do that. 
	 */
	crtn = crl->verifyWithContextNow(vfyCtx, &forCert);
	if(crtn == CSSM_OK) {
		crl->uri(url);
	}
	else {
		delete crl;
		crl = NULL;
	}
	rtnCrl = crl;
	return crtn;
}
/*
 * Find CRL for specified cert. Only returns a fully verified CRL. 
 * Cert-specific errors such as CSSMERR_APPLETP_CRL_NOT_FOUND will be added
 * to cert's return codes. 
 */
static CSSM_RETURN tpFindCrlForCert(
	TPCertInfo						&subject,
	TPCrlInfo						*&foundCrl,		// RETURNED
	TPVerifyContext					&vfyCtx)
{
	
	tpCrlDebug("tpFindCrlForCert top");
	TPCrlInfo *crl = NULL;
	foundCrl = NULL;
	CSSM_APPLE_TP_CRL_OPT_FLAGS crlOptFlags = 0;
	
	if(vfyCtx.crlOpts) {
		crlOptFlags = vfyCtx.crlOpts->CrlFlags;
	}
	
	/* Search inputCrls for a CRL for subject cert */
	if(vfyCtx.inputCrls != NULL) {
		crl = vfyCtx.inputCrls->findCrlForCert(subject);
		if(crl && (crl->verifyWithContextNow(vfyCtx, &subject) == CSSM_OK)) {
			foundCrl = crl;
			crl->mFromWhere = CFW_InGroup;
			tpCrlDebug("   ...CRL found in CrlGroup");
			return CSSM_OK;
		}
	}

	/* local process-wide cache */
	crl = tpGlobalCrlCache().search(subject, vfyCtx);
	if(crl) {
		tpCrlDebug("...tpFindCrlForCert found CRL in cache, calling verifyWithContext");
		if(crl->verifyWithContextNow(vfyCtx, &subject) == CSSM_OK) {
			foundCrl = crl;
			crl->mFromWhere = CFW_LocalCache;
			tpCrlDebug("   ...CRL found in local cache");
			return CSSM_OK;
		}
		else {
			tpGlobalCrlCache().release(*crl);
		}
	}
	
	/* 
	 * Try DL/DB.
	 * Note tpDbFindIssuerCrl() returns a verified CRL.
	 */
	crl = tpDbFindIssuerCrl(vfyCtx, *subject.issuerName(), subject);
	if(crl) {
		foundCrl = crl;
		crl->mFromWhere = CFW_DlDb;
		tpCrlDebug("   ...CRL found in DlDb");
		return CSSM_OK;
	}
	
	/* Last resort: try net if enabled */
	CSSM_RETURN crtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
	crl = NULL;
	if(crlOptFlags & CSSM_TP_ACTION_FETCH_CRL_FROM_NET) {
		crtn = tpFetchCrlFromNet(subject, vfyCtx, crl);
	}
	
	if(crtn) {
		tpCrlDebug("   ...tpFindCrlForCert: CRL not found");
		if(subject.addStatusCode(crtn)) {
			return crtn;
		}
		else {
			return CSSM_OK;
		}
	}
	
	/* got one from net - add to global cache */
	assert(crl != NULL);
	tpGlobalCrlCache().add(*crl);
	crl->mFromWhere = CFW_Net;
	tpCrlDebug("   ...CRL found from net");
	
	foundCrl = crl;
	return CSSM_OK;
}
/*
 * Search a list of DBs for a CRL from the specified issuer and (optional)
 * TPVerifyContext.verifyTime.
 * Just a boolean return - we found it, or not. If we did, we return a
 * TPCrlInfo which has been verified with the specified TPVerifyContext.
 */
TPCrlInfo *tpDbFindIssuerCrl(
	TPVerifyContext		&vfyCtx,
	const CSSM_DATA		&issuer,
	TPCertInfo			&forCert)
{
	StLock<Mutex> _(SecTrustKeychainsGetMutex());

	uint32						dbDex;
	CSSM_HANDLE					resultHand;
	CSSM_DATA					crl;
	CSSM_DL_DB_HANDLE			dlDb;
	CSSM_DB_UNIQUE_RECORD_PTR	record;
	TPCrlInfo 					*issuerCrl = NULL;
	CSSM_DL_DB_LIST_PTR 		dbList = vfyCtx.dbList;
	CSSM_RETURN					crtn;

	if(dbList == NULL) {
		return NULL;
	}
	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
		dlDb = dbList->DLDBHandle[dbDex];
		crl.Data = NULL;
		crl.Length = 0;
		record = tpCrlLookup(dlDb,
			&issuer,
			vfyCtx.verifyTime,
			&resultHand,
			&crl);
		/* remember we have to:
		 * -- abort this query regardless, and
		 * -- free the CSSM_DATA crl regardless, and
		 * -- free the unique record if we don't use it
		 *    (by placing it in issuerCert)...
		 */
		if(record != NULL) {
			/* Found one */
			assert(crl.Data != NULL);
			issuerCrl = new TPCrlInfo(vfyCtx.clHand,
				vfyCtx.cspHand,
				&crl,
				TIC_CopyData,
				vfyCtx.verifyTime);
			/* we're done with raw CRL data */
			/* FIXME this assumes that vfyCtx.alloc is the same as the
			 * allocator associated with DlDB...OK? */
			tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
			crl.Data = NULL;
			crl.Length = 0;

			/* and we're done with the record */
			CSSM_DL_FreeUniqueRecord(dlDb, record);

			/* Does it verify with specified context? */
			crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
			if(crtn) {

				delete issuerCrl;
				issuerCrl = NULL;

				/*
				 * Verify fail. Continue searching this DB. Break on
				 * finding the holy grail or no more records found.
				 */
				for(;;) {
					crl.Data = NULL;
					crl.Length = 0;
					crtn = CSSM_DL_DataGetNext(dlDb,
						resultHand,
						NULL,		// no attrs
						&crl,
						&record);
					if(crtn) {
						/* no more, done with this DB */
						assert(crl.Data == NULL);
						break;
					}
					assert(crl.Data != NULL);

					/* found one - is it any good? */
					issuerCrl = new TPCrlInfo(vfyCtx.clHand,
						vfyCtx.cspHand,
						&crl,
						TIC_CopyData,
						vfyCtx.verifyTime);
					/* we're done with raw CRL data */
					/* FIXME this assumes that vfyCtx.alloc is the same as the
					* allocator associated with DlDB...OK? */
					tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
					crl.Data = NULL;
					crl.Length = 0;

					CSSM_DL_FreeUniqueRecord(dlDb, record);

					crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
					if(crtn == CSSM_OK) {
						/* yes! */
						break;
					}
					delete issuerCrl;
					issuerCrl = NULL;
				} /* searching subsequent records */
			}	/* verify fail */
			/* else success! */

			if(issuerCrl != NULL) {
				/* successful return */
				CSSM_DL_DataAbortQuery(dlDb, resultHand);
				tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
				return issuerCrl;
			}
		}	/* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
		else {
			assert(crl.Data == NULL);
		}
		/* in any case, abort the query for this db */
		CSSM_DL_DataAbortQuery(dlDb, resultHand);

	}	/* main loop searching dbList */

	/* issuer not found */
	return NULL;
}