/* 
 * Returns true if we are to allow/trust the specified
 * cert as a PKINIT-only anchor.
 */
static bool tpCheckPkinitServerCert(
	TPCertGroup &certGroup)
{
	/* 
	 * Basic requirement: exactly one cert, self-signed.
	 * The numCerts == 1 requirement might change...
	 */
	unsigned numCerts = certGroup.numCerts();
	if(numCerts != 1) {
		tpDebug("tpCheckPkinitServerCert: too many certs");
		return false;
	}
	/* end of chain... */
	TPCertInfo *theCert = certGroup.certAtIndex(numCerts - 1);
	if(!theCert->isSelfSigned()) {
		tpDebug("tpCheckPkinitServerCert: 1 cert, not self-signed");
		return false;
	}
	const CSSM_DATA *subjectName = theCert->subjectName();
	
	/* 
	 * Open the magic keychain.
	 * We're going up and over the Sec layer here, not generally 
	 * kosher, but this is a temp hack.
	 */
	OSStatus ortn;
	SecKeychainRef kcRef = NULL;
	string fullPathName;
	const char *homeDir = getenv("HOME");
	if (homeDir == NULL)
	{
		// If $HOME is unset get the current user's home directory
		// from the passwd file.
		uid_t uid = geteuid();
		if (!uid) uid = getuid();
		struct passwd *pw = getpwuid(uid);
		if (!pw) {
			return false;
		}
		homeDir = pw->pw_dir;
	}
	fullPathName = homeDir;
	fullPathName += "/Library/Application Support/PKINIT/TrustedServers.keychain";
	ortn = SecKeychainOpen(fullPathName.c_str(), &kcRef);
	if(ortn) {
		tpDebug("tpCheckPkinitServerCert: keychain not found (1)");
		return false;
	}
	/* subsequent errors to errOut: */
	
	bool ourRtn = false;
	SecKeychainStatus kcStatus;
	CSSM_DATA_PTR subjSerial = NULL;
	CSSM_RETURN crtn;
	SecKeychainSearchRef		srchRef = NULL;
	SecKeychainAttributeList	attrList;
	SecKeychainAttribute		attrs[2];
	SecKeychainItemRef			foundItem = NULL;
	
	ortn = SecKeychainGetStatus(kcRef, &kcStatus);
	if(ortn) {
		tpDebug("tpCheckPkinitServerCert: keychain not found (2)");
		goto errOut;
	}
	
	/*
	 * We already have this cert's normalized name; get its
	 * serial number.
	 */
	crtn = theCert->fetchField(&CSSMOID_X509V1SerialNumber, &subjSerial);
	if(crtn) {
		/* should never happen */
		tpDebug("tpCheckPkinitServerCert: error fetching serial number");
		goto errOut;
	}
	
	attrs[0].tag    = kSecSubjectItemAttr;
	attrs[0].length = subjectName->Length;
	attrs[0].data   = subjectName->Data;
	attrs[1].tag    = kSecSerialNumberItemAttr;
	attrs[1].length = subjSerial->Length;
	attrs[1].data   = subjSerial->Data;
	attrList.count  = 2;
	attrList.attr   = attrs;
	
	ortn = SecKeychainSearchCreateFromAttributes(kcRef,
		kSecCertificateItemClass,
		&attrList,
		&srchRef);
	if(ortn) {
		tpDebug("tpCheckPkinitServerCert: search failure");
		goto errOut;
	}
	for(;;) {
		ortn = SecKeychainSearchCopyNext(srchRef, &foundItem);
		if(ortn) {
			tpDebug("tpCheckPkinitServerCert: end search");
			break;
		}
		
		/* found a matching cert; do byte-for-byte compare */
		CSSM_DATA certData;
		ortn = SecCertificateGetData((SecCertificateRef)foundItem, &certData);
		if(ortn) {
			tpDebug("tpCheckPkinitServerCert: SecCertificateGetData failure");
			continue;
		}
		if(tpCompareCssmData(&certData, theCert->itemData())){
			tpDebug("tpCheckPkinitServerCert: FOUND CERT");
			ourRtn = true;
			break;
		}
		tpDebug("tpCheckPkinitServerCert: skipping matching cert");
		CFRelease(foundItem);
		foundItem = NULL;
	}
errOut:
	CFRELEASE(kcRef);
	CFRELEASE(srchRef);
	CFRELEASE(foundItem);
	if(subjSerial != NULL) {
		theCert->freeField(&CSSMOID_X509V1SerialNumber, subjSerial);
	}
	return ourRtn;
}
void AppleTPSession::CertGroupVerify(CSSM_CL_HANDLE clHand,
		CSSM_CSP_HANDLE cspHand,
		const CSSM_CERTGROUP &CertGroupToBeVerified,
		const CSSM_TP_VERIFY_CONTEXT *VerifyContext,
		CSSM_TP_VERIFY_CONTEXT_RESULT_PTR VerifyContextResult)
{
	CSSM_BOOL				verifiedToRoot = CSSM_FALSE;
	CSSM_BOOL				verifiedToAnchor = CSSM_FALSE;
	CSSM_BOOL				verifiedViaTrustSetting = CSSM_FALSE;
	CSSM_RETURN				constructReturn = CSSM_OK;
	CSSM_RETURN				policyReturn = CSSM_OK;
	const CSSM_TP_CALLERAUTH_CONTEXT *cred;
	/* declare volatile as compiler workaround to avoid caching in CR4 */
	const CSSM_APPLE_TP_ACTION_DATA * volatile actionData = NULL;
	CSSM_TIMESTRING			cssmTimeStr;
	CSSM_APPLE_TP_ACTION_FLAGS	actionFlags = 0;
	CSSM_TP_STOP_ON 		tpStopOn = 0;
	
	/* keep track of whether we did policy checking; if not, we do defaults */
	bool					didCertPolicy = false;
	bool					didRevokePolicy = false;
	
	/* user trust parameters */
	CSSM_OID				utNullPolicy = {0, NULL};
	const CSSM_OID			*utPolicyOid = NULL;
	const char				*utPolicyStr = NULL;
	uint32					utPolicyStrLen = 0;
	SecTrustSettingsKeyUsage	utKeyUse = 0;
	bool					utTrustSettingEnabled = false;
	
	if(VerifyContextResult) {
		memset(VerifyContextResult, 0, sizeof(*VerifyContextResult));
	}

	/* verify input args, skipping the ones checked by CertGroupConstruct */
	if((VerifyContext == NULL) || (VerifyContext->Cred == NULL)) {
		/* the spec says that this is optional but we require it */
			CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
	}
	cred = VerifyContext->Cred;
	
	/* Optional ActionData affecting all policies */
	actionData = (CSSM_APPLE_TP_ACTION_DATA * volatile)VerifyContext->ActionData.Data;
	if(actionData != NULL) {
		switch(actionData->Version) {
			case CSSM_APPLE_TP_ACTION_VERSION:
				if(VerifyContext->ActionData.Length !=
						sizeof(CSSM_APPLE_TP_ACTION_DATA)) {
					CssmError::throwMe(CSSMERR_TP_INVALID_ACTION_DATA);
				}
				break;
			/* handle backwards versions here if we ever go beyond version 0 */
			default:
				CssmError::throwMe(CSSMERR_TP_INVALID_ACTION_DATA);
		}
		actionFlags = actionData->ActionFlags;
		if(actionFlags & CSSM_TP_ACTION_TRUST_SETTINGS) {
			utTrustSettingEnabled = true;
		}
	}
	
	/* optional, may be NULL */
	cssmTimeStr = cred->VerifyTime;
	
	tpStopOn = cred->VerificationAbortOn;
	switch(tpStopOn) {
		/* the only two we support */
		case CSSM_TP_STOP_ON_NONE:	
		case CSSM_TP_STOP_ON_FIRST_FAIL:
			break;
		/* default maps to stop on first fail */
		case CSSM_TP_STOP_ON_POLICY:
			tpStopOn = CSSM_TP_STOP_ON_FIRST_FAIL;
			break;
		default:
			CssmError::throwMe(CSSMERR_TP_INVALID_STOP_ON_POLICY);
	}
	
	/* now the args we can't deal with */
	if(cred->CallerCredentials != NULL) {
			CssmError::throwMe(CSSMERR_TP_INVALID_CALLERAUTH_CONTEXT_POINTER);
	}
	/* ...any others? */
	
	/* set up for optional user trust evaluation */
	if(utTrustSettingEnabled) {
		const CSSM_TP_POLICYINFO *pinfo = &cred->Policy;
		TPPolicy utPolicy = kTPx509Basic;
		
		/* default policy OID in case caller hasn't specified one */
		utPolicyOid = &utNullPolicy;
		if(pinfo->NumberOfPolicyIds == 0) {
			tpTrustSettingsDbg("CertGroupVerify: User trust enabled but no policies (1)");
			/* keep going, I guess - no policy-specific info - use kTPx509Basic */
		}
		else {
			CSSM_FIELD_PTR utPolicyField = &pinfo->PolicyIds[0];
			utPolicyOid = &utPolicyField->FieldOid;
			bool foundPolicy = checkPolicyOid(*utPolicyOid, utPolicy);
			if(!foundPolicy) {
				tpTrustSettingsDbg("CertGroupVerify: User trust enabled but no policies");
				/* keep going, I guess - no policy-specific info - use kTPx509Basic */
			}
			else {
				/* get policy-specific info */
				tp_policyTrustSettingParams(utPolicy, &utPolicyField->FieldValue,
					&utPolicyStr, &utPolicyStrLen, &utKeyUse);
			}
		}	
	}
	
	/* get verified (possibly partial) outCertGroup - error is fatal */
	/* BUT: we still return partial evidence if asked to...from now on. */
	TPCertGroup outCertGroup(*this, 
		TGO_Caller);		// certs are owned by inCertGroup
	TPCertGroup inCertGroup(CertGroupToBeVerified, clHand, cspHand, *this, 
		cssmTimeStr, 		// optional 'this' time
		true, 				// firstCertMustBeValid
		TGO_Group);	
		
	/* set up for disposal of TPCertInfos created by CertGroupConstructPriv */
	TPCertGroup	gatheredCerts(*this, TGO_Group);
	
	try {
		CertGroupConstructPriv(
			clHand,
			cspHand,
			inCertGroup,
			cred->DBList, 
			cssmTimeStr,
			cred->NumberOfAnchorCerts,
			cred->AnchorCerts,
			actionFlags,
			utPolicyOid,
			utPolicyStr,
			utPolicyStrLen,
			utKeyUse,
			gatheredCerts,
			verifiedToRoot, 
			verifiedToAnchor,
			verifiedViaTrustSetting,
			outCertGroup);
	}
	catch(const CssmError &cerr) {
		constructReturn = cerr.error;
		/* abort if no certs found */
		if(outCertGroup.numCerts() == 0) {
			CssmError::throwMe(constructReturn);
		}
		/* else press on, collecting as much info as we can */
	}
	/* others are way fatal */
	assert(outCertGroup.numCerts() >= 1);
	
	/* Infer interim status from return values */
	switch(constructReturn) {
		/* these values do not get overridden */
		case CSSMERR_TP_CERTIFICATE_CANT_OPERATE:
		case CSSMERR_TP_INVALID_CERT_AUTHORITY:
		case CSSMERR_APPLETP_TRUST_SETTING_DENY:
		case errSecInvalidTrustSettings:
			break;
		default:
			/* infer status from these values... */
			if(verifiedToAnchor || verifiedViaTrustSetting) {
				/* full success; anchor doesn't have to be root */
				constructReturn = CSSM_OK;
			}
			else if(verifiedToRoot) {
				if(actionFlags & CSSM_TP_ACTION_IMPLICIT_ANCHORS) {
					constructReturn = CSSM_OK;
				}
				else {
					/* verified to root which is not an anchor */
					constructReturn = CSSMERR_TP_INVALID_ANCHOR_CERT;
				}
			}
			else {
				/* partial chain, no root, not verifiable by anchor */
				constructReturn = CSSMERR_TP_NOT_TRUSTED;
			}

			/* 
 			 * Those errors can be allowed, cert-chain-wide, per individual
			 * certs' allowedErrors
			 */
			if((constructReturn != CSSM_OK) && 
			    outCertGroup.isAllowedError(constructReturn)) {
				constructReturn = CSSM_OK;
			}
			break;
	}
	
	/*
	 * Parameters passed to tp_policyVerify() and which vary per policy
	 * in the loop below 
	 */
	TPPolicy tpPolicy;
	const CSSM_APPLE_TP_SSL_OPTIONS	*sslOpts;
	CSSM_RETURN thisPolicyRtn = CSSM_OK;	// returned from tp_policyVerify()
	
	/* common CRL verify parameters */
	TPCrlGroup *crlGroup = NULL;
	try {
		crlGroup = new TPCrlGroup(&VerifyContext->Crls,
			clHand, cspHand, 
			*this,				// alloc
			NULL, 				// cssmTimeStr - we want CRLs that are valid 'now'
			TGO_Group);
	}
	catch(const CssmError &cerr) {
		CSSM_RETURN cr = cerr.error;
		/* I don't see a straightforward way to report this error,
		 * other than adding it to the leaf cert's status... */
		outCertGroup.certAtIndex(0)->addStatusCode(cr);
		tpDebug("CertGroupVerify: error constructing CrlGroup; continuing\n");
	}
	/* others are way fatal */

	TPVerifyContext revokeVfyContext(*this,
		clHand,
		cspHand,
		cssmTimeStr,
		cred->NumberOfAnchorCerts,
		cred->AnchorCerts,
		&inCertGroup,
		crlGroup,
		/*
		 * This may consist of certs gathered from the net (which is the purpose
		 * of this argument) and from DLDBs (a side-effect optimization).
		 */
		gatheredCerts,
		cred->DBList,
		kRevokeNone,		// policy
		actionFlags,
		NULL,				// CRL options
		NULL,				// OCSP options
		utPolicyOid,
		utPolicyStr,
		utPolicyStrLen,
		utKeyUse);
		
	/* true if we're to execute tp_policyVerify at end of loop */
	bool doPolicyVerify;
	/* true if we're to execute a revocation policy at end of loop */
	bool doRevocationPolicy;
	
	/* grind thru each policy */
	for(uint32 polDex=0; polDex<cred->Policy.NumberOfPolicyIds; polDex++) {
		if(cred->Policy.PolicyIds == NULL) {
			policyReturn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
			break;
		}
		CSSM_FIELD_PTR policyId = &cred->Policy.PolicyIds[polDex];
		const CSSM_DATA *fieldVal = &policyId->FieldValue;
		const CSSM_OID	*oid = &policyId->FieldOid;
		thisPolicyRtn = CSSM_OK;
		doPolicyVerify = false;
		doRevocationPolicy = false;
		sslOpts = NULL;
		
		/* first the basic cert policies */
		doPolicyVerify = checkPolicyOid(*oid, tpPolicy);
		if(doPolicyVerify) {
			/* some basic checks... */
			bool policyAbort = false;
			switch(tpPolicy) {
				case kTPx509Basic:
				case kTPiSign:
				case kTP_PKINIT_Client:
				case kTP_PKINIT_Server:
					if(fieldVal->Data != NULL) {
						policyReturn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
						policyAbort = true;
						break;
					}
					break;
				default:
					break;
			}
			if(policyAbort) {
				break;
			}
			#if		TP_PKINIT_SERVER_HACK
			if(tpPolicy == kTP_PKINIT_Server) {
				/* possible override of "root not in anchors" */
				if(constructReturn == CSSMERR_TP_INVALID_ANCHOR_CERT) {
					if(tpCheckPkinitServerCert(outCertGroup)) {
						constructReturn = CSSM_OK;
					}
				}
			}
			#endif	/* TP_PKINIT_SERVER_HACK */
		}
		
		/* 
		 * Now revocation policies. Note some fields in revokeVfyContext can 
		 * accumulate across multiple policy calls, e.g., signerCerts. 
		 */
		else if(tpCompareOids(oid, &CSSMOID_APPLE_TP_REVOCATION_CRL)) {
			/* CRL-specific options */
			const CSSM_APPLE_TP_CRL_OPTIONS *crlOpts;
			crlOpts = (CSSM_APPLE_TP_CRL_OPTIONS *)fieldVal->Data;
			thisPolicyRtn = CSSM_OK;
			if(crlOpts != NULL) {
				switch(crlOpts->Version) {
					case CSSM_APPLE_TP_CRL_OPTS_VERSION:
						if(fieldVal->Length != 
								sizeof(CSSM_APPLE_TP_CRL_OPTIONS)) {
							thisPolicyRtn = 
								CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
							break;
						}
						break;
					/* handle backwards compatibility here if necessary */
					default:
						thisPolicyRtn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
						break;
				}
				if(thisPolicyRtn != CSSM_OK) {
					policyReturn = thisPolicyRtn;
					break;
				}
			}
			revokeVfyContext.policy = kRevokeCrlBasic;
			revokeVfyContext.crlOpts = crlOpts;
			doRevocationPolicy = true;
		}
		else if(tpCompareOids(oid, &CSSMOID_APPLE_TP_REVOCATION_OCSP)) {
			/* OCSP-specific options */
			const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts;
			ocspOpts = (CSSM_APPLE_TP_OCSP_OPTIONS *)fieldVal->Data;
			thisPolicyRtn = CSSM_OK;
			if(ocspOpts != NULL) {
				switch(ocspOpts->Version) {
					case CSSM_APPLE_TP_OCSP_OPTS_VERSION:
						if(fieldVal->Length != 
								sizeof(CSSM_APPLE_TP_OCSP_OPTIONS)) {
							thisPolicyRtn = 
								CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
							break;
						}
						break;
					/* handle backwards compatibility here if necessary */
					default:
						thisPolicyRtn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
						break;
				}
				if(thisPolicyRtn != CSSM_OK) {
					policyReturn = thisPolicyRtn;
					break;
				}
			}
			revokeVfyContext.policy = kRevokeOcsp;
			revokeVfyContext.ocspOpts = ocspOpts;
			doRevocationPolicy = true;
		}
		/* etc. - add more policies here */
		else {
			/* unknown TP policy OID */
			policyReturn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
			break;
		}
		
		/* common cert policy call */
		if(doPolicyVerify) {
			assert(!doRevocationPolicy);	// one at a time
			thisPolicyRtn = tp_policyVerify(tpPolicy,
				*this,
				clHand,
				cspHand,
				&outCertGroup,
				verifiedToRoot,
				verifiedViaTrustSetting,
				actionFlags,
				fieldVal,
				cred->Policy.PolicyControl);	// not currently used
			didCertPolicy = true;
		}
		/* common revocation policy call */
		if(doRevocationPolicy) {
			assert(!doPolicyVerify);	// one at a time
			thisPolicyRtn = tpRevocationPolicyVerify(revokeVfyContext, outCertGroup);
			didRevokePolicy = true;
		}
		/* See if possible error is allowed, cert-chain-wide. */
		if((thisPolicyRtn != CSSM_OK) &&
		    outCertGroup.isAllowedError(thisPolicyRtn)) {
			thisPolicyRtn = CSSM_OK;
		}
		if(thisPolicyRtn) {
			/* Now remember the error if it's the first policy
			 * error we've seen. */
			if(policyReturn == CSSM_OK) {
				policyReturn = thisPolicyRtn;
			}
			/* Keep going? */
			if(tpStopOn == CSSM_TP_STOP_ON_FIRST_FAIL) {
				/* Nope; we're done with policy evaluation */
				break;
			}
		}
	}	/* for each policy */
	
	/*
	 * Upon completion of the above loop, perform default policy ops if
	 * appropriate.
	 */
	if((policyReturn == CSSM_OK) || (tpStopOn == CSSM_TP_STOP_ON_NONE)) {
		if(!didCertPolicy) {
			policyReturn = tp_policyVerify(kTPDefault,
				*this,
				clHand,
				cspHand,
				&outCertGroup,
				verifiedToRoot,
				verifiedViaTrustSetting,
				actionFlags,
				NULL,							// policyFieldData
				cred->Policy.PolicyControl);	// not currently used
			/* See if error is allowed, cert-chain-wide. */
			if((policyReturn != CSSM_OK) &&
				outCertGroup.isAllowedError(policyReturn)) {
				policyReturn = CSSM_OK;
			}
		}
		if( !didRevokePolicy &&							// no revoke policy yet
			( (policyReturn == CSSM_OK || 				// default cert policy OK
		      (tpStopOn == CSSM_TP_STOP_ON_NONE)) 		// keep going anyway
			)
		  ) {
			revokeVfyContext.policy = TP_CRL_POLICY_DEFAULT;
			CSSM_RETURN thisPolicyRtn = tpRevocationPolicyVerify(revokeVfyContext, 
				outCertGroup);
			if((thisPolicyRtn != CSSM_OK) &&
				outCertGroup.isAllowedError(thisPolicyRtn)) {
				thisPolicyRtn = CSSM_OK;
			}
			if((thisPolicyRtn != CSSM_OK) && (policyReturn == CSSM_OK)) {
				policyReturn = thisPolicyRtn;
			}
		
		}
	}	/* default policy opts */
	
	delete crlGroup;
	
	/* return evidence - i.e., constructed chain - if asked to */
	if(VerifyContextResult != NULL) {
		/*
		 * VerifyContextResult->Evidence[0] : CSSM_TP_APPLE_EVIDENCE_HEADER
		 * VerifyContextResult->Evidence[1] : CSSM_CERTGROUP
		 * VerifyContextResult->Evidence[2] : CSSM_TP_APPLE_EVIDENCE_INFO
		 */
		VerifyContextResult->NumberOfEvidences = 3;
		VerifyContextResult->Evidence = 
			(CSSM_EVIDENCE_PTR)calloc(3, sizeof(CSSM_EVIDENCE));

		CSSM_TP_APPLE_EVIDENCE_HEADER *hdr = 
			(CSSM_TP_APPLE_EVIDENCE_HEADER *)malloc(
				sizeof(CSSM_TP_APPLE_EVIDENCE_HEADER));
		hdr->Version = CSSM_TP_APPLE_EVIDENCE_VERSION;
		CSSM_EVIDENCE_PTR ev = &VerifyContextResult->Evidence[0];
		ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_HEADER;
		ev->Evidence = hdr;
		
		ev = &VerifyContextResult->Evidence[1];
		ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_CERTGROUP;
		ev->Evidence = outCertGroup.buildCssmCertGroup();
		
		ev = &VerifyContextResult->Evidence[2];
		ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_CERT_INFO;
		ev->Evidence = outCertGroup.buildCssmEvidenceInfo();
	}
	else {
		/* caller responsible for freeing these if they are for evidence.... */
		outCertGroup.freeDbRecords();
	}
	CSSM_RETURN outErr = outCertGroup.getReturnCode(constructReturn, policyReturn,
		actionFlags);
	
	if(outErr) {
		CssmError::throwMe(outErr);
	}
}
/*
 * Fetch a CRL or a cert via a GeneralNames.
 * Shared by cert and CRL code to avoid duplicating GeneralNames traversal
 * code, despite the awkward interface for this function. 
 */
static CSSM_RETURN tpFetchViaGeneralNames(
	const CE_GeneralNames	*names,
	TPCertInfo 				&forCert,
	const CSSM_DATA			*issuer,			// optional, and only for CRLs
	TPVerifyContext			*verifyContext,		// only for CRLs
	CSSM_CL_HANDLE			clHand,				// only for certs
	CSSM_CSP_HANDLE			cspHand,			// only for certs
	const char				*verifyTime,		// optional
	/* exactly one must be non-NULL, that one is returned */
	TPCertInfo				**certInfo,
	TPCrlInfo				**crlInfo)
{	
	assert(certInfo || crlInfo);
	assert(!certInfo || !crlInfo);
	CSSM_RETURN crtn;
	
	for(unsigned nameDex=0; nameDex<names->numNames; nameDex++) {
		CE_GeneralName *name = &names->generalName[nameDex];
		switch(name->nameType) {
			case GNT_URI:
				if(name->name.Length < 5) {
					continue;
				}
				if(strncmp((char *)name->name.Data, "ldap:", 5) &&
				   strncmp((char *)name->name.Data, "http:", 5) && 
				   strncmp((char *)name->name.Data, "https:", 6)) {
					/* eventually handle other schemes here */
					continue;
				}
				if(certInfo) {
					tpDebug("   fetching cert via net"); 
					crtn = tpIssuerCertViaNet(name->name, 
						clHand,
						cspHand,
						verifyTime,
						forCert,
						*certInfo);
				}
				else {
					tpDebug("   fetching CRL via net"); 
					assert(verifyContext != NULL);
					crtn = tpCrlViaNet(name->name, 
						issuer,
						*verifyContext,
						forCert,
						*crlInfo);
				}
				switch(crtn) {
					case CSSM_OK:
					case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:	// caller handles
						return crtn;
					default:
						break;
				}
				/* not found/no good; try again */
				break;
			default:
				tpCrlDebug("  tpFetchCrlFromNet: unknown"
					"nameType (%u)", (unsigned)name->nameType); 
				break;
		}	/* switch nameType */
	}	/* for each name */
	if(certInfo) {
		return CSSMERR_TP_CERTGROUP_INCOMPLETE;
	}
	else {
		return CSSMERR_APPLETP_CRL_NOT_FOUND;
	}
}
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;
}
/*
 * 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;
}