]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_apple_x509_tp/lib/TPCertInfo.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_apple_x509_tp / lib / TPCertInfo.cpp
diff --git a/libsecurity_apple_x509_tp/lib/TPCertInfo.cpp b/libsecurity_apple_x509_tp/lib/TPCertInfo.cpp
new file mode 100644 (file)
index 0000000..c32bb40
--- /dev/null
@@ -0,0 +1,2289 @@
+/*
+ * Copyright (c) 2000-2011 Apple 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.
+ */
+
+
+/*
+ * TPCertInfo.cpp - TP's private certificate info classes
+ */
+
+#include "TPCertInfo.h"
+#include "tpdebugging.h"
+#include "tpTime.h"
+#include "certGroupUtils.h"
+#include "TPDatabase.h"
+#include "TPNetwork.h"
+#include <Security/cssmapi.h>
+#include <Security/x509defs.h>
+#include <Security/oidscert.h>
+#include <Security/oidsalg.h>
+#include <string.h>                                            /* for memcmp */
+#include <security_utilities/threading.h> /* for Mutex */
+#include <security_utilities/globalizer.h>
+#include <security_utilities/debugging.h>
+#include <security_cdsa_utilities/cssmerrors.h>
+#include <Security/cssmapple.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecImportExport.h>
+#include <Security/SecTrustSettingsPriv.h>
+
+#define tpTimeDbg(args...)             secdebug("tpTime", ## args) 
+#define tpCertInfoDbg(args...) secdebug("tpCert", ## args)
+
+static const TPClItemCalls tpCertClCalls =
+{
+       CSSM_CL_CertGetFirstCachedFieldValue,
+       CSSM_CL_CertAbortQuery,
+       CSSM_CL_CertCache,
+       CSSM_CL_CertAbortCache,
+       CSSM_CL_CertVerify,
+       &CSSMOID_X509V1ValidityNotBefore,
+       &CSSMOID_X509V1ValidityNotAfter,
+       CSSMERR_TP_INVALID_CERT_POINTER,
+       CSSMERR_TP_CERT_EXPIRED,
+       CSSMERR_TP_CERT_NOT_VALID_YET
+};
+
+TPClItemInfo::TPClItemInfo(
+       CSSM_CL_HANDLE          clHand,
+       CSSM_CSP_HANDLE         cspHand,
+       const TPClItemCalls     &clCalls,
+       const CSSM_DATA         *itemData,
+       TPItemCopy                      copyItemData,
+       const char                      *verifyTime)    // may be NULL
+               :       
+                       mClHand(clHand),
+                       mCspHand(cspHand),
+                       mClCalls(clCalls),
+                       mWeOwnTheData(false),
+                       mCacheHand(0),
+                       mIssuerName(NULL),
+                       mItemData(NULL),
+                       mSigAlg(CSSM_ALGID_NONE),
+                       mNotBefore(NULL),
+                       mNotAfter(NULL),
+                       mIsExpired(false),
+                       mIsNotValidYet(false),
+                       mIndex(0)
+{
+       try {
+               CSSM_RETURN crtn = cacheItem(itemData, copyItemData);
+               if(crtn) {
+                       CssmError::throwMe(crtn);
+               }                       
+                       
+               /* 
+                * Fetch standard fields...
+                * Issue name assumes same OID for Certs and CRLs!
+                */
+               crtn = fetchField(&CSSMOID_X509V1IssuerName, &mIssuerName);
+               if(crtn) {
+                       CssmError::throwMe(crtn);
+               }
+               
+               /* 
+                * Signing algorithm, infer from TBS algId 
+                * Note this assumes that the OID for fetching this field is the
+                * same for CRLs and Certs.
+                */
+               CSSM_DATA_PTR algField;
+               crtn = fetchField(&CSSMOID_X509V1SignatureAlgorithmTBS, &algField);
+               if(crtn) {
+                       releaseResources();
+                       CssmError::throwMe(crtn);
+               }
+               if(algField->Length != sizeof(CSSM_X509_ALGORITHM_IDENTIFIER)) {
+                       tpErrorLog("TPClItemInfo: bad CSSM_X509_ALGORITHM_IDENTIFIER\n");
+                       CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+               }
+               CSSM_X509_ALGORITHM_IDENTIFIER *algId = 
+                       (CSSM_X509_ALGORITHM_IDENTIFIER *)algField->Data;
+               bool algFound = cssmOidToAlg(&algId->algorithm, &mSigAlg);
+               if(!algFound) {
+                       tpErrorLog("TPClItemInfo: unknown signature algorithm\n");
+                       CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT);
+               }
+               if(mSigAlg == CSSM_ALGID_ECDSA_SPECIFIED) {
+                       /* Further processing needed to get digest algorithm */
+                       if(decodeECDSA_SigAlgParams(&algId->parameters, &mSigAlg)) {
+                               tpErrorLog("TPClItemInfo: incomplete/unknown ECDSA signature algorithm\n");
+                               CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT);
+                       }
+               }
+               freeField(&CSSMOID_X509V1SignatureAlgorithmTBS, algField);
+       
+               fetchNotBeforeAfter();
+               calculateCurrent(verifyTime);
+       }
+       catch(...) {
+               releaseResources();
+               throw;
+       }
+}
+
+TPClItemInfo::~TPClItemInfo()
+{
+       tpCertInfoDbg("TPClItemInfo destruct this %p", this);
+       releaseResources();
+}
+
+void TPClItemInfo::releaseResources()
+{
+       if(mWeOwnTheData && (mItemData != NULL)) {
+               tpFreeCssmData(Allocator::standard(), mItemData, CSSM_TRUE);
+               mWeOwnTheData = false;
+               mItemData = NULL;
+       }
+       if(mIssuerName) {
+               freeField(&CSSMOID_X509V1IssuerName, mIssuerName);
+               mIssuerName = NULL;
+       }
+       if(mCacheHand != 0) {
+               mClCalls.abortCache(mClHand, mCacheHand);
+               mCacheHand = 0;
+       }
+       if(mNotBefore) {
+               CFRelease(mNotBefore);
+               mNotBefore = NULL;
+       }
+       if(mNotAfter) {
+               CFRelease(mNotAfter);
+               mNotAfter = NULL;
+       }
+}
+
+/* fetch arbitrary field from cached cert */
+CSSM_RETURN TPClItemInfo::fetchField(
+       const CSSM_OID  *fieldOid,
+       CSSM_DATA_PTR   *fieldData)             // mallocd by CL and RETURNED
+{
+       CSSM_RETURN crtn;
+       
+       uint32 NumberOfFields = 0;
+       CSSM_HANDLE resultHand = 0;
+       *fieldData = NULL;
+
+       assert(mClCalls.getField != NULL);
+       assert(mCacheHand != 0);
+       crtn = mClCalls.getField(
+               mClHand,
+               mCacheHand,
+           fieldOid,
+           &resultHand,
+           &NumberOfFields,
+               fieldData);
+       if(crtn) {
+               return crtn;
+       }
+       if(NumberOfFields != 1) {
+               tpErrorLog("TPCertInfo::fetchField: numFields %d, expected 1\n", 
+                       (int)NumberOfFields);
+       }
+       mClCalls.abortQuery(mClHand, resultHand);
+       return CSSM_OK;
+}
+
+/* free arbitrary field obtained from fetchField() */
+CSSM_RETURN TPClItemInfo::freeField( 
+       const CSSM_OID  *fieldOid,
+       CSSM_DATA_PTR   fieldData)      
+{
+       return CSSM_CL_FreeFieldValue(mClHand, fieldOid, fieldData);
+
+}
+
+/* 
+ * Verify with an issuer cert - works on certs and CRLs.
+ * Issuer/subject name match already performed by caller.
+ * Optional paramCert is used to provide parameters when issuer
+ * has a partial public key.
+ */
+CSSM_RETURN TPClItemInfo::verifyWithIssuer(
+       TPCertInfo              *issuerCert,
+       TPCertInfo              *paramCert /* = NULL */) const
+{
+       CSSM_RETURN     crtn;
+
+       assert(mClHand != 0);
+       assert(issuerCert->isIssuerOf(*this));
+       assert(mCspHand != 0);
+       
+       /*
+        * Special case: detect partial public key right now; don't even 
+        * bother trying the cert verify in that case.
+        */
+       if(issuerCert->hasPartialKey() && (paramCert == NULL)) {
+               /* caller deals with this later */
+               tpVfyDebug("verifyWithIssuer PUBLIC_KEY_INCOMPLETE");
+               return CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE;
+       }
+       
+       CSSM_CC_HANDLE ccHand;
+       crtn = CSSM_CSP_CreateSignatureContext(mCspHand,
+               mSigAlg,
+               NULL,                   // Access Creds
+               issuerCert->pubKey(),
+               &ccHand);
+       if(crtn != CSSM_OK) {
+               tpErrorLog("verifyWithIssuer: CreateSignatureContext error\n");
+               CssmError::throwMe(crtn);
+       }
+       if(paramCert != NULL) {
+               assert(issuerCert->hasPartialKey());
+               
+               /* add in parameter-bearing key */
+               CSSM_CONTEXT_ATTRIBUTE          newAttr;        
+               
+               newAttr.AttributeType   = CSSM_ATTRIBUTE_PARAM_KEY;
+               newAttr.AttributeLength = sizeof(CSSM_KEY);
+               newAttr.Attribute.Key   = paramCert->pubKey();
+               crtn = CSSM_UpdateContextAttributes(ccHand, 1, &newAttr);
+               if(crtn) {
+                       tpErrorLog("verifyWithIssuer: CSSM_UpdateContextAttributes error\n");
+                       CssmError::throwMe(crtn);
+               }
+       }
+       crtn = mClCalls.itemVerify(mClHand, 
+       ccHand, 
+       mItemData,
+       NULL,                           // issuer cert
+       NULL,                           // VerifyScope
+       0);                                     // ScopeSize
+
+       switch(crtn) {
+               case CSSM_OK:           // success
+               case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:   // caller handles
+                       tpVfyDebug("verifyWithIssuer GOOD");
+                       break;
+               default:
+                       /* all others appear here as general cert verify error */
+                       crtn = CSSMERR_TP_VERIFICATION_FAILURE;
+                       tpVfyDebug("verifyWithIssuer BAD");
+                       break;
+       }
+       CSSM_DeleteContext(ccHand);
+       return crtn;
+}
+
+CSSM_RETURN TPClItemInfo::cacheItem(
+       const CSSM_DATA         *itemData,
+       TPItemCopy                      copyItemData)                                                                                           
+{
+       switch(copyItemData) {
+               case TIC_NoCopy:
+                       mItemData = const_cast<CSSM_DATA *>(itemData);
+                       break;
+               case TIC_CopyData:
+                       mItemData = tpMallocCopyCssmData(Allocator::standard(), itemData);
+                       mWeOwnTheData = true;
+                       break;
+               default:
+                       assert(0);
+                       CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       
+       /* cache the cert/CRL in the CL */
+       return mClCalls.cacheItem(mClHand, mItemData, &mCacheHand);
+}
+
+/* 
+ * Calculate not before/after times as struct tm. Only throws on 
+ * gross error (CSSMERR_TP_INVALID_CERT_POINTER, etc.).
+ *
+ * Only differences between Cert and CRL flavors of this are the 
+ * OIDs used to fetch the appropriate before/after times, both of
+ * which are expressed as CSSM_X509_TIME structs for both Certs 
+ * and CRLS.
+ */
+void TPClItemInfo::fetchNotBeforeAfter()
+{
+       CSSM_DATA_PTR   notBeforeField = NULL;
+       CSSM_DATA_PTR   notAfterField = NULL;
+       CSSM_RETURN             crtn = CSSM_OK;
+       CSSM_X509_TIME  *xTime;
+       
+       assert(cacheHand() != CSSM_INVALID_HANDLE);
+       crtn = fetchField(mClCalls.notBeforeOid, &notBeforeField);
+       if(crtn) {
+               tpErrorLog("fetchNotBeforeAfter: GetField error\n");
+               CssmError::throwMe(mClCalls.invalidItemRtn);
+       }
+       
+       /* subsequent errors to errOut */
+       xTime = (CSSM_X509_TIME *)notBeforeField->Data;
+       if(timeStringToCfDate((char *)xTime->time.Data, xTime->time.Length, &mNotBefore)) {
+               tpErrorLog("fetchNotBeforeAfter: malformed notBefore time\n");
+               crtn = mClCalls.invalidItemRtn;
+               goto errOut;
+       }
+
+       crtn = fetchField(mClCalls.notAfterOid, &notAfterField);
+       if(crtn) {
+               /*
+                * Tolerate a missing NextUpdate in CRL only 
+                */
+               if(mClCalls.notAfterOid == &CSSMOID_X509V1ValidityNotAfter) {
+                       tpErrorLog("fetchNotBeforeAfter: GetField error\n");
+                       crtn = mClCalls.invalidItemRtn;
+                       goto errOut;
+               }
+               else {
+                       /*
+                        * Fake NextUpdate to be "at the end of time"
+                        */
+                       timeStringToCfDate(CSSM_APPLE_CRL_END_OF_TIME, 
+                               strlen(CSSM_APPLE_CRL_END_OF_TIME), 
+                               &mNotAfter);
+               }
+       }
+       else {
+               xTime = (CSSM_X509_TIME *)notAfterField->Data;
+               if(timeStringToCfDate((char *)xTime->time.Data, xTime->time.Length, &mNotAfter)) {
+                       tpErrorLog("fetchNotBeforeAfter: malformed notAfter time\n");
+                       crtn = mClCalls.invalidItemRtn;
+                       goto errOut;
+               }
+       }
+       crtn = CSSM_OK;
+errOut:
+       if(notAfterField) {
+               freeField(mClCalls.notAfterOid, notAfterField);
+       }
+       if(notBeforeField) {
+               freeField(mClCalls.notBeforeOid, notBeforeField);
+       }
+       if(crtn != CSSM_OK) {
+               CssmError::throwMe(crtn);
+       }
+}
+
+/* 
+ * Verify validity (not before/after) by comparing the reference
+ * time (verifyString if present, or "now" if NULL) to the 
+ * not before/after fields fetched from the item at construction.
+ *
+ * Called implicitly at construction; can be called again any time
+ * to re-establish validity (e.g. after fetching an item from a cache).
+ *
+ * We use some stdlib time calls over in tpTime.c; the stdlib function
+ * gmtime() is not thread-safe, so we do the protection here. Note that
+ * this makes *our* calls to gmtime() thread-safe, but if the app has
+ * other threads which are also calling gmtime, we're out of luck.
+ */
+ModuleNexus<Mutex> tpTimeLock;
+
+CSSM_RETURN TPClItemInfo::calculateCurrent(
+       const char                      *verifyString)
+{
+       CFDateRef refTime = NULL;
+       
+       if(verifyString != NULL) {
+               /* caller specifies verification time base */
+               if(timeStringToCfDate(verifyString, strlen(verifyString), &refTime)) {
+                       tpErrorLog("calculateCurrent: timeStringToCfDate error\n");
+                       return CSSMERR_TP_INVALID_TIMESTRING;
+               }
+       }
+       else {
+               /* time base = right now */
+               refTime = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
+       }
+       if(compareTimes(refTime, mNotBefore) < 0) {
+               mIsNotValidYet = true;
+               tpTimeDbg("\nTP_CERT_NOT_VALID_YET: now %g notBefore %g", 
+                       CFDateGetAbsoluteTime(refTime), CFDateGetAbsoluteTime(mNotBefore));
+               CFRelease(refTime);
+               return mClCalls.notValidYetRtn;
+       }
+       else {
+               mIsNotValidYet = false;
+       }
+
+       if(compareTimes(refTime, mNotAfter) > 0) {
+               mIsExpired = true;
+               tpTimeDbg("\nTP_CERT_EXPIRED: now %g notBefore %g", 
+                       CFDateGetAbsoluteTime(refTime), CFDateGetAbsoluteTime(mNotBefore));
+               CFRelease(refTime);
+               return mClCalls.expiredRtn;
+       }
+       else {
+               mIsExpired = false;
+               CFRelease(refTime);
+               return CSSM_OK;
+       }
+}
+
+
+/* 
+ * No default constructor - this is the only way.
+ * This caches the cert and fetches subjectName, issuerName, and
+ * mPublicKey to ensure the incoming certData is well-constructed.
+ */
+TPCertInfo::TPCertInfo(
+       CSSM_CL_HANDLE          clHand,
+       CSSM_CSP_HANDLE         cspHand,
+       const CSSM_DATA         *certData,
+       TPItemCopy                      copyCertData,           // true: we copy, we free
+                                                                                       // false - caller owns
+       const char                      *verifyTime)            // may be NULL
+       :
+               TPClItemInfo(clHand, cspHand, tpCertClCalls, certData, 
+                       copyCertData, verifyTime),
+               mSubjectName(NULL),
+               mPublicKeyData(NULL),
+               mPublicKey(NULL),
+               mIsAnchor(false),
+               mIsFromInputCerts(false),
+               mIsFromNet(false),
+               mNumStatusCodes(0),
+               mStatusCodes(NULL),
+               mUniqueRecord(NULL),
+               mUsed(false),
+               mIsLeaf(false),
+               mIsRoot(TRS_Unknown),
+               mRevCheckGood(false),
+               mRevCheckComplete(false),
+               mTrustSettingsEvaluated(false),
+               mTrustSettingsDomain(kSecTrustSettingsDomainSystem),
+               mTrustSettingsResult(kSecTrustSettingsResultInvalid),
+               mTrustSettingsFoundAnyEntry(false),
+               mTrustSettingsFoundMatchingEntry(false),
+               mAllowedErrs(NULL),
+               mNumAllowedErrs(0),
+               mIgnoredError(false),
+               mTrustSettingsKeyUsage(0),
+               mCertHashStr(NULL)
+{
+       CSSM_RETURN     crtn;
+
+       tpCertInfoDbg("TPCertInfo construct this %p", this);
+       mDlDbHandle.DLHandle = 0;
+       mDlDbHandle.DBHandle = 0;
+       
+       /* fetch subject name */
+       crtn = fetchField(&CSSMOID_X509V1SubjectName, &mSubjectName);
+       if(crtn) {
+               /* bad cert */
+               releaseResources();
+               CssmError::throwMe(crtn);
+       }
+       
+       /* this cert's public key */
+       crtn = fetchField(&CSSMOID_CSSMKeyStruct, &mPublicKeyData);
+       if(crtn || (mPublicKeyData->Length != sizeof(CSSM_KEY))) {
+               /* bad cert */
+               releaseResources();
+               CssmError::throwMe(crtn);
+       }
+       mPublicKey = (CSSM_KEY_PTR)mPublicKeyData->Data;
+       
+       /* calculate other commonly used fields */
+       if(tpCompareCssmData(mSubjectName, issuerName())) {
+               /*
+                * Per Radar 3374978, perform complete signature verification
+                * lazily - just check subject/issuer match here.
+                */
+               tpAnchorDebug("TPCertInfo potential anchor");
+               mIsRoot = TRS_NamesMatch;
+       }
+       else {
+               mIsRoot = TRS_NotRoot;
+       }
+}
+       
+/* frees mSubjectName, mIssuerName, mCacheHand via mClHand */
+TPCertInfo::~TPCertInfo()
+{
+       tpCertInfoDbg("TPCertInfo destruct this %p", this);
+       releaseResources();
+}
+
+void TPCertInfo::releaseResources()
+{
+       if(mSubjectName) {
+               freeField(&CSSMOID_X509V1SubjectName, mSubjectName);
+               mSubjectName = NULL;
+       }
+       if(mPublicKeyData) {
+               freeField(&CSSMOID_CSSMKeyStruct, mPublicKeyData);
+               mPublicKey = NULL;
+               mPublicKeyData = NULL;
+       }
+       if(mStatusCodes) {
+               free(mStatusCodes);
+               mStatusCodes = NULL;
+       }
+       if(mAllowedErrs) {
+               free(mAllowedErrs);
+       }
+       if(mCertHashStr) {
+               CFRelease(mCertHashStr);
+       }
+       TPClItemInfo::releaseResources();
+}
+
+const CSSM_DATA *TPCertInfo::subjectName()
+{
+       assert(mSubjectName != NULL);
+       return mSubjectName;
+}
+
+/* 
+ * Perform semi-lazy evaluation of "rootness". Subject and issuer names
+ * compared at constructor.
+ * If avoidVerify is true, we won't do the signature verify: caller 
+ * just wants to know if the subject and issuer names match.
+ */
+bool TPCertInfo::isSelfSigned(bool avoidVerify)
+{
+       switch(mIsRoot) {
+               case TRS_NotRoot:                       // known not to be root
+                       return false;
+               case TRS_IsRoot:
+                       return true;
+               case TRS_NamesMatch:
+                       if(avoidVerify) {
+                               return true;
+                       }
+                       /* else drop through and verify */
+               case TRS_Unknown:                       // actually shouldn't happen, but to be safe...
+               default:
+                       /* do the signature verify */
+                       if(verifyWithIssuer(this) == CSSM_OK) {
+                               tpAnchorDebug("isSelfSigned anchor verified");
+                               mIsRoot = TRS_IsRoot;
+                               return true;
+                       }
+                       else {
+                               tpAnchorDebug("isSelfSigned anchor vfy FAIL");
+                               mIsRoot = TRS_NotRoot;
+                               return false;
+                       }
+       }
+}
+
+/*
+ * Am I the issuer of the specified subject item? Returns true if so.
+ * Works for subject certs as well as CRLs. 
+ */
+bool TPCertInfo::isIssuerOf(
+       const TPClItemInfo      &subject)
+{
+       assert(mSubjectName != NULL);
+       assert(subject.issuerName() != NULL);
+       if(tpCompareCssmData(mSubjectName, subject.issuerName())) {
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+bool TPCertInfo::addStatusCode(CSSM_RETURN code)
+{
+       mNumStatusCodes++;
+       mStatusCodes = (CSSM_RETURN *)realloc(mStatusCodes, 
+               mNumStatusCodes * sizeof(CSSM_RETURN));
+       mStatusCodes[mNumStatusCodes - 1] = code;
+       return isStatusFatal(code);
+}
+
+bool TPCertInfo::hasStatusCode(CSSM_RETURN code)
+{
+       for(unsigned dex=0; dex<mNumStatusCodes; dex++) {
+               if(mStatusCodes[dex] == code) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+bool TPCertInfo::isStatusFatal(CSSM_RETURN code)
+{
+       for(unsigned dex=0; dex<mNumAllowedErrs; dex++) {
+               if(mAllowedErrs[dex] == code) {
+                       tpTrustSettingsDbg("isStatusFatal(%ld): ALLOWED", (unsigned long)code);
+                       mIgnoredError = true;
+                       return false;
+               }
+       }
+       return true;
+}
+
+/* 
+ * Indicate whether this cert's public key is a CSSM_KEYATTR_PARTIAL
+ * key.
+ */
+bool TPCertInfo::hasPartialKey()
+{
+       if(mPublicKey->KeyHeader.KeyAttr & CSSM_KEYATTR_PARTIAL) {
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/*
+ * <rdar://9145531>
+ */
+bool TPCertInfo::shouldReject()
+{
+       static unsigned char _UTN_UF_H_ISSUER_BYTES[154] = {
+         0x30, 0x81, 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+         0x13, 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+         0x08, 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55,
+         0x04, 0x07, 0x13, 0x0e, 0x53, 0x41, 0x4c, 0x54, 0x20, 0x4c, 0x41, 0x4b,
+         0x45, 0x20, 0x43, 0x49, 0x54, 0x59, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03,
+         0x55, 0x04, 0x0a, 0x13, 0x15, 0x54, 0x48, 0x45, 0x20, 0x55, 0x53, 0x45,
+         0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f,
+         0x52, 0x4b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+         0x18, 0x48, 0x54, 0x54, 0x50, 0x3a, 0x2f, 0x2f, 0x57, 0x57, 0x57, 0x2e,
+         0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x2e, 0x43, 0x4f,
+         0x4d, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16,
+         0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x49, 0x52, 0x53,
+         0x54, 0x2d, 0x48, 0x41, 0x52, 0x44, 0x57, 0x41, 0x52, 0x45
+       };
+       CSSM_DATA _UTN_UF_H_ISSUER = { sizeof(_UTN_UF_H_ISSUER_BYTES), _UTN_UF_H_ISSUER_BYTES };
+
+       static CSSM_DATA _UTN_UF_H_SERIALS[] = {
+               { 17, (uint8*)"\x00\x92\x39\xd5\x34\x8f\x40\xd1\x69\x5a\x74\x54\x70\xe1\xf2\x3f\x43" }, // amo
+               { 17, (uint8*)"\x00\xd8\xf3\x5f\x4e\xb7\x87\x2b\x2d\xab\x06\x92\xe3\x15\x38\x2f\xb0" }, // gt
+               { 17, (uint8*)"\x00\xb0\xb7\x13\x3e\xd0\x96\xf9\xb5\x6f\xae\x91\xc8\x74\xbd\x3a\xc0" }, // llc
+               { 17, (uint8*)"\x00\xe9\x02\x8b\x95\x78\xe4\x15\xdc\x1a\x71\x0a\x2b\x88\x15\x44\x47" }, // lsc
+               { 17, (uint8*)"\x00\xd7\x55\x8f\xda\xf5\xf1\x10\x5b\xb2\x13\x28\x2b\x70\x77\x29\xa3" }, // lyc
+               { 16, (uint8*)"\x39\x2a\x43\x4f\x0e\x07\xdf\x1f\x8a\xa3\x05\xde\x34\xe0\xc2\x29" },     // lyc1
+               { 16, (uint8*)"\x3e\x75\xce\xd4\x6b\x69\x30\x21\x21\x88\x30\xae\x86\xa8\x2a\x71" },     // lyc2
+               { 16, (uint8*)"\x04\x7e\xcb\xe9\xfc\xa5\x5f\x7b\xd0\x9e\xae\x36\xe1\x0c\xae\x1e" },     // mgc
+               { 17, (uint8*)"\x00\xf5\xc8\x6a\xf3\x61\x62\xf1\x3a\x64\xf5\x4f\x6d\xc9\x58\x7c\x06" }, // wgc
+               { 0, NULL }
+       };
+
+       const CSSM_DATA *issuer=issuerName();
+       if(!issuer || !(tpCompareCssmData(issuer, &_UTN_UF_H_ISSUER)))
+               return false;
+
+       CSSM_DATA *serialNumber=NULL;
+       CSSM_RETURN crtn = fetchField(&CSSMOID_X509V1SerialNumber, &serialNumber);
+       if(crtn || !serialNumber)
+               return false;
+
+       CSSM_DATA *p=_UTN_UF_H_SERIALS;
+       bool matched=false;
+       while(p->Length) {
+               if(tpCompareCssmData(serialNumber, p)) {
+                       matched=true;
+                       addStatusCode(CSSMERR_TP_CERT_REVOKED);
+                       break;
+               }
+               ++p;
+       }
+       freeField(&CSSMOID_X509V1SerialNumber, serialNumber);
+       return matched;
+}
+
+/*
+ * Evaluate trust settings; returns true in *foundMatchingEntry if positive 
+ * match found - i.e., cert chain construction is done. 
+ */
+OSStatus TPCertInfo::evaluateTrustSettings(
+       const CSSM_OID &policyOid,
+       const char *policyString,                       // optional
+       uint32 policyStringLen,
+       SecTrustSettingsKeyUsage keyUse,        // required
+       bool *foundMatchingEntry,                       // RETURNED
+       bool *foundAnyEntry)                            // RETURNED
+{
+       /* 
+        * We might have to force a re-evaluation if the requested key usage
+        * is not a subset of what we already checked for (and cached). 
+        */
+       if(mTrustSettingsEvaluated) {
+               bool doFlush = false;
+               if(mTrustSettingsKeyUsage != kSecTrustSettingsKeyUseAny) {
+                       if(keyUse == kSecTrustSettingsKeyUseAny) {
+                               /* now want "any", checked something else before */
+                               doFlush = true;
+                       }
+                       else if((keyUse & mTrustSettingsKeyUsage) != keyUse) {
+                               /* want bits that we didn't ask for before */
+                               doFlush = true;
+                       }
+               }
+               if(doFlush) {
+                       tpTrustSettingsDbg("evaluateTrustSettings: flushing cached trust for "
+                               "%p due to keyUse 0x%x", this, (int)keyUse);
+                       mTrustSettingsEvaluated = false;
+                       mTrustSettingsFoundAnyEntry = false;
+                       mTrustSettingsResult = kSecTrustSettingsResultInvalid;
+                       mTrustSettingsFoundMatchingEntry = false;
+                       if(mAllowedErrs != NULL) {
+                               free(mAllowedErrs);
+                       }
+                       mNumAllowedErrs = 0;
+               }
+               /* else we can safely use the cached values */
+       }
+       if(!mTrustSettingsEvaluated) {
+       
+               if(mCertHashStr == NULL) {
+                       const CSSM_DATA *certData = itemData();
+                       mCertHashStr = SecTrustSettingsCertHashStrFromData(certData->Data, 
+                               certData->Length);
+               }
+
+               OSStatus ortn = SecTrustSettingsEvaluateCert(mCertHashStr,
+                       &policyOid,
+                       policyString,
+                       policyStringLen,
+                       keyUse,
+                       /* 
+                        * This is the purpose of the avoidVerify option, right here.
+                        * If this is a root cert and it has trust settings, we avoid
+                        * the signature verify. If it turns out there are no trust
+                        * settings and this is a root, we'll verify the signature
+                        * elsewhere (e.g. post_trust_setting: in buildCertGroup()).
+                        */
+                       isSelfSigned(true),
+                       &mTrustSettingsDomain,
+                       &mAllowedErrs,
+                       &mNumAllowedErrs,
+                       &mTrustSettingsResult,
+                       &mTrustSettingsFoundMatchingEntry,
+                       &mTrustSettingsFoundAnyEntry);
+               if(ortn) {
+                       tpTrustSettingsDbg("evaluateTrustSettings: SecTrustSettingsEvaluateCert error!");
+                       return ortn;
+               }
+               mTrustSettingsEvaluated = true;
+               mTrustSettingsKeyUsage = keyUse;
+               #ifndef NDEBUG
+               if(mTrustSettingsFoundMatchingEntry) {
+                       tpTrustSettingsDbg("evaluateTrustSettings: found for %p result %d", 
+                               this, (int)mTrustSettingsResult);
+               }
+               #endif
+               /* one more thing... */
+               if(shouldReject()) {
+                       return CSSMERR_TP_INVALID_CERTIFICATE;
+               }
+       }
+       *foundMatchingEntry = mTrustSettingsFoundMatchingEntry;
+       *foundAnyEntry = mTrustSettingsFoundAnyEntry;
+
+       return noErr;
+}
+
+/* true means "verification terminated due to user trust setting" */
+bool TPCertInfo::trustSettingsFound()
+{
+       switch(mTrustSettingsResult) {
+               case kSecTrustSettingsResultUnspecified:        /* entry but not definitive */
+               case kSecTrustSettingsResultInvalid:            /* no entry */
+                       return false;
+               default:
+                       return true;
+       }
+}
+
+/* 
+ * Determine if this has an empty SubjectName field. Returns true if so.
+ */
+bool TPCertInfo::hasEmptySubjectName()
+{
+       /*
+        * A "pure" empty subject is two bytes (0x30 00) - constructed sequence,
+        * short form length, length 0. We'll be robust and tolerate a missing
+        * field, as well as a possible BER-encoded subject with some extra cruft.
+        */
+       if((mSubjectName == NULL) || (mSubjectName->Length <= 4)) {
+               return true;    
+       }
+       else {
+               return false;   
+       }
+}
+
+/* 
+ * Free mUniqueRecord if it exists.
+ * This is *not* done in our destructor because this record sometimes 
+ * has to persist in the form of a CSSM evidence chain.
+ */
+void TPCertInfo::freeUniqueRecord()
+{
+       if(mUniqueRecord == NULL) {
+               return;
+       }
+       tpDbDebug("freeUniqueRecord: freeing cert record %p", mUniqueRecord);
+       CSSM_DL_FreeUniqueRecord(mDlDbHandle, mUniqueRecord);
+}
+
+/***
+ *** TPCertGroup class
+ ***/
+/* build empty group */
+TPCertGroup::TPCertGroup(
+       Allocator                       &alloc,
+       TPGroupOwner            whoOwns) :
+               mAlloc(alloc),
+               mCertInfo(NULL),
+               mNumCerts(0),
+               mSizeofCertInfo(0),
+               mWhoOwns(whoOwns)
+{
+       tpCertInfoDbg("TPCertGroup simple construct this %p", this);
+       /* nothing for now */
+}
+       
+/*
+ * Construct from unordered, untrusted CSSM_CERTGROUP. Resulting
+ * TPCertInfos are more or less in the same order as the incoming
+ * certs, though incoming certs are discarded if they don't parse.
+ * No verification of any sort is performed. 
+ */
+TPCertGroup::TPCertGroup(
+       const CSSM_CERTGROUP    &CertGroupFrag,
+       CSSM_CL_HANDLE                  clHand,
+       CSSM_CSP_HANDLE                 cspHand,
+       Allocator                               &alloc,
+       const char                              *verifyTime,                    // may be NULL
+       bool                                    firstCertMustBeValid,
+       TPGroupOwner                    whoOwns) :
+               mAlloc(alloc),
+               mCertInfo(NULL),
+               mNumCerts(0),
+               mSizeofCertInfo(0),
+               mWhoOwns(whoOwns)
+{
+       tpCertInfoDbg("TPCertGroup hard construct this %p", this);
+
+       /* 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(firstCertMustBeValid) {
+               if( (CertGroupFrag.NumCerts == 0) ||                    
+               (CertGroupFrag.GroupList.CertList[0].Data == NULL) ||
+               (CertGroupFrag.GroupList.CertList[0].Length == 0)) {
+                               CssmError::throwMe(CSSMERR_TP_INVALID_CERTIFICATE);
+               }
+       }
+       if(CertGroupFrag.CertGroupType != CSSM_CERTGROUP_DATA) {
+               CssmError::throwMe(CSSMERR_TP_INVALID_CERTGROUP);
+       }
+       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);
+       }
+       
+       /* 
+        * Add remaining input certs to mCertInfo. 
+        */
+       TPCertInfo *certInfo = NULL;
+       for(unsigned certDex=0; certDex<CertGroupFrag.NumCerts; certDex++) {
+               try {
+                       certInfo = new TPCertInfo(clHand,
+                               cspHand,
+                               &CertGroupFrag.GroupList.CertList[certDex],
+                               TIC_NoCopy,                     // caller owns
+                               verifyTime);
+               }
+               catch (...) {
+                       if((certDex == 0) && firstCertMustBeValid) {
+                               CssmError::throwMe(CSSMERR_TP_INVALID_CERTIFICATE);
+                       }
+                       /* else just ignore this cert */
+                       continue;
+               }
+               certInfo->index(certDex);
+               appendCert(certInfo);
+       }
+}
+
+/*
+ * Deletes contents of mCertInfo[] if appropriate.
+ */
+TPCertGroup::~TPCertGroup()
+{
+       if(mWhoOwns == TGO_Group) {
+               unsigned i;
+               for(i=0; i<mNumCerts; i++) {
+                       delete mCertInfo[i];
+               }
+       }
+       mAlloc.free(mCertInfo);
+}
+
+/* add/remove/access TPTCertInfo's. */
+/*
+ * NOTE: I am aware that most folks would just use an array<> here, but
+ * gdb is so lame that it doesn't even let one examine the contents
+ * of an array<> (or just about anything else in the STL). I prefer
+ * debuggability over saving a few lines of trivial code.
+ */
+void TPCertGroup::appendCert(
+       TPCertInfo                      *certInfo)                      // appends to end of mCertInfo
+{
+       if(mNumCerts == mSizeofCertInfo) {
+               if(mSizeofCertInfo == 0) {
+                       /* appending to empty array */
+                       mSizeofCertInfo = 1;
+               }
+               else {
+                       mSizeofCertInfo *= 2;
+               }
+               mCertInfo = (TPCertInfo **)mAlloc.realloc(mCertInfo, 
+                       mSizeofCertInfo * sizeof(TPCertInfo *));
+       }
+       mCertInfo[mNumCerts++] = certInfo;
+}
+
+TPCertInfo *TPCertGroup::certAtIndex(
+       unsigned                        index)
+{
+       if(index > (mNumCerts - 1)) {
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       return mCertInfo[index];
+}
+
+TPCertInfo *TPCertGroup::removeCertAtIndex(
+       unsigned                        index)                          // doesn't delete the cert, just 
+                                                                                       // removes it from out list
+{
+       if(index > (mNumCerts - 1)) {
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       TPCertInfo *rtn = mCertInfo[index];
+       
+       /* removed requested element and compact remaining array */
+       unsigned i;
+       for(i=index; i<(mNumCerts - 1); i++) {
+               mCertInfo[i] = mCertInfo[i+1];
+       }
+       mNumCerts--;
+       return rtn;
+}
+
+TPCertInfo *TPCertGroup::firstCert()
+{
+       if(mNumCerts == 0) {
+               /* the caller really should not do this... */
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       else {
+               return mCertInfo[0];
+       }
+}
+
+TPCertInfo *TPCertGroup::lastCert()
+{
+       if(mNumCerts == 0) {
+               return NULL;
+       }
+       else {
+               return mCertInfo[mNumCerts - 1];
+       }
+}
+
+/* build a CSSM_CERTGROUP corresponding with our mCertInfo */
+CSSM_CERTGROUP_PTR TPCertGroup::buildCssmCertGroup()
+{
+       CSSM_CERTGROUP_PTR cgrp = 
+               (CSSM_CERTGROUP_PTR)mAlloc.malloc(sizeof(CSSM_CERTGROUP));
+       cgrp->NumCerts = mNumCerts;
+       cgrp->CertGroupType = CSSM_CERTGROUP_DATA;
+       cgrp->CertType = CSSM_CERT_X_509v3;
+       cgrp->CertEncoding = CSSM_CERT_ENCODING_DER; 
+       if(mNumCerts == 0) {
+               /* legal */
+               cgrp->GroupList.CertList = NULL;
+               return cgrp;
+       }
+       cgrp->GroupList.CertList = (CSSM_DATA_PTR)mAlloc.calloc(mNumCerts, 
+               sizeof(CSSM_DATA));
+       for(unsigned i=0; i<mNumCerts; i++) {
+               tpCopyCssmData(mAlloc, mCertInfo[i]->itemData(), 
+                       &cgrp->GroupList.CertList[i]);
+       }
+       return cgrp;
+}
+
+/* build a CSSM_TP_APPLE_EVIDENCE_INFO array */
+CSSM_TP_APPLE_EVIDENCE_INFO *TPCertGroup::buildCssmEvidenceInfo()
+{
+       CSSM_TP_APPLE_EVIDENCE_INFO *infoArray;
+       
+       infoArray = (CSSM_TP_APPLE_EVIDENCE_INFO *)mAlloc.calloc(mNumCerts,
+               sizeof(CSSM_TP_APPLE_EVIDENCE_INFO));
+       for(unsigned i=0; i<mNumCerts; i++) {
+               TPCertInfo *certInfo = mCertInfo[i];
+               CSSM_TP_APPLE_EVIDENCE_INFO *evInfo = &infoArray[i];
+               
+               /* first the booleans */
+               if(certInfo->isExpired()) {
+                       evInfo->StatusBits |= CSSM_CERT_STATUS_EXPIRED;
+               }
+               if(certInfo->isNotValidYet()) {
+                       evInfo->StatusBits |= CSSM_CERT_STATUS_NOT_VALID_YET;
+               }
+               if(certInfo->isAnchor()) {
+                       tpAnchorDebug("buildCssmEvidenceInfo: flagging IS_IN_ANCHORS");
+                       evInfo->StatusBits |= CSSM_CERT_STATUS_IS_IN_ANCHORS;
+               }
+               if(certInfo->dlDbHandle().DLHandle == 0) {
+                       if(certInfo->isFromNet()) {
+                               evInfo->StatusBits |= CSSM_CERT_STATUS_IS_FROM_NET;
+                       }
+                       else if(certInfo->isFromInputCerts()) {
+                               evInfo->StatusBits |= CSSM_CERT_STATUS_IS_IN_INPUT_CERTS;
+                       }
+               }
+               /* If trust settings apply to a root, skip verifying the signature */
+               bool avoidVerify = false;
+               switch(certInfo->trustSettingsResult()) {
+                       case kSecTrustSettingsResultTrustRoot:
+                       case kSecTrustSettingsResultTrustAsRoot:
+                               /* these two can be disambiguated by IS_ROOT */
+                               evInfo->StatusBits |= CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST;
+                               avoidVerify = true;
+                               break;
+                       case kSecTrustSettingsResultDeny:
+                               evInfo->StatusBits |= CSSM_CERT_STATUS_TRUST_SETTINGS_DENY;
+                               avoidVerify = true;
+                               break;
+                       case kSecTrustSettingsResultUnspecified:
+                       case kSecTrustSettingsResultInvalid:
+                       default:
+                               break;
+               }
+               if(certInfo->isSelfSigned(avoidVerify)) {
+                       evInfo->StatusBits |= CSSM_CERT_STATUS_IS_ROOT;
+               }
+               if(certInfo->ignoredError()) {
+                       evInfo->StatusBits |= CSSM_CERT_STATUS_TRUST_SETTINGS_IGNORED_ERROR;
+               }
+               unsigned numCodes = certInfo->numStatusCodes();
+               if(numCodes) {
+                       evInfo->NumStatusCodes = numCodes;
+                       evInfo->StatusCodes = (CSSM_RETURN *)mAlloc.calloc(numCodes,
+                               sizeof(CSSM_RETURN));
+                       for(unsigned j=0; j<numCodes; j++) {
+                               evInfo->StatusCodes[j] = (certInfo->statusCodes())[j];
+                       }
+               }
+               if(evInfo->StatusBits & (CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST |
+                                                                CSSM_CERT_STATUS_TRUST_SETTINGS_DENY |
+                                                                CSSM_CERT_STATUS_TRUST_SETTINGS_IGNORED_ERROR)) {
+                       /* Something noteworthy happened involving TrustSettings */
+                       uint32 whichDomain = 0;
+                       switch(certInfo->trustSettingsDomain()) {
+                               case kSecTrustSettingsDomainUser:
+                                       whichDomain = CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_USER;
+                                       break;
+                               case kSecTrustSettingsDomainAdmin:
+                                       whichDomain = CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_ADMIN;
+                                       break;
+                               case kSecTrustSettingsDomainSystem:
+                                       whichDomain = CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_SYSTEM;
+                                       break;
+                       }
+                       evInfo->StatusBits |= whichDomain;
+               }
+               evInfo->Index = certInfo->index();
+               evInfo->DlDbHandle = certInfo->dlDbHandle();
+               evInfo->UniqueRecord = certInfo->uniqueRecord();
+       }
+       return infoArray;
+}
+               
+/* Given a status for basic construction of a cert group and a status
+ * of (optional) policy verification, plus the implicit notBefore/notAfter
+ * status in the certs, calculate a global return code. This just 
+ * encapsulates a policy for CertGroupConstruct and CertGroupVerify.
+ */
+CSSM_RETURN TPCertGroup::getReturnCode(
+       CSSM_RETURN                                     constructStatus,
+       CSSM_RETURN                                     policyStatus,
+       CSSM_APPLE_TP_ACTION_FLAGS      actionFlags)
+{
+       if(constructStatus) {
+               /* CSSMERR_TP_NOT_TRUSTED, CSSMERR_TP_INVALID_ANCHOR_CERT, gross errors */
+               return constructStatus;
+       }
+
+       bool expired = false;
+       bool postdated = false;
+       bool allowExpiredRoot = (actionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED_ROOT) ?
+               true : false;
+       bool allowExpired = (actionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED) ? true : false;
+       bool allowPostdated = allowExpired; // flag overrides any temporal invalidity
+       bool requireRevPerCert = (actionFlags & CSSM_TP_ACTION_REQUIRE_REV_PER_CERT) ?
+               true : false;
+               
+       /* check for expired, not valid yet */
+       for(unsigned i=0; i<mNumCerts; i++) {
+               TPCertInfo *ci = mCertInfo[i];
+               /* 
+                * Note avoidVerify = true for isSelfSigned(); if it were appropriate to 
+                * verify the signature, that would have happened in 
+                * buildCssmEvidenceInfo() at the latest.
+                */
+               if(ci->isExpired() &&
+                  !(allowExpiredRoot && ci->isSelfSigned(true)) &&             // allowed globally
+                   ci->isStatusFatal(CSSMERR_TP_CERT_EXPIRED)) {       // allowed for this cert
+                       expired = true;
+               }
+               if(ci->isNotValidYet() && 
+                  ci->isStatusFatal(CSSMERR_TP_CERT_NOT_VALID_YET)) {
+                       postdated = true;
+               }
+       }
+       if(expired && !allowExpired) {
+               return CSSMERR_TP_CERT_EXPIRED;
+       }
+       if(postdated && !allowPostdated) {
+               return CSSMERR_TP_CERT_NOT_VALID_YET;
+       }
+       
+       /* Check for missing revocation check */
+       if(requireRevPerCert) {
+               for(unsigned i=0; i<mNumCerts; i++) {
+                       TPCertInfo *ci = mCertInfo[i];
+                       if(ci->isSelfSigned(true)) {
+                               /* revocation check meaningless for a root cert */
+                               tpDebug("getReturnCode: ignoring revocation for self-signed cert %d", i);
+                               continue;
+                       }
+                       if(!ci->revokeCheckGood() &&
+                          ci->isStatusFatal(CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK)) {
+                               tpDebug("getReturnCode: FATAL: revocation check incomplete for cert %d", i);
+                               return CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK;
+                       }
+                       #ifndef NDEBUG
+                       else {
+                               tpDebug("getReturnCode: revocation check %s for cert %d",
+                                       (ci->revokeCheckGood()) ? "GOOD" : "OK", i);
+                       }
+                       #endif
+               }
+       }
+       return policyStatus;
+}
+
+/* set all TPCertInfo.mUsed flags false */
+void TPCertGroup::setAllUnused()
+{
+       for(unsigned dex=0; dex<mNumCerts; dex++) {
+               mCertInfo[dex]->used(false);
+       }
+}
+
+/*
+ * See if the specified error status is allowed (return true) or
+ * fatal (return false) per each cert's mAllowedErrs[]. Returns
+ * true if any cert returns false for its isStatusFatal() call. 
+ * The list of errors which can apply to cert-chain-wide allowedErrors
+ * is right here; if the incoming error is not in that list, we
+ * return false. If the incoming error code is CSSM_OK we return
+ * true as a convenience for our callers. 
+ */
+bool TPCertGroup::isAllowedError(
+       CSSM_RETURN     code)
+{
+       switch(code) {
+               case CSSM_OK:
+                       return true;
+               case CSSMERR_TP_NOT_TRUSTED:
+               case CSSMERR_TP_INVALID_ANCHOR_CERT:
+               case CSSMERR_TP_VERIFY_ACTION_FAILED:
+               case CSSMERR_TP_INVALID_CERT_AUTHORITY:
+               case CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH:
+               case CSSMERR_APPLETP_RS_BAD_CERT_CHAIN_LENGTH:
+                       /* continue processing these candidates */
+                       break;
+               default:
+                       /* not a candidate for cert-chain-wide allowedErrors */
+                       return false; 
+       }
+
+       for(unsigned dex=0; dex<mNumCerts; dex++) {
+               if(!mCertInfo[dex]->isStatusFatal(code)) {
+                       tpTrustSettingsDbg("TPCertGroup::isAllowedError: allowing for cert %u",
+                               dex);
+                       return true;
+               }
+       }
+
+       /* every cert thought this was fatal; it is. */
+       return false;
+}
+
+/*
+ * Determine if we already have the specified cert in this group.
+ */
+bool TPCertGroup::isInGroup(TPCertInfo &certInfo)
+{
+       for(unsigned dex=0; dex<mNumCerts; dex++) {
+               if(tpCompareCssmData(certInfo.itemData(), mCertInfo[dex]->itemData())) {
+                       return true;    
+               }
+       }
+       return false;
+}
+
+/* 
+ * Encode issuing certs in this group as a PEM-encoded data blob.
+ * Caller must free.
+ */
+void TPCertGroup::encodeIssuers(CSSM_DATA &issuers)
+{
+       /* FIXME: probably want to rewrite this using pemEncode() from libsecurity_cdsa_utils,
+        * since use of Sec* APIs from this layer violates the API reentrancy contract.
+        */
+       issuers.Data = NULL;
+       issuers.Length = 0;
+       CFMutableArrayRef certArray = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       if(!certArray) {
+               return;
+       }
+       for(unsigned certDex=0; certDex<mNumCerts; certDex++) {
+               TPCertInfo *certInfo = certAtIndex(certDex);
+               if(!certDex && mNumCerts > 1) {
+                       continue; /* don't need the leaf */
+               }
+               CSSM_DATA *cssmData = (CSSM_DATA*)((certInfo) ? certInfo->itemData() : NULL);
+               if(!cssmData || !cssmData->Data || !cssmData->Length) {
+                       continue;
+               }
+               CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
+                       (const UInt8 *)cssmData->Data, cssmData->Length,
+                       kCFAllocatorNull);
+               if(!dataRef) {
+                       continue;
+               }
+               SecCertificateRef certRef = SecCertificateCreateWithData(kCFAllocatorDefault,
+                       dataRef);
+               if(!certRef) {
+                       CFRelease(dataRef);
+                       continue;
+               }
+               CFArrayAppendValue(certArray, certRef);
+               CFRelease(certRef);
+               CFRelease(dataRef);
+       }
+       CFDataRef exportedPEMData = NULL;
+       OSStatus status = SecItemExport(certArray,
+               kSecFormatPEMSequence,
+               kSecItemPemArmour,
+               NULL,
+               &exportedPEMData);
+       CFRelease(certArray);
+
+       if(!status)     {
+               uint8 *dataPtr = (uint8*)CFDataGetBytePtr(exportedPEMData);
+               size_t dataLen = CFDataGetLength(exportedPEMData);
+               issuers.Data = (uint8*)malloc(dataLen);
+               memmove(issuers.Data, dataPtr, dataLen);
+               issuers.Length = dataLen;
+               CFRelease(exportedPEMData);
+       }
+}
+
+/*
+ * Search unused incoming certs to find an issuer of specified cert or CRL.
+ * WARNING this assumes a valid "used" state for all certs in this group.
+ * If partialIssuerKey is true on return, caller must re-verify signature
+ * of subject later when sufficient info is available. 
+ */
+TPCertInfo *TPCertGroup::findIssuerForCertOrCrl(
+       const TPClItemInfo &subject,
+       bool &partialIssuerKey)
+{
+       partialIssuerKey = false;
+       TPCertInfo *expiredIssuer = NULL;
+       
+       for(unsigned certDex=0; certDex<mNumCerts; certDex++) {
+               TPCertInfo *certInfo = certAtIndex(certDex);
+               
+               /* has this one already been used in this search? */
+               if(certInfo->used()) {
+                       continue;
+               }
+               
+               /* subject/issuer names match? */
+               if(certInfo->isIssuerOf(subject)) {
+                       /* yep, do a sig verify */
+                       tpVfyDebug("findIssuerForCertOrCrl issuer/subj match checking sig");
+                       CSSM_RETURN crtn = subject.verifyWithIssuer(certInfo);
+                       switch(crtn) {
+                               case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
+                                       /* issuer OK, check sig later */
+                                       partialIssuerKey = true;
+                                       /* and fall thru */
+                               case CSSM_OK:
+                                       /* 
+                                        * Temporal validity check: if we're not already holding an expired
+                                        * issuer, and this one's invalid, hold it and keep going. 
+                                        */
+                                       if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
+                                               if(certInfo->isExpired() || certInfo->isNotValidYet()) {
+                                                       tpDebug("findIssuerForCertOrCrl: holding expired cert %p",
+                                                               certInfo);
+                                                       expiredIssuer = certInfo;
+                                                       break;
+                                               }
+                                       }
+                                       /* YES */
+                                       certInfo->used(true);
+                                       return certInfo;
+                               default:
+                                       /* just skip this one and keep looking */
+                                       tpVfyDebug("findIssuerForCertOrCrl issuer/subj match BAD SIG");
+                                       break;
+                       }
+               }       /* names match */
+       }
+       if(expiredIssuer != NULL) {
+               /* OK, we'll use this one */
+               tpDbDebug("findIssuerForCertOrCrl: using expired cert %p", expiredIssuer);
+               expiredIssuer->used(true);
+               return expiredIssuer;
+       }
+       
+       /* not found */
+       return NULL;
+}      
+
+/*
+ * Construct ordered, verified cert chain from a variety of inputs. 
+ * Time validity does not affect the function return or any status, 
+ * we always try to find a valid cert to replace an expired or 
+ * not-yet-valid cert if we can. Final temporal validity of each 
+ * cert must be checked by caller (it's stored in each TPCertInfo 
+ * we add to ourself during construction). 
+ * 
+ * Only possible error returns are:
+ *      CSSMERR_TP_CERTIFICATE_CANT_OPERATE : issuer cert was found with a partial
+ *                     public key, rendering full verification impossible. 
+ *   CSSMERR_TP_INVALID_CERT_AUTHORITY : issuer cert was found with a partial 
+ *                     public key and which failed to perform subsequent signature
+ *                     verification.
+ *
+ * Other interesting status is returned via the verifiedToRoot and 
+ * verifiedToAnchor flags. 
+ *
+ * NOTE: is it the caller's responsibility to call setAllUnused() for both 
+ * incoming cert groups (inCertGroup and gatheredCerts). We don't do that
+ * here because we may call ourself recursively. 
+ */
+CSSM_RETURN TPCertGroup::buildCertGroup(
+       const TPClItemInfo              &subjectItem,   // Cert or CRL
+       TPCertGroup                             *inCertGroup,   // optional
+       const CSSM_DL_DB_LIST   *dbList,                // optional
+       CSSM_CL_HANDLE                  clHand,
+       CSSM_CSP_HANDLE                 cspHand,
+       const char                              *verifyTime,    // optional, for establishing
+                                                                                       //   validity of new TPCertInfos
+       /* trusted anchors, optional */
+       /* FIXME - maybe this should be a TPCertGroup */
+       uint32                                  numAnchorCerts,
+       const CSSM_DATA                 *anchorCerts,
+       
+       /* 
+        * Certs to be freed by caller (i.e., TPCertInfo which we allocate
+        * as a result of using a cert from anchorCerts or dbList) are added
+        * to this group.
+        */
+       TPCertGroup                             &certsToBeFreed,
+       
+       /*
+        * Other certificates gathered during the course of this operation,
+        * currently consisting of certs fetched from DBs and from the net.
+        * This is not used when called by AppleTPSession::CertGroupConstructPriv;
+        * it's an optimization for the case when we're building a cert group
+        * for TPCrlInfo::verifyWithContext - we avoid re-fetching certs from
+        * the net which are needed to verify both the subject cert and a CRL.
+        * We don't modify this TPCertGroup, we only use certs from it. 
+        */
+       TPCertGroup                             *gatheredCerts,
+       
+       /*
+        * Indicates that subjectItem is a cert in this cert group.
+        * If true, that cert will be tested for "root-ness", including 
+        *   -- subject/issuer compare
+        *   -- signature self-verify
+        *   -- anchor compare
+        */
+       CSSM_BOOL                               subjectIsInGroup,
+       
+       /* 
+        * CSSM_TP_ACTION_FETCH_CERT_FROM_NET,
+        * CSSM_TP_ACTION_TRUST_SETTING,
+        * CSSM_TP_ACTION_IMPLICIT_ANCHORS are interesting 
+        */
+       CSSM_APPLE_TP_ACTION_FLAGS      actionFlags,
+       
+       /* CSSM_TP_ACTION_TRUST_SETTING parameters */
+       const CSSM_OID                  *policyOid,
+       const char                              *policyStr,
+       uint32                                  policyStrLen,
+       SecTrustSettingsKeyUsage leafKeyUse,                            // usage of *first* cert in chain
+       
+       /* returned */
+       CSSM_BOOL                               &verifiedToRoot,                        // end of chain self-verifies
+       CSSM_BOOL                               &verifiedToAnchor,                      // end of chain in anchors
+       CSSM_BOOL                               &verifiedViaTrustSettings)      // chain ends per User Trust setting
+{
+       const TPClItemInfo *thisSubject = &subjectItem;
+       CSSM_RETURN crtn = CSSM_OK;
+       TPCertInfo *issuerCert = NULL;
+       unsigned certDex;
+       TPCertInfo *anchorInfo = NULL;
+       bool foundPartialIssuer = false;
+       bool attemptNetworkFetch = false;
+       CSSM_BOOL firstSubjectIsInGroup = subjectIsInGroup;
+       TPCertInfo *endCert;
+       
+       tpVfyDebug("buildCertGroup top");
+       
+       /* possible expired root which we'll only use if we can't find 
+        * a better one */
+       TPCertInfo *expiredRoot = NULL;
+       
+       /* and the general case of an expired or not yet valid cert */
+       TPCertInfo *expiredIssuer = NULL;
+       
+       verifiedToRoot = CSSM_FALSE;
+       verifiedToAnchor = CSSM_FALSE;
+       verifiedViaTrustSettings = CSSM_FALSE;
+       
+       /*** main loop to seach inCertGroup and dbList ***
+        *
+        * Exit loop on: 
+        *   -- find a root cert in the chain (self-signed)
+        *   -- find a non-root cert which is also in the anchors list
+        *   -- find a cert which is trusted per Trust Settings (if enabled)
+        *   -- memory error
+        *   -- or no more certs to add to chain. 
+        */
+       for(;;) {
+               /* 
+                * Top of loop: thisSubject is the item we're trying to verify. 
+                */
+                
+               /* is thisSubject a root cert or listed in user trust list?  */
+               if(subjectIsInGroup) {
+                       TPCertInfo *subjCert = lastCert();
+                       assert(subjCert != NULL);
+                       
+                       if(actionFlags & CSSM_TP_ACTION_TRUST_SETTINGS) {
+                               assert(policyOid != NULL);
+                               
+                               /* 
+                                * Figure out key usage. If this is a leaf cert, the caller - actually
+                                * the per-policy code - inferred the usage. Else it could be for
+                                * verifying a cert or a CRL. 
+                                *
+                                * We want to avoid multiple calls to the effective portion of 
+                                * evaluateTrustSettings(), but a CA cert could be usable for only 
+                                * signing certs and not CRLs. Thus we're evaluating a CA cert, 
+                                * try to evaluate for signing certs *and* CRLs in case we come 
+                                * this way again later when performing CRL verification. If that 
+                                * fails, then retry with just cert signing.
+                                */
+                               SecTrustSettingsKeyUsage localKeyUse;
+                               bool doRetry = false;
+                               if(subjCert == firstCert()) {
+                                       /* leaf - use caller's spec */
+                                       localKeyUse = leafKeyUse;
+                                       /* FIXME - add in CRL if this is cert checking? */
+                               }
+                               else {
+                                       localKeyUse = kSecTrustSettingsKeyUseSignCert | kSecTrustSettingsKeyUseSignRevocation;
+                                       /* and if necessary */
+                                       doRetry = true; 
+                               }
+                               /* this lets us avoid searching for the same thing twice when there
+                                * is in fact no entry for it */
+                               bool foundEntry = false;
+                               bool trustSettingsFound = false;
+                               OSStatus ortn = subjCert->evaluateTrustSettings(*policyOid,
+                                       policyStr, policyStrLen, localKeyUse, &trustSettingsFound, &foundEntry);
+                               if(ortn) {
+                                       /* this is only a dire error */
+                                       crtn = ortn;
+                                       goto final_out;
+                               }
+                               if(!trustSettingsFound && foundEntry && doRetry) {
+                                       tpTrustSettingsDbg("buildCertGroup: retrying evaluateTrustSettings with Cert only");
+                                       ortn = subjCert->evaluateTrustSettings(*policyOid,
+                                               policyStr, policyStrLen, kSecTrustSettingsKeyUseSignCert, 
+                                               &trustSettingsFound, &foundEntry);
+                                       if(ortn) {
+                                               crtn = ortn;
+                                               goto final_out;
+                                       }
+                               }
+                               if(trustSettingsFound) {                                        
+                                       switch(subjCert->trustSettingsResult()) {
+                                               case kSecTrustSettingsResultInvalid:
+                                                       /* should not happen... */
+                                                       assert(0);
+                                                       crtn = CSSMERR_TP_INTERNAL_ERROR;
+                                                       break;
+                                               case kSecTrustSettingsResultTrustRoot:
+                                               case kSecTrustSettingsResultTrustAsRoot:
+                                                       tpTrustSettingsDbg("Trust[As]Root found");
+                                                       crtn = CSSM_OK;
+                                                       break;
+                                               case kSecTrustSettingsResultDeny:
+                                                       tpTrustSettingsDbg("TrustResultDeny found");
+                                                       crtn = CSSMERR_APPLETP_TRUST_SETTING_DENY;
+                                                       break;
+                                               case kSecTrustSettingsResultUnspecified:
+                                                       /* special case here: this means "keep going, we don't trust or 
+                                                        * distrust this cert". Typically used to express allowed errors
+                                                        * only. 
+                                                        */
+                                                       tpTrustSettingsDbg("TrustResultUnspecified found");
+                                                       goto post_trust_setting;
+                                               default:
+                                                       tpTrustSettingsDbg("Unknown TrustResult (%d)", 
+                                                               (int)subjCert->trustSettingsResult());
+                                                       crtn = CSSMERR_TP_INTERNAL_ERROR;
+                                                       break;
+                                       }
+                                       /* cleanup partial key processing */
+                                       verifiedViaTrustSettings = CSSM_TRUE;
+                                       goto final_out;
+                               }
+                       }       /* CSSM_TP_ACTION_TRUST_SETTING */
+
+post_trust_setting:
+                       if(subjCert->isSelfSigned()) {
+                               /* We're at the end of the chain. */
+                               verifiedToRoot = CSSM_TRUE;
+                               
+                               /*
+                                * Special case if this root is temporally invalid (and it's not
+                                * the leaf): remove it from the outgoing cert group, save it,
+                                * and proceed, looking another (good) root in anchors. 
+                                * There's no way we'll find another good one in this loop.
+                                */
+                               if((subjCert->isExpired() || subjCert->isNotValidYet()) && 
+                                  (!firstSubjectIsInGroup || (mNumCerts > 1))) {
+                                       tpDebug("buildCertGroup: EXPIRED ROOT %p, looking for good one", subjCert);
+                                       expiredRoot = subjCert;
+                                       if(mNumCerts) {
+                                               /* roll back to previous cert */
+                                               mNumCerts--;
+                                       }
+                                       if(mNumCerts == 0) {
+                                               /* roll back to caller's initial condition */
+                                               thisSubject = &subjectItem;
+                                       }
+                                       else {
+                                               thisSubject = lastCert();
+                                       }
+                               }
+                               break;          /* out of main loop */
+                       }       /* root */
+                       
+                       /*
+                        * If this non-root cert is in the provided anchors list,
+                        * we can stop building the chain at this point.
+                        *
+                        * If this cert is a leaf, the chain ends in an anchor, but if it's
+                        * also temporally invalid, we can't do anything further. However,
+                        * if it's not a leaf, then we need to roll back the chain to a
+                        * point just before this cert, so Case 1 will subsequently find
+                        * the anchor (and handle the anchor correctly if it's expired.)
+                        */
+                       if(numAnchorCerts && anchorCerts) {
+                               bool foundNonRootAnchor = false;
+                               for(certDex=0; certDex<numAnchorCerts; certDex++) {
+                                       if(tp_CompareCerts(subjCert->itemData(), &anchorCerts[certDex])) {
+                                               foundNonRootAnchor = true;
+                                               /* if it's not the leaf, remove it from the outgoing cert group. */
+                                               if(!firstSubjectIsInGroup || (mNumCerts > 1)) {
+                                                       if(mNumCerts) {
+                                                               /* roll back to previous cert */
+                                                               mNumCerts--;
+                                                       }
+                                                       if(mNumCerts == 0) {
+                                                               /* roll back to caller's initial condition */
+                                                               thisSubject = &subjectItem;
+                                                       }
+                                                       else {
+                                                               thisSubject = lastCert();
+                                                       }
+                                                       tpAnchorDebug("buildCertGroup: CA cert in input AND anchors");
+                                               } /* not leaf */
+                                               else {
+                                                       if(subjCert->isExpired() || subjCert->isNotValidYet()) {
+                                                               crtn = CSSM_CERT_STATUS_EXPIRED;
+                                                       } else {
+                                                               crtn = CSSM_OK;
+                                                       }
+                                                       subjCert->isAnchor(true);
+                                                       verifiedToAnchor = CSSM_TRUE;
+                                                       tpAnchorDebug("buildCertGroup: leaf cert in input AND anchors");
+                                               } /* leaf */
+                                               break;  /* out of anchor-checking loop */
+                                       }
+                               }
+                               if(foundNonRootAnchor) {
+                                       break; /* out of main loop */
+                               }
+                       } /* non-root */
+                       
+               }       /* subjectIsInGroup */
+               
+               /* 
+                * Search unused incoming certs to find an issuer.
+                * Both cert groups are optional.
+                * We'll add issuer to outCertGroup below.
+                * If we find  a cert that's expired or not yet valid, we hold on to it
+                * and look for a better one. If we don't find it here we drop back to the 
+                * expired one at the end of the loop. If that expired cert is a root 
+                * cert, we'll use the expiredRoot mechanism (see above) to roll back and 
+                * see if we can find a good root in the incoming anchors. 
+                */
+               if(inCertGroup != NULL) {
+                       bool partial = false;
+                       issuerCert = inCertGroup->findIssuerForCertOrCrl(*thisSubject, 
+                               partial);
+                       if(issuerCert) {
+                               issuerCert->isFromInputCerts(true);
+                               if(partial) {
+                                       /* deal with this later */
+                                       foundPartialIssuer = true;
+                                       tpDebug("buildCertGroup: PARTIAL Cert FOUND in inCertGroup");           
+                               }
+                               else {
+                                       tpDebug("buildCertGroup: Cert FOUND in inCertGroup");           
+                               }
+                       }
+               }
+               if(issuerCert != NULL) {
+                       if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
+                               if(expiredIssuer == NULL) {
+                                       tpDebug("buildCertGroup: saving expired cert %p (1)", issuerCert);              
+                                       expiredIssuer = issuerCert;
+                                       issuerCert = NULL;
+                               }
+                               /* else we already have an expired issuer candidate */
+                       }
+                       else {
+                               /* unconditionally done with possible expiredIssuer */
+                               #ifndef NDEBUG
+                               if(expiredIssuer != NULL) {
+                                       tpDebug("buildCertGroup: DISCARDING expired cert %p (1)", expiredIssuer);
+                               }
+                               #endif
+                               expiredIssuer = NULL;
+                       }
+               }
+               
+               if((issuerCert == NULL) && (gatheredCerts != NULL)) {
+                       bool partial = false;
+                       issuerCert = gatheredCerts->findIssuerForCertOrCrl(*thisSubject,
+                               partial);
+                       if(issuerCert) {
+                               if(partial) {
+                                       /* deal with this later */
+                                       foundPartialIssuer = true;
+                                       tpDebug("buildCertGroup: PARTIAL Cert FOUND in gatheredCerts");         
+                               }
+                               else {
+                                       tpDebug("buildCertGroup: Cert FOUND in gatheredCerts");
+                               }
+                       }
+               }
+               
+               if(issuerCert != NULL) {
+                       if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
+                               if(expiredIssuer == NULL) {
+                                       tpDebug("buildCertGroup: saving expired cert %p (2)", issuerCert);              
+                                       expiredIssuer = issuerCert;
+                                       issuerCert = NULL;
+                               }
+                               /* else we already have an expired issuer candidate */
+                       }
+                       else {
+                               /* unconditionally done with possible expiredIssuer */
+                               #ifndef NDEBUG
+                               if(expiredIssuer != NULL) {
+                                       tpDebug("buildCertGroup: DISCARDING expired cert %p (2)", expiredIssuer);
+                               }
+                               #endif
+                               expiredIssuer = NULL;
+                       }
+               }
+
+               if((issuerCert == NULL) && (dbList != NULL)) {
+                       /* Issuer not in incoming cert group or gathered certs. Search DBList. */
+                       bool partial = false;
+                       try {
+                               issuerCert = tpDbFindIssuerCert(mAlloc,
+                                       clHand,
+                                       cspHand,
+                                       thisSubject,
+                                       dbList,
+                                       verifyTime,
+                                       partial);
+                       }
+                       catch (...) {}
+
+                       if(issuerCert) {
+                               /* unconditionally done with possible expiredIssuer */
+                               #ifndef NDEBUG
+                               if(expiredIssuer != NULL) {
+                                       tpDebug("buildCertGroup: DISCARDING expired cert %p (3)", expiredIssuer);
+                               }
+                               #endif
+                               expiredIssuer = NULL;
+
+                               /*
+                                * Handle Radar 4566041, endless loop of cross-signed certs.
+                                * This can only happen when fetching certs from a DLDB or 
+                                * from the net; we prevent that from happening when the certs
+                                * are in inCertGroup or gatheredCerts by keeping track of those
+                                * certs' mUsed state.
+                                */
+                               if(isInGroup(*issuerCert)) {
+                                       tpDebug("buildCertGroup: Multiple instances of cert");
+                                       delete issuerCert;
+                                       issuerCert = NULL;
+                               }
+                               else {
+                                       /* caller must free */
+                                       certsToBeFreed.appendCert(issuerCert);
+                                       if(partial) {
+                                               /* deal with this later */
+                                               foundPartialIssuer = true;
+                                               tpDebug("buildCertGroup: PARTIAL Cert FOUND in dbList");                
+                                       }
+                                       else {
+                                               tpDebug("buildCertGroup: Cert FOUND in dbList");
+                                       }
+                               }
+                       }
+               }       /*  searching DLDB list */
+               
+               /*
+                * Note: we don't handle an expired cert returned from tpDbFindIssuerCert()
+                * in any special way like we do with findIssuerForCertOrCrl(). 
+                * tpDbFindIssuerCert() does its best to give us a temporally valid cert; if
+                * it returns an expired cert (or, if findIssuerForCertOrCrl() gave us an
+                * expired cert and tpDbFindIssuerCert() could not do any better), that's all 
+                * we have to work with at this point. We'll go back to the top of the loop 
+                * and apply trust settings if enabled; if an expired cert is trusted per 
+                * Trust Settings, we're done. (Note that anchors are fetched from a DLDB 
+                * when Trust Settings are enabled, so even if two roots with the same key 
+                * and subject name are in DLDBs, and one of them is expired, we'll have the 
+                * good one at this time because of tpDbFindIssuerCert()'s ability to find 
+                * the best cert.) 
+                * 
+                * If Trust Settings are not enabled, and we have an expired root at this 
+                * point, the expiredRoot mechanism is used to roll back and search for 
+                * an anchor that verifies the last good cert. 
+                */
+                
+               if((issuerCert == NULL) &&                      /* tpDbFindIssuerCert() hasn't found one and
+                                                                                        * we don't have a good one */
+                  (expiredIssuer != NULL)) {           /* but we have an expired candidate */
+                       /* 
+                        * OK, we'll take the expired issuer. 
+                        * Note we don't have to free expiredIssuer if we found a good one since
+                        * expiredIssuer can only come from inCertGroup or gatheredCerts (not from 
+                        * dbList).
+                        */
+                       tpDebug("buildCertGroup: USING expired cert %p", expiredIssuer);
+                       issuerCert = expiredIssuer;
+                       expiredIssuer = NULL;
+               }
+               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 thisSubject for
+                * the next pass.
+                */
+               appendCert(issuerCert);
+               thisSubject = issuerCert;
+               subjectIsInGroup = CSSM_TRUE;
+               issuerCert = NULL;
+       }       /* main loop */
+       
+       /* 
+        * This can be NULL if we're evaluating a CRL (and we haven't 
+        * gotten very far).
+        */
+       endCert = lastCert();
+
+       /* 
+        * This, on the other hand, is always valid. It could be a CRL.
+        */
+       assert(thisSubject != NULL);
+       
+       if( (actionFlags & CSSM_TP_ACTION_IMPLICIT_ANCHORS) &&
+               ( (endCert && endCert->isSelfSigned()) || expiredRoot) ) {
+               /* 
+                * Caller will be satisfied with this; skip further anchor processing.
+                */
+               tpAnchorDebug("buildCertGroup: found IMPLICIT anchor");
+               goto post_anchor;
+       }
+       if(numAnchorCerts == 0) {
+               /* we're probably done */
+               goto post_anchor;
+       }
+       assert(anchorCerts != NULL);
+       
+       /*** anchor cert handling ***/
+       
+       /* 
+        * Case 1: If thisSubject is not a root cert, try to validate with incoming anchor certs.
+        */
+       expiredIssuer = NULL;
+       if(!(endCert && endCert->isSelfSigned())) {
+               for(certDex=0; certDex<numAnchorCerts; certDex++) {
+                       
+                       try {
+                               anchorInfo = new TPCertInfo(clHand,
+                                       cspHand,
+                                       &anchorCerts[certDex], 
+                                       TIC_NoCopy, 
+                                       verifyTime);
+                       }
+                       catch(...) {
+                               /* bad anchor cert - ignore it */
+                               anchorInfo = NULL;
+                               continue;
+                       }
+                       
+                       /* 
+                        * We must subsequently delete anchorInfo one way or the other.
+                        * If we add it to tpCertGroup, we also add it to certsToBeFreed.
+                        * Otherwise we delete it.
+                        */
+                       if(!anchorInfo->isIssuerOf(*thisSubject)) {
+                               /* not this anchor */
+                               tpAnchorDebug("buildCertGroup anchor not issuer");
+                               delete anchorInfo;
+                               anchorInfo = NULL;
+                               continue;
+                       }
+
+                       crtn = thisSubject->verifyWithIssuer(anchorInfo);
+                       
+                       if(crtn == CSSM_OK) {
+                               if(anchorInfo->isExpired() || anchorInfo->isNotValidYet()) {
+                                       if(expiredIssuer == NULL) {
+                                               /* 
+                                                * Hang on to this one; keep looking for a better one. 
+                                                */
+                                               tpDebug("buildCertGroup: saving expired anchor %p", anchorInfo);                
+                                               expiredIssuer = anchorInfo;
+                                               /* flag this condition for the switch below */
+                                               crtn = CSSM_CERT_STATUS_EXPIRED;
+                                               expiredIssuer->isAnchor(true);
+                                               assert(!anchorInfo->isFromInputCerts());
+                                               expiredIssuer->index(certDex);
+                                               certsToBeFreed.appendCert(expiredIssuer);
+                                       }
+                                       /* else we already have an expired candidate anchor */
+                               }
+                               else {
+                                       /* 
+                                        * Done with possible expiredIssuer. We don't delete it, since we already added 
+                                        * it to certsToBeFreed, above.
+                                        */
+                                       if(expiredIssuer != NULL) {
+                                               tpDebug("buildCertGroup: DISCARDING expired anchor %p", expiredIssuer);
+                                               expiredIssuer = NULL;
+                                       }
+                               }
+                       }
+                       
+                       switch(crtn) {
+                               case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
+                                       /*
+                                        * A bit of a corner case. Found an issuer in AnchorCerts, but
+                                        * we can't do a signature verify since the issuer has a partial
+                                        * public key. Proceed but return 
+                                        * CSSMERR_TP_CERTIFICATE_CANT_OPERATE.
+                                        */
+                                       if(anchorInfo->addStatusCode(CSSMERR_TP_CERTIFICATE_CANT_OPERATE)) {
+                                               foundPartialIssuer = true;
+                                               crtn = CSSMERR_TP_CERTIFICATE_CANT_OPERATE;
+                                       }
+                                       else {
+                                               /* ignore */
+                                               crtn = CSSM_OK;
+                                       }
+                                        /* drop thru */
+                               case CSSM_OK:
+                                       /*  A fully successful return. */
+                                       verifiedToAnchor = CSSM_TRUE;
+                                       if(anchorInfo->isSelfSigned()) {
+                                               verifiedToRoot = CSSM_TRUE;     
+                                       }
+                                       
+                                       /*
+                                        * Add this anchor cert to the output group 
+                                        * and to certsToBeFreed.
+                                        */
+                                       appendCert(anchorInfo);
+                                       anchorInfo->isAnchor(true);
+                                       assert(!anchorInfo->isFromInputCerts());
+                                       anchorInfo->index(certDex);
+                                       certsToBeFreed.appendCert(anchorInfo);
+                                       tpDebug("buildCertGroup: Cert FOUND by signer in AnchorList");  
+                                       tpAnchorDebug("buildCertGroup: Cert FOUND by signer in AnchorList");
+                                       /* one more thing: partial public key processing needed? */
+                                       if(foundPartialIssuer) {
+                                               return verifyWithPartialKeys(subjectItem);
+                                       }
+                                       else {
+                                               return crtn;
+                                       }
+                                       
+                               default:
+                                       /* continue to next anchor */
+                                       if(crtn != CSSM_CERT_STATUS_EXPIRED) {
+                                               /* Expired means we're saving it in expiredIssuer */
+                                               tpVfyDebug("buildCertGroup found issuer in anchor, BAD SIG");
+                                               delete anchorInfo;
+                                       }
+                                       anchorInfo = NULL;
+                                       break;
+                       }
+               }       /* for each anchor */
+       }       /* thisSubject not a root cert */
+       
+       /*
+        * Case 2: Check whether endCert is present in anchor certs.
+        *
+        * Also used to validate an expiredRoot that we pulled off the chain in 
+        * hopes of finding something better (which, if we're here, we haven't done). 
+        *
+        * Note that the main loop above did the actual root self-verify test.
+        */
+       if(endCert || expiredRoot) {
+               
+               TPCertInfo *theRoot;
+               if(expiredRoot) {
+                       /* this is NOT in our outgoing cert group (yet) */
+                       theRoot = expiredRoot;
+               }
+               else {
+                       theRoot = endCert;
+               }
+               /* see if that root cert is identical to one of the anchor certs */
+               for(certDex=0; certDex<numAnchorCerts; certDex++) {
+                       if(tp_CompareCerts(theRoot->itemData(), &anchorCerts[certDex])) {
+                               /* one fully successful return */
+                               tpAnchorDebug("buildCertGroup: end cert in input AND anchors");
+                               verifiedToAnchor = CSSM_TRUE;
+                               theRoot->isAnchor(true);
+                               if(!theRoot->isFromInputCerts()) {
+                                       /* Don't override index into input certs */
+                                       theRoot->index(certDex);
+                               }
+                               if(expiredRoot) {
+                                       /* verified to anchor but caller will see 
+                                        * CSSMERR_TP_CERT_EXPIRED */
+                                       appendCert(expiredRoot);
+                               }
+                               /* one more thing: partial public key processing needed? */
+                               if(foundPartialIssuer) {
+                                       return verifyWithPartialKeys(subjectItem);
+                               }
+                               else {
+                                       return CSSM_OK;
+                               }
+                       }
+               }
+               tpAnchorDebug("buildCertGroup: end cert in input, NOT anchors");
+               
+               if(!expiredRoot && endCert->isSelfSigned()) {
+                       /* verified to a root cert which is not an anchor */
+                       /* Generally maps to CSSMERR_TP_INVALID_ANCHOR_CERT by caller */
+                       /* one more thing: partial public key processing needed? */
+                       if(foundPartialIssuer) {
+                               return verifyWithPartialKeys(subjectItem);
+                       }
+                       else {
+                               return CSSM_OK;
+                       }
+               }
+               /* else try finding a good anchor */
+       }
+
+       /* regardless of anchor search status... */
+       crtn = CSSM_OK;
+       if(!verifiedToAnchor && (expiredIssuer != NULL)) {
+               /* expiredIssuer here is always an anchor */
+               tpDebug("buildCertGroup: accepting expired anchor %p", expiredIssuer);
+               appendCert(expiredIssuer);
+               verifiedToAnchor = CSSM_TRUE;
+               if(expiredIssuer->isSelfSigned()) {
+                       verifiedToRoot = CSSM_TRUE;     
+               }
+               /* no matter what, we don't want this one */
+               expiredRoot = NULL;
+       }
+post_anchor:
+       if(expiredRoot) {
+               /*
+                * One remaining special case: expiredRoot found in input certs, but 
+                * no luck resolving the problem with the anchors. Go ahead and (re-)append
+                * the expired root and return.
+                */
+               tpDebug("buildCertGroup: accepting EXPIRED root");
+               appendCert(expiredRoot);
+               if(foundPartialIssuer) {
+                       return verifyWithPartialKeys(subjectItem);
+               }
+               else {
+                       return CSSM_OK;
+               }
+       }
+
+       /* If we get here, determine if fetching the issuer from the network
+        * should be attempted: <rdar://6113890&7419584&7422356>
+        */
+       attemptNetworkFetch = (actionFlags & CSSM_TP_ACTION_FETCH_CERT_FROM_NET);
+       if( (!dbList || (dbList->NumHandles == 0)) &&
+                (!anchorCerts || (numAnchorCerts == 0)) ) {
+               /* DB list is empty *and* anchors are empty; there is no point in going
+                * out to the network, since we cannot build a chain to a trusted root.
+                * (This can occur when the caller wants to evaluate a single certificate
+                * without trying to build the chain, e.g. to check its key usage.)
+                */
+               attemptNetworkFetch = false;
+       }
+       
+       /* 
+        * If we haven't verified to a root, and net fetch of certs is enabled,
+        * try to get the issuer of the last cert in the chain from the net.
+        * If that succeeds, then call ourself recursively to perform the 
+        * whole search again (including comparing to or verifying against
+        * anchor certs).
+        */
+       if(!verifiedToRoot && !verifiedToAnchor &&
+               (endCert != NULL) && attemptNetworkFetch) {
+               TPCertInfo *issuer = NULL;
+               CSSM_RETURN cr = tpFetchIssuerFromNet(*endCert,
+                       clHand,
+                       cspHand,
+                       verifyTime,
+                       issuer);
+               switch(cr) {
+                       case CSSMERR_TP_CERTGROUP_INCOMPLETE:
+                               /* no issuerAltName, no reason to log this */
+                               break;
+                       default:
+                               /* gross error */
+                               endCert->addStatusCode(cr);
+                               break;
+                       case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
+                               /* use this one but re-verify later */
+                               foundPartialIssuer = true;
+                               /* and drop thru */
+                       case CSSM_OK:
+                               if (!issuer)
+                                       break;
+                               tpDebug("buildCertGroup: Cert FOUND from Net; recursing");
+
+                               if(isInGroup(*issuer)) {
+                                       tpDebug("buildCertGroup: Multiple instances of cert from net");
+                                       delete issuer;
+                                       issuer = NULL;
+                                       crtn = CSSMERR_TP_CERTGROUP_INCOMPLETE;
+                                       break;
+                               }
+                                       
+                               /* add this fetched cert to constructed group */
+                               appendCert(issuer);
+                               issuer->isFromNet(true);
+                               certsToBeFreed.appendCert(issuer);
+                               
+                               /* and go again */
+                               cr = buildCertGroup(*issuer,
+                                       inCertGroup,
+                                       dbList,
+                                       clHand,
+                                       cspHand,
+                                       verifyTime,
+                                       numAnchorCerts,
+                                       anchorCerts,
+                                       certsToBeFreed,
+                                       gatheredCerts,
+                                       CSSM_TRUE,              // subjectIsInGroup     
+                                       actionFlags,
+                                       policyOid,
+                                       policyStr,
+                                       policyStrLen,
+                                       leafKeyUse,             // actually don't care since the leaf will not
+                                                                       // be evaluated
+                                       verifiedToRoot,
+                                       verifiedToAnchor,
+                                       verifiedViaTrustSettings);
+                               if(cr) {
+                                       return cr;
+                               }
+                               
+                               /* one more thing: partial public key processing needed? */
+                               if(foundPartialIssuer) {
+                                       return verifyWithPartialKeys(subjectItem);
+                               }
+                               else {
+                                       return CSSM_OK;
+                               }
+               }
+       }
+final_out:
+       /* regardless of outcome, check for partial keys to log per-cert status */
+       CSSM_RETURN partRtn = CSSM_OK;
+       if(foundPartialIssuer) {
+               partRtn = verifyWithPartialKeys(subjectItem);
+       }
+       if(crtn) {
+               return crtn;
+       }
+       else {
+               return partRtn;
+       }
+}
+
+/* 
+ * Called from buildCertGroup as final processing of a constructed
+ * group when CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE has been
+ * detected. Perform partial public key processing.
+ *
+ * We don't have to verify every element, just the ones whose
+ * issuers have partial public keys.
+ *
+ * Returns:
+ *      CSSMERR_TP_CERTIFICATE_CANT_OPERATE in the case of an issuer cert 
+ *             with a partial public key which can't be completed.
+ *      CSSMERR_TP_INVALID_CERT_AUTHORITY if sig verify failed with 
+ *             a (supposedly) completed partial key
+ */
+CSSM_RETURN TPCertGroup::verifyWithPartialKeys(
+       const TPClItemInfo      &subjectItem)           // Cert or CRL
+{
+       TPCertInfo *lastFullKeyCert = NULL;
+       tpDebug("verifyWithPartialKeys top");
+       
+       /* start from the end - it's easier */
+       for(int dex=mNumCerts-1; dex >= 0; dex--) {
+               TPCertInfo *thisCert = mCertInfo[dex];
+               
+               /*
+                * If this is the start of the cert chain, and it's not being
+                * used to verify subjectItem, then we're done.
+                */
+               if(dex == 0) {
+                       if((void *)thisCert == (void *)&subjectItem) {
+                               tpDebug("verifyWithPartialKeys: success at leaf cert");
+                               return CSSM_OK;
+                       }
+               }
+               if(!thisCert->hasPartialKey()) {
+                       /* 
+                        * Good to know. Record this and move on.
+                        */
+                       lastFullKeyCert = thisCert;
+                       tpDebug("full key cert found at index %d", dex);
+                       continue;
+               }
+               if(lastFullKeyCert == NULL) {
+                       /*
+                        * No full keys between here and the end!
+                        */
+                       tpDebug("UNCOMPLETABLE cert at index %d", dex);
+                       if(thisCert->addStatusCode(CSSMERR_TP_CERTIFICATE_CANT_OPERATE)) {
+                               return CSSMERR_TP_CERTIFICATE_CANT_OPERATE;
+                       }
+                       else {
+                               break;
+                       }
+               }
+               
+               /* do the verify - of next cert in chain or of subjectItem */
+               const TPClItemInfo *subject;
+               if(dex == 0) {
+                       subject = &subjectItem;
+                       tpDebug("...verifying subject item with partial cert 0");
+               }
+               else {
+                       subject = mCertInfo[dex - 1];
+                       tpDebug("...verifying with partial cert %d", dex);
+               }
+               CSSM_RETURN crtn = subject->verifyWithIssuer(thisCert, 
+                       lastFullKeyCert);
+               if(crtn) {
+                       tpDebug("CERT VERIFY ERROR with partial cert at index %d", dex);
+                       if(thisCert->addStatusCode(CSSMERR_TP_CERTIFICATE_CANT_OPERATE)) {
+                               return CSSMERR_TP_INVALID_CERT_AUTHORITY;
+                       }
+                       else {
+                               break;
+                       }
+               }
+       }
+       
+       /* we just verified subjectItem - right?  */
+       assert((void *)mCertInfo[0] != (void *)&subjectItem);
+       tpDebug("verifyWithPartialKeys: success at subjectItem");
+       return CSSM_OK;
+}
+
+/* 
+ * Free records obtained from DBs. Called when these records are not going to 
+ * be passed to caller of CertGroupConstruct or CertGroupVerify.
+ */
+void TPCertGroup::freeDbRecords()
+{
+       for(unsigned dex=0; dex<mNumCerts; dex++) {
+               TPCertInfo *certInfo = mCertInfo[dex];
+               certInfo->freeUniqueRecord();
+       }
+}