tpCertGroup.cpp   [plain text]


/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This 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.
 */


/*
 * tpCertGroup.cpp - Cert group functions (construct, verify) 
 *
 * Created 10/5/2000 by Doug Mitchell.
 */
 
#include "AppleTPSession.h"
#include "certGroupUtils.h"
#include "TPCertInfo.h"
#include "tpPolicies.h"
#include "tpdebugging.h"
#include "rootCerts.h"
#include <Security/oidsalg.h>
#include <Security/cssmapple.h>

/*-----------------------------------------------------------------------------
 * CertGroupConstruct
 *
 * Description:
 *   This function returns a pointer to a mallocd CSSM_CERTGROUP which
 *   refers to a mallocd list of raw ordered X.509 certs which verify back as
 *   far as the TP is able to go. The first cert of the returned list is the
 *   subject cert. The TP will attempt to search thru the DBs passed in
 *   DBList in order to complete the chain. The chain is completed when a
 *   self-signed (root) cert is found in the chain. The root cert may be
 *   present in the input CertGroupFrag, or it may have been obtained from
 *   one of the DBs passed in DBList. It is not an error if no root cert is
 *   found. 
 *   
 *   The error conditions are: 
 *   -- The first cert of CertGroupFrag is an invalid cert. NULL is returned,
 *		err = CSSM_TP_INVALID_CERTIFICATE.
 *   -- The root cert (if found) fails to verify. Valid certgroup is returned,
 *      err = CSSMERR_TP_VERIFICATION_FAILURE.
 *   -- Any cert in the (possibly partially) constructed chain has expired or
 *      isn't valid yet, err = CSSMERR_TP_CERT_EXPIRED or 
 *      CSSMERR_TP_CERT_NOT_VALID_YET. A CertGroup is returned. 
 *   -- CSSMERR_TP_CERT_EXPIRED and CSSMERR_TP_CERT_NOT_VALID_YET. If one of these
 *		conditions obtains for the first (leaf) cert, the function throws this
 *		error immediately and the outgoing cert group is empty. For subsequent certs,
 *		the temporal validity of a cert is only tested AFTER a cert successfully
 *		meets the cert chaining criteria (subject/issuer match and signature
 *		verify). A cert in a chain with this error is not added to the outgoing
 *		cert group. 
 *   -- the usual errors like bad handle or memory failure. 
 *
 * Parameters:
 *   Two handles - to an open CL and CSP. The CSP must be capable of
 *   dealing with the signature algorithms used by the certs. The CL must be
 *   an X.509-savvy CL.  
 *   
 *   CertGroupFrag, an unordered array of raw X.509 certs in the form of a
 *   CSSM_CERTGROUP_PTR. The first cert of this list is the subject cert
 *   which is eventually to be verified. The other certs can be in any order 
 *   and may not even have any relevance to the cert chain being constructed. 
 *   They may also be invalid certs. 
 *   
 *   DBList, a list of DB/DL handles which may contain certs necessary to
 *   complete the desired cert chain. (Not currently implemented.)
 *
 *---------------------------------------------------------------------------*/
 
/* public version */
void AppleTPSession::CertGroupConstruct(CSSM_CL_HANDLE clHand,
		CSSM_CSP_HANDLE cspHand,
		const CSSM_DL_DB_LIST &DBList,
		const void *ConstructParams,
		const CSSM_CERTGROUP &CertGroupFrag,
		CSSM_CERTGROUP_PTR &CertGroup)
{
	TPCertGroup *tpCertGroup;
	CertGroupConstructPriv(clHand,
		cspHand,
		DBList,
		ConstructParams,
		CertGroupFrag,
		CSSM_FALSE,			// allowExpired
		NULL,				// cssmTimeStr
		tpCertGroup);
	CertGroup = tpCertGroup->buildCssmCertGroup();
	delete tpCertGroup;	
}


/* 
 * Private version of CertGroupConstruct, used by CertGroupConstruct and
 * CertGroupVerify. Returns a TP-style TPCertGroup for further processing.
 * This only throws CSSM-style exceptions in the following cases: 
 * 
 *  -- input parameter errors
 *  -- the first (leaf) cert is bad (doesn't parse, expired, not valid yet).
 *
 *  All other cert-related errors simply result in the bad cert being ignored.
 *  Other exceptions are gross system errors like malloc failure.
 */
void AppleTPSession::CertGroupConstructPriv(CSSM_CL_HANDLE clHand,
		CSSM_CSP_HANDLE cspHand,
		const CSSM_DL_DB_LIST &DBList,
		const void *ConstructParams,
		const CSSM_CERTGROUP &CertGroupFrag,
		CSSM_BOOL allowExpired,
		const char *cssmTimeStr,					// May be NULL
		TPCertGroup *&CertGroup)
{
	TPCertGroup			*inCertGroup;				// unordered input certs
	TPCertGroup			*outCertGroup;				// ordered, verified output certs

	/*
	 * subjectCert refers to the cert we're currently trying to verify. It's either
	 * an element in inCertGroup (if we're verifying a cert from the incoming
	 * CertGroupFrag) or dbSubject (if we're verifying a cert which came from a DB).
	 *
	 * Similarly, issuerCert, when non-NULL, points to a cert which has just
	 * been located as a verifiable issuer of subjectCert. It points to either
	 * an element in inCertGroup or to dbIssuer.
	 */
	TPCertInfo			*subjectCert;				// the one we're working on
	TPCertInfo			*issuerCert = NULL;			// verified as next one in chain
	TPCertInfo			*certInfo;					// working cert
	unsigned			certDex;					// index into certInfo
	CSSM_RETURN			crtn;
	CSSM_RETURN			outErr = CSSM_OK;
	
	/* verify input args */
	if(cspHand == CSSM_INVALID_HANDLE) {
		CssmError::throwMe(CSSMERR_TP_INVALID_CSP_HANDLE);
	}
	if(clHand == CSSM_INVALID_HANDLE)	{
		CssmError::throwMe(CSSMERR_TP_INVALID_CL_HANDLE);
	}
	if( (CertGroupFrag.NumCerts == 0) ||				// list is empty
	    (CertGroupFrag.CertGroupType != CSSM_CERTGROUP_DATA) ||
	    (CertGroupFrag.GroupList.CertList[0].Data == NULL) ||	// first cert empty
	    (CertGroupFrag.GroupList.CertList[0].Length == 0)) {		// first cert empty
		CssmError::throwMe(CSSMERR_CL_INVALID_CERTGROUP_POINTER);
	}
	switch(CertGroupFrag.CertType) {
		case CSSM_CERT_X_509v1:
		case CSSM_CERT_X_509v2:
		case CSSM_CERT_X_509v3:
			break;
		default:
			CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT);
	}
	switch(CertGroupFrag.CertEncoding) {
		case CSSM_CERT_ENCODING_BER:
		case CSSM_CERT_ENCODING_DER:
			break;
		default:
			CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT);
	}
	
	/* 
	 * Set up incoming and outgoing TPCertGrorups. 
	 */
	inCertGroup  = new TPCertGroup(*this, CertGroupFrag.NumCerts - 1);
	outCertGroup = new TPCertGroup(*this, CertGroupFrag.NumCerts);
	
	/*
	 * Parse first (leaf) cert. Note that this cert is special: if it's bad we abort
	 * immediately; otherwise it goes directly into outCertGroup.
	 */
	try {
		certInfo = new TPCertInfo(
			&CertGroupFrag.GroupList.CertList[0],
			clHand,
			cssmTimeStr);
		certInfo->index(0);
	}
	catch(const CssmError &cerr) {
		outErr = CSSMERR_TP_INVALID_CERTIFICATE;
		goto abort;
	}
	catch(...) {
		/* everything else is way fatal */
		throw;
	}
	
	/* Add to outCertGroup even if it's not current */
	outCertGroup->appendCert(certInfo);
	
	#if 	TP_CERT_CURRENT_CHECK_INLINE
	/* verify this first one is current */
	outErr = certInfo->isCurrent(allowExpired);
	if(outErr) {
		goto abort;
	}
	#endif
	
	/* this'll be the first subject cert in the main loop */
	subjectCert = certInfo;
	
	/* 
	 * Add remaining input certs to inCertGroup. Note that this lets us 
	 * skip bad incoming certs right away.
	 */
	for(certDex=1; certDex<CertGroupFrag.NumCerts; certDex++) {
		try {
			certInfo = new TPCertInfo(&CertGroupFrag.GroupList.CertList[certDex],
				clHand,
				cssmTimeStr);
		}
		catch (...) {
			/* just ignore this cert */
			continue;
		}
		certInfo->index(certDex);
		inCertGroup->appendCert(certInfo);
	}
	
	/*** main loop ***
	 *
	 * On entry, we have two TPCertGroups. InCertGroup contains n-1 certs, where n 
	 * is the size of the CertGroupFrag passed to us by the caller. The certs in
	 * inCertGroup are unordered but are known to be parseable, CL-cacheable certs.
	 * OutGroupCert contains one cert, the incoming leaf cert.
	 *
	 * The job in this loop is to build an ordered, verified cert chain in  
	 * outCertGroup out of certs from inCertGroup and/or DBList. As good certs
	 * are found in inCertGroup, they're removed from that TPCertGroup. On exit
	 * we delete inCertGroup, which deletes all the remaining TPCertInfo's in it. 
	 * The constructed outCertGroup is returned to the caller. 
	 *
	 * Exit loop on: 
	 *   -- find a root cert in the chain
	 *   -- memory error
	 *   -- or no more certs to add to chain. 
	 */
	for(;;) {
		/* top of loop: subjectCert is the cert we're trying to verify. */
		
		/* is this a root cert?  */
		if(subjectCert->isSelfSigned()) {
			/*
			 * Verify this alleged root cert. We're at the end of the chain no 
			 * matter what happens here. 
			 * Note we already validated before/after when this was tested
			 * as issuer (or, if it's the leaf cert, before we entered this loop). 
			 */ 
			outErr = tp_VerifyCert(clHand,
				cspHand,
				subjectCert,
				subjectCert,
				CSSM_FALSE,		// checkIssuerCurrent
				CSSM_TRUE);		// allowExpired, don't care
			break;
		}
		
		/* Search unused incoming certs to find an issuer */
		for(certDex=0; certDex<inCertGroup->numCerts(); certDex++) {
			certInfo = inCertGroup->certAtIndex(certDex);
			
			/* potential issuer - names match? */
			if(tpIsSameName(subjectCert->issuerName(), certInfo->subjectName())) {
				/* yep, do a sig verify with "not before/after" check */
				crtn = tp_VerifyCert(clHand,
					cspHand,
					subjectCert,
					certInfo,
					CSSM_TRUE,
					allowExpired);			
				switch(crtn) {
					case CSSM_OK:
						/* YES! We'll add it to outCertGroup below...*/
						issuerCert = certInfo;
						inCertGroup->removeCertAtIndex(certDex);
						goto issuerLoopEnd;
					case CSSMERR_TP_CERT_NOT_VALID_YET:
					case CSSMERR_TP_CERT_EXPIRED:
						/* special case - abort immediateley (note the cert
						 * sig verify succeeded.) */
						/*** for now we include this in the evidence ***/
						outCertGroup->appendCert(subjectCert);
						outErr = crtn;
						goto abort;
					default:
						/* just skip this one and keep looking */
						break;
				}
			} 	/* names match */
		} 		/* searching inCertGroup for issuer */
		
issuerLoopEnd:

		#if	TP_DL_ENABLE
		if(issuerCert == NULL) {
			/* Issuer not in incoming cert group. Search DBList. */
			CSSM_BOOL subjectExpired = CSSM_FALSE;
			issuerCert = tpFindIssuer(*this,
				clHand,
				cspHand,
				subjectCert,
				subjectCert->issuerName(),
				&DBList,
				cssmTimeStr,
				&subjectExpired);
			if(subjectExpired) {
				/* special case - abort immediately */
				outErr = subjectExpired;
				goto abort;
			}
		}	/*  Issuer not in incoming cert group */
		#endif	/* TP_DL_ENABLE */
		
		if(issuerCert == NULL) {
			/* end of search, broken chain */
			break;
		}
		
		/*
		 * One way or the other, we've found a cert which verifies subjectCert.
		 * Add the issuer to outCertGroup and make it the new subjectCert for
		 * the next pass.
		 */
		outCertGroup->appendCert(issuerCert);
		subjectCert = issuerCert;
		issuerCert = NULL;
	}	/* main loop */
	
abort:
	delete inCertGroup;
	CertGroup = outCertGroup;
	if(outErr) {
		CssmError::throwMe(outErr);
	}
}
/*-----------------------------------------------------------------------------
 * CertGroupVerify
 *
 * Description:
 *   -- Construct a cert chain using TP_CertGroupConstruct. 
 *   -- Attempt to verify that cert chain against one of the known 
 *      good certs passed in AnchorCerts. 
 *   -- Optionally enforces additional policies (TBD) when verifying the cert chain.
 *   -- Optionally returns the entire cert chain constructed in 
 *      TP_CertGroupConstruct and here, all the way to an anchor cert or as 
 *      far as we were able to go, in *Evidence. 
 *
 * Parameters:
 *   Two handles - to an open CL and CSP. The CSP must be capable of
 *   dealing with the signature algorithms used by the certs. The CL must be
 *   an X.509-savvy CL.  
 *   
 *   RawCerts, an unordered array of raw certs in the form of a
 *   CSSM_CERTGROUP_PTR. The first cert of this list is the subject cert
 *   which is eventually to be verified. The other certs can be in any order
 *   and may not even have any relevance to the cert chain being constructed.
 *   They may also be invalid certs. 
 *   
 *   DBList, a list of DB/DL handles which may contain certs necessary to
 *   complete the desired cert chain. (Currently not implemented.)
 *   
 *   AnchorCerts, a list of known trusted certs. 
 *   NumberOfAnchorCerts, size of AnchorCerts array. 
 *   
 *   PolicyIdentifiers, Optional policy OID. NULL indicates default
 *		X.509 trust policy.
 *
 *	 Supported Policies:
 *			CSSMOID_APPLE_ISIGN
 *			CSSMOID_APPLE_X509_BASIC
 *		
 *			For both of these, the associated FieldValue must be {0, NULL},
 *
 *   NumberOfPolicyIdentifiers, size of PolicyIdentifiers array, must be 
 *      zero or one. 
 * 
 *   All other arguments must be zero/NULL.
 *
 *   Returns:
 *      CSSM_OK : cert chain verified all the way back to an AnchorCert.
 *      CSSMERR_TP_INVALID_ANCHOR_CERT : In this case, the cert chain
 *   		was validated back to a self-signed (root) cert found in either
 *   		CertToBeVerified or in one of the DBs in DBList, but that root cert
 *   		was *NOT* found in the AnchorCert list. 
 *		CSSMERR_TP_NOT_TRUSTED: no root cert was found and no AnchorCert
 *   		verified the end of the constructed cert chain.
 *		CSSMERR_TP_VERIFICATION_FAILURE: a root cert was found which does
 *   		not self-verify. 
 *   	CSSMERR_TP_VERIFY_ACTION_FAILED: indicates a failure of the requested 
 *			policy action. 
 *   	CSSMERR_TP_INVALID_CERTIFICATE: indicates a bad leaf cert. 
 *		CSSMERR_TP_INVALID_REQUEST_INPUTS : no incoming VerifyContext.
 *		CSSMERR_TP_CERT_EXPIRED and CSSMERR_TP_CERT_NOT_VALID_YET: see comments
 *			for CertGroupConstruct. 
 *---------------------------------------------------------------------------*/

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)
{
	unsigned				i;
	TPCertInfo				*lastCert;
	CSSM_BOOL				verifiedToRoot = CSSM_FALSE;
	TPPolicy				policy;
	CSSM_RETURN				constructReturn = CSSM_OK;
	CSSM_RETURN				policyReturn = CSSM_OK;
	CSSM_RETURN				crtn;
	const CSSM_TP_CALLERAUTH_CONTEXT *cred;
	CSSM_OID_PTR 			oid = NULL;
	TPCertGroup 			*tpCertGroup = NULL;	// created by
													// CertGroupConstructPriv
	TPCertInfo 				*certInfo = NULL;
	CSSM_BOOL				allowExpired = CSSM_FALSE;
	CSSM_BOOL				allowExpiredRoot = CSSM_FALSE;
	/* declare volatile as compiler workaround to avoid caching in CR4 */
	const CSSM_APPLE_TP_ACTION_DATA * volatile actionData = NULL;
	const CSSM_APPLE_TP_SSL_OPTIONS *sslOpts = NULL;
	const CSSM_DATA 		*fieldVal;
	CSSM_TIMESTRING			cssmTimeStr;
	
	/* 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;
	
	/* Check out requested policies */
	switch(cred->Policy.NumberOfPolicyIds) {
		case 0:
			/* default */
			policy = kTPDefault;
			break;			
	    case 1:
	    	if(cred->Policy.PolicyIds == NULL) {
				CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
	    	}
			fieldVal = &cred->Policy.PolicyIds->FieldValue;
			oid      = &cred->Policy.PolicyIds->FieldOid;
	    	if(tpCompareOids(oid, &CSSMOID_APPLE_ISIGN)) {
				policy = kTPiSign;
				/* no options */
				if(fieldVal->Data != NULL) {
					CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
				}
	    	}
	    	else if(tpCompareOids(oid, &CSSMOID_APPLE_X509_BASIC)) {
				policy = kTPx509Basic;
				/* no options */
				if(fieldVal->Data != NULL) {
					CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
				}
	    	}
	    	else if(tpCompareOids(oid, &CSSMOID_APPLE_TP_SSL)) {
				policy = kTP_SSL;
				/* SSL-specific options */
				sslOpts = (CSSM_APPLE_TP_SSL_OPTIONS *)fieldVal->Data;
				if(sslOpts != NULL) {
					switch(sslOpts->Version) {
						case CSSM_APPLE_TP_SSL_OPTS_VERSION:
							if(fieldVal->Length != 
									sizeof(CSSM_APPLE_TP_SSL_OPTIONS)) {
								CssmError::throwMe(
									CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
							}
							break;
						/* handle backwards compatibility here if necessary */
						default:
							CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
					}
				}
	    	}
	    	else {
	    		/* unknown TP OID */
				CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
	    	}
	    	break;
		default:
			/* only zero or one allowed */
			CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
	} 
	
	/* 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 byond version 0 */
			default:
				CssmError::throwMe(CSSMERR_TP_INVALID_ACTION_DATA);
		}
		if(actionData->ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED) {
			allowExpired = CSSM_TRUE;
		}
		if(actionData->ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED_ROOT) {
			allowExpiredRoot = CSSM_TRUE;
		}
	}
	
	/* optional, may be NULL */
	cssmTimeStr = cred->VerifyTime;
	
	/* now the args we can't deal with */
	if(cred->CallerCredentials != NULL) {
			CssmError::throwMe(CSSMERR_TP_INVALID_CALLERAUTH_CONTEXT_POINTER);
	}
	/* FIXME - ANY OTHERS? */
	
	/* get verified (possibly partial) outCertGroup - error is fatal */
	/* BUT: we still return partial evidence if asked to...from now on. */
	try {
		CertGroupConstructPriv(
			clHand,
			cspHand,
			*cred->DBList, 		// not optional to Construct!
			NULL,
			CertGroupToBeVerified,
			allowExpired,
			cssmTimeStr,
			tpCertGroup);
	}
	catch(const CssmError &cerr) {
		constructReturn = cerr.cssmError();
		/* abort if no certs found */
		if((tpCertGroup == NULL) || (tpCertGroup->numCerts() == 0)) {
			CssmError::throwMe(constructReturn);
		}
		/* else press on, collecting as much info as we can */
	}
	/* others are way fatal */
	CASSERT(tpCertGroup != NULL);
	CASSERT(tpCertGroup->numCerts() >= 1);
	
	/* subsequent errors and returns to out: */

	/*
	 * Case 1: last cert in outCertGroup is a root cert. See if 
	 * the root cert is in AnchorCerts.
	 * Note that TP_CertGroupConstruct did the actual root 
	 * self-verify test.
	 */
	lastCert = tpCertGroup->lastCert();
	if(lastCert->isSelfSigned()) {
		verifiedToRoot = CSSM_TRUE;
		
		/* see if that root cert is identical to one of the anchor certs */
		for(i=0; i<cred->NumberOfAnchorCerts; i++) {
			if(tp_CompareCerts(lastCert->certData(), &cred->AnchorCerts[i])) {
				/* one fully successful return */
				goto out;
			}
		}
		
		/* verified to a root cert which is not an anchor */
		constructReturn = CSSMERR_TP_INVALID_ANCHOR_CERT;
		goto out;
	}

	/* try to validate lastCert with anchor certs */
	for(i=0; i<cred->NumberOfAnchorCerts; i++) {
		try {
			certInfo = new TPCertInfo(&cred->AnchorCerts[i],
				clHand,
				cssmTimeStr);
		}
		catch(...) {
			/* bad anchor cert - ignore it */
			continue;
		}
		if(!tpIsSameName(lastCert->issuerName(), certInfo->subjectName())) {
			/* not this anchor */
			delete certInfo;
			continue;
		}
		crtn = tp_VerifyCert(clHand, 
			cspHand, 
			lastCert, 
			certInfo, 
			CSSM_TRUE,				// check not/before of anchor
			allowExpired);
		switch(crtn) {
			case CSSM_OK:
				/*  The other normal fully successful return. */
				if(certInfo->isSelfSigned()) {
					verifiedToRoot = CSSM_TRUE;	
				}
				
				/*
				 * One more thing: add this anchor cert to the Evidence chain
				 */
				try {
					tpCertGroup->appendCert(certInfo);
					certInfo->isAnchor(true);
					certInfo->index(i);
				}
				catch(...) {
					/* shoot - must be memory error */
					verifiedToRoot = CSSM_FALSE;
					delete certInfo;
					constructReturn = CSSMERR_TP_MEMORY_ERROR;
				}
				goto out;
				
			#if 	TP_CERT_CURRENT_CHECK_INLINE
			case CSSMERR_TP_CERT_NOT_VALID_YET:
			case CSSMERR_TP_CERT_EXPIRED:
				/* special case - abort immediateley */
				delete certInfo;
				constructReturn = crtn;
				goto out;
			#endif	/* TP_CERT_CURRENT_CHECK_INLINE */
			
			default:
				/* continue to next anchor */
				delete certInfo;
				break;
		}
	}	/* for each anchor */
	
	/* partial chain, no root, not verifiable by anchor */
	constructReturn = CSSMERR_TP_NOT_TRUSTED;

	/* common exit - error or success */
out:
	/* 
	 * Do further policy verification if appropriate.
	 *
	 * SSL: CSSMERR_TP_NOT_TRUSTED and CSSMERR_TP_INVALID_ANCHOR_CERT
	 * are both special cases which can result in full success. 
	 */
	#if 	TP_ROOT_CERT_ENABLE
	if((policy == kTP_SSL) && 
	   (constructReturn == CSSMERR_TP_NOT_TRUSTED) &&
	   (actionData != NULL) &&
	   (actionData->ActionFlags & 0x80000000)) {// The secret "enable root cert check" flag
		/* see if last cert can be verified by an embedded SSL root */
		certInfo = tpCertGroup->lastCert();
		CSSM_BOOL brtn = tp_verifyWithSslRoots(clHand, 
			cspHand, 
			certInfo);
		if(brtn) {
			/* SSL success with no incoming root */
			/* note unknown incoming root (INVALID_ANCHOR_CERT) is handled
			 * below, after tp_policyVerify */
			constructReturn = CSSM_OK;
		}
	}
	#endif	/* TP_ROOT_CERT_ENABLE */
	if(tpCertGroup->numCerts() != 0) {
		/* policy check if we saw even one cert */
		policyReturn = tp_policyVerify(policy,
			*this,
			clHand,
			cspHand,
			tpCertGroup,
			verifiedToRoot,
			actionData,
			sslOpts,
			cred->Policy.PolicyControl);		// not currently used
		#if TP_ROOT_CERT_ENABLE
		if((policyReturn == CSSM_OK) &&
		   (constructReturn == CSSMERR_TP_INVALID_ANCHOR_CERT) && 
		   (policy == kTP_SSL) &&
		   (actionData != NULL) &&
		   (actionData->ActionFlags & 0x80000000)) {
			/* The secret "enable root cert check" flag... */
			/* SSL - found a good anchor, move to full success */
			constructReturn = CSSM_OK;
		}
		#endif
	}

	/* return evidence - i.e., current 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 = tpCertGroup->buildCssmCertGroup();
		
		ev = &VerifyContextResult->Evidence[2];
		ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_CERT_INFO;
		ev->Evidence = tpCertGroup->buildCssmEvidenceInfo();

	}
	CSSM_RETURN outErr = tpCertGroup->getReturnCode(constructReturn,
		allowExpired, allowExpiredRoot, policyReturn);
		
	/* delete (internal use only) TPCertGroup */
	delete tpCertGroup;
	if(outErr) {
		CssmError::throwMe(outErr);
	}
}