/*
 * 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;
}