]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_apple_x509_tp/lib/TPCrlInfo.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_apple_x509_tp / lib / TPCrlInfo.cpp
diff --git a/Security/libsecurity_apple_x509_tp/lib/TPCrlInfo.cpp b/Security/libsecurity_apple_x509_tp/lib/TPCrlInfo.cpp
new file mode 100644 (file)
index 0000000..21581d2
--- /dev/null
@@ -0,0 +1,787 @@
+/*
+ * Copyright (c) 2002,2011-2012,2014 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.
+ */
+
+
+/*
+ * TPCrlInfo.h - TP's private CRL and CRL group
+ *
+ */
+
+#include "TPCrlInfo.h"
+#include "tpdebugging.h"
+#include "certGroupUtils.h"
+#include "tpCrlVerify.h"
+#include "tpPolicies.h"
+#include "tpTime.h"
+#include <Security/cssmapi.h>
+#include <Security/x509defs.h>
+#include <Security/oidscert.h>
+#include <Security/oidscrl.h>
+#include <security_cdsa_utilities/cssmerrors.h>
+#include <string.h>                                            /* for memcmp */
+#include <Security/cssmapple.h>
+
+/*
+ * Replacement for CSSM_CL_CrlGetFirstCachedFieldValue for use with 
+ * TPCrlItemInfo's generic getFirstCachedField mechanism. 
+ */
+static CSSM_RETURN tpGetFirstCachedFieldValue (CSSM_CL_HANDLE CLHandle,
+                                     CSSM_HANDLE CrlHandle,
+                                     const CSSM_OID *CrlField,
+                                     CSSM_HANDLE_PTR ResultsHandle,
+                                     uint32 *NumberOfMatchedFields,
+                                     CSSM_DATA_PTR *Value)
+{
+       return CSSM_CL_CrlGetFirstCachedFieldValue(CLHandle,
+               CrlHandle,
+        NULL,          // const CSSM_DATA *CrlRecordIndex,
+               CrlField,
+               ResultsHandle,
+               NumberOfMatchedFields,
+               Value);
+}
+
+static const TPClItemCalls tpCrlClCalls =
+{
+       tpGetFirstCachedFieldValue,
+       CSSM_CL_CrlAbortQuery,
+       CSSM_CL_CrlCache,
+       CSSM_CL_CrlAbortCache,
+       CSSM_CL_CrlVerify,
+       &CSSMOID_X509V1CRLThisUpdate,
+       &CSSMOID_X509V1CRLNextUpdate,
+       CSSMERR_TP_INVALID_CRL_POINTER,
+       CSSMERR_APPLETP_CRL_EXPIRED,
+       CSSMERR_APPLETP_CRL_NOT_VALID_YET
+};
+
+
+/* 
+ * No default constructor - this is the only way.
+ * This caches the cert and fetches subjectName and issuerName
+ * to ensure the incoming certData is well-constructed.
+ */
+TPCrlInfo::TPCrlInfo(
+       CSSM_CL_HANDLE          clHand,
+       CSSM_CSP_HANDLE         cspHand,
+       const CSSM_DATA         *crlData,
+       TPItemCopy                      copyCrlData,            // true: we copy, we free
+                                                                                       // false - caller owns
+       const char                      *verifyTime)            // = NULL
+       
+       : TPClItemInfo(clHand, cspHand, tpCrlClCalls, crlData, 
+                       copyCrlData, verifyTime),
+               mRefCount(0),
+               mFromWhere(CFW_Nowhere),
+               mX509Crl(NULL),
+               mCrlFieldToFree(NULL),
+               mVerifyState(CVS_Unknown),
+               mVerifyError(CSSMERR_TP_INTERNAL_ERROR)
+{
+       CSSM_RETURN     crtn;
+
+       mUri.Data = NULL;
+       mUri.Length = 0;
+       
+       /* fetch parsed CRL */
+       crtn = fetchField(&CSSMOID_X509V2CRLSignedCrlCStruct, &mCrlFieldToFree);
+       if(crtn) {
+               /* bad CRL */
+               releaseResources();
+               CssmError::throwMe(crtn);
+       }
+       if(mCrlFieldToFree->Length != sizeof(CSSM_X509_SIGNED_CRL)) {
+               tpErrorLog("fetchField(SignedCrlCStruct) length error\n");
+               releaseResources();
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       mX509Crl = (CSSM_X509_SIGNED_CRL *)mCrlFieldToFree->Data;
+       /* any other other commonly used fields? */
+}
+       
+TPCrlInfo::~TPCrlInfo()
+{
+       releaseResources();
+}
+
+void TPCrlInfo::releaseResources()
+{
+       if(mCrlFieldToFree) {
+               freeField(&CSSMOID_X509V2CRLSignedCrlCStruct, mCrlFieldToFree);
+               mCrlFieldToFree = NULL;
+       }
+       if(mUri.Data) {
+               Allocator::standard().free(mUri.Data);
+               mUri.Data = NULL;
+               mUri.Length = 0;
+       }
+       TPClItemInfo::releaseResources();
+}
+
+void TPCrlInfo::uri(const CSSM_DATA &uri)
+{
+       tpCopyCssmData(Allocator::standard(), &uri, &mUri);
+}
+
+/*
+ * List of extensions we understand and can accept as critical.
+ */
+static const CSSM_OID *const TPGoodCrlExtens[] = 
+{
+       &CSSMOID_CrlNumber,
+       /* Note NOT CSSMOID_DeltaCrlIndicator! That's fatal */
+       &CSSMOID_CrlReason,
+       &CSSMOID_CertIssuer,
+       &CSSMOID_IssuingDistributionPoint,
+       &CSSMOID_HoldInstructionCode,
+       &CSSMOID_InvalidityDate,
+       &CSSMOID_AuthorityKeyIdentifier,
+       &CSSMOID_SubjectAltName,
+       &CSSMOID_IssuerAltName
+};
+
+#define NUM_KNOWN_EXTENS (sizeof(TPGoodCrlExtens) / sizeof(CSSM_OID_PTR))
+
+/*
+ * Do our best to understand all the entries in a CSSM_X509_EXTENSIONS,
+ * which may be per-CRL or per-entry.
+ *
+ * For now, we just ensure that for every critical extension,
+ * we actually understand it and can deal it.
+ */
+CSSM_RETURN TPCrlInfo::parseExtensions(
+       TPVerifyContext                         &vfyCtx,
+       bool                                            isPerEntry,
+       uint32                                          entryIndex,             // if isPerEntry
+       const CSSM_X509_EXTENSIONS      &extens,
+       TPCertInfo                                      *forCert,               // optional
+       bool                                            &isIndirectCrl) // RETURNED
+{
+       isIndirectCrl = false;
+       for(uint32 dex=0; dex<extens.numberOfExtensions; dex++) {
+               CSSM_X509_EXTENSION_PTR exten = &extens.extensions[dex];
+               if(exten->critical) {
+                       /* critical: is it in our list of understood extensions? */
+                       unsigned i;
+                       for(i=0; i<NUM_KNOWN_EXTENS; i++) {
+                               if(tpCompareOids(&exten->extnId, TPGoodCrlExtens[i])) {
+                                       /* we're cool with this one */
+                                       break;
+                               }
+                       }
+                       if(i == NUM_KNOWN_EXTENS) {
+                               tpCrlDebug("parseExtensions: Unknown Critical Extension\n");
+                               return CSSMERR_APPLETP_UNKNOWN_CRL_EXTEN;
+                       }
+               }
+               
+               /* Specific extension handling. */
+               if(tpCompareOids(&exten->extnId, 
+                               &CSSMOID_IssuingDistributionPoint)) {
+                       /*
+                        * If this assertion fails, we're out of sync with the CL
+                        */
+                       assert(exten->format == CSSM_X509_DATAFORMAT_PARSED);
+                       CE_IssuingDistributionPoint *idp = 
+                               (CE_IssuingDistributionPoint *)
+                                       exten->value.parsedValue;
+                                       
+                       /*
+                        * Snag indirectCrl flag for caller in any case
+                        */
+                       if(idp->indirectCrlPresent && idp->indirectCrl) {
+                               isIndirectCrl = true;
+                       }
+                       if(forCert != NULL) {
+                               /* If no target cert, i.e., we're just verifying a CRL,
+                                * skip the remaining IDP checks. */
+                               
+                               /* verify onlyCACerts/onlyUserCerts */
+                               bool isUserCert;
+                               if(forCert->isLeaf() &&
+                                       !(vfyCtx.actionFlags & CSSM_TP_ACTION_LEAF_IS_CA)) {
+                                               isUserCert = true;
+                               }
+                               else {
+                                       isUserCert = false;
+                               }
+                               if((idp->onlyUserCertsPresent) && (idp->onlyUserCerts)) {
+                                       if(!isUserCert) {
+                                               tpCrlDebug("parseExtensions: onlyUserCerts, "
+                                                       "!leaf\n");
+                                               return CSSMERR_APPLETP_IDP_FAIL;
+                                       }
+                               }
+                               if((idp->onlyCACertsPresent) && (idp->onlyCACerts)) {
+                                       if(isUserCert) {
+                                               tpCrlDebug("parseExtensions: onlyCACerts, leaf\n");
+                                               return CSSMERR_APPLETP_IDP_FAIL;
+                                       }
+                               }
+                       }       /* IDP */
+               }               /* have target cert */
+       }
+       
+       return CSSM_OK;
+}
+
+/* 
+ * The heavyweight "perform full verification of this CRL" op.
+ * Must verify to an anchor cert in tpVerifyContext or via
+ * Trust Settings if so enabled. 
+ * Intermediate certs can come from signerCerts or dBList. 
+ */
+CSSM_RETURN TPCrlInfo::verifyWithContext(
+       TPVerifyContext                 &tpVerifyContext,
+       TPCertInfo                              *forCert,               // optional 
+       bool                                    doCrlVerify)    
+{
+       /*
+        * Step 1: this CRL must be current. Caller might have re-evaluated
+        * expired/notValidYet since our construction via calculateCurrent().
+        */
+       if(isExpired()) {
+               return CSSMERR_APPLETP_CRL_EXPIRED;
+       }
+       if(isNotValidYet()) {
+               return CSSMERR_APPLETP_CRL_NOT_VALID_YET;
+       }
+
+       /* subsequent verify state is cached */
+       switch(mVerifyState) {
+               case CVS_Good:
+                       return CSSM_OK;
+               case CVS_Bad:
+                       return mVerifyError;
+               case CVS_Unknown:
+                       break;
+               default:
+                       tpErrorLog("verifyWithContext: bad verifyState\n");
+                       return CSSMERR_TP_INTERNAL_ERROR;
+       }
+       
+       /*
+        * Step 2: parse & understand all critical CRL extensions. 
+        */
+       CSSM_RETURN crtn;
+       bool isIndirectCrl;
+       crtn = parseExtensions(tpVerifyContext,
+               false,
+               0,
+               mX509Crl->tbsCertList.extensions,
+               forCert,
+               isIndirectCrl);
+       if(crtn) {
+               mVerifyState = CVS_Bad;
+               if(!forCert || forCert->addStatusCode(crtn)) {
+                       return crtn;
+               }
+               /* else continue */
+       }
+       CSSM_X509_REVOKED_CERT_LIST_PTR revoked = 
+                       mX509Crl->tbsCertList.revokedCertificates;
+       if(revoked != NULL) {
+               for(uint32 dex=0; dex<revoked->numberOfRevokedCertEntries; dex++) {
+                       bool dummyIsIndirect;   // can't be set here 
+                       crtn = parseExtensions(tpVerifyContext,
+                               true,
+                               dex,
+                               revoked->revokedCertEntry[dex].extensions,
+                               forCert,
+                               dummyIsIndirect);
+                       if(crtn) {
+                               if(!forCert || forCert->addStatusCode(crtn)) {
+                                       mVerifyState = CVS_Bad;
+                                       return crtn;
+                               }
+                       }
+               }
+       }
+       
+       /*
+        * Step 3: obtain a fully verified cert chain which verifies this CRL.
+        */
+       CSSM_BOOL       verifiedToRoot;
+       CSSM_BOOL       verifiedToAnchor;
+       CSSM_BOOL       verifiedViaTrustSetting;
+       
+       TPCertGroup outCertGroup(tpVerifyContext.alloc, 
+               TGO_Caller);                    // CRLs owned by inCertGroup
+
+       /* set up for disposal of TPCertInfos created by 
+        * CertGroupConstructPriv */
+       TPCertGroup     certsToBeFreed(tpVerifyContext.alloc, TGO_Group);
+       
+       if(tpVerifyContext.signerCerts) {
+               /* start from scratch with this group */
+               tpVerifyContext.signerCerts->setAllUnused();
+       }
+       crtn = outCertGroup.buildCertGroup(
+                       *this,                                                  // subject item
+                       tpVerifyContext.signerCerts,    // inCertGroup, optional
+                       tpVerifyContext.dbList,                 // optional
+                       tpVerifyContext.clHand,
+                       tpVerifyContext.cspHand,
+                       tpVerifyContext.verifyTime,
+                       tpVerifyContext.numAnchorCerts,
+                       tpVerifyContext.anchorCerts,
+                       certsToBeFreed,
+                       &tpVerifyContext.gatheredCerts,
+                       CSSM_FALSE,                                             // subjectIsInGroup
+                       tpVerifyContext.actionFlags,
+                       tpVerifyContext.policyOid,
+                       tpVerifyContext.policyStr,
+                       tpVerifyContext.policyStrLen,
+                       kSecTrustSettingsKeyUseSignRevocation,
+                       verifiedToRoot, 
+                       verifiedToAnchor,
+                       verifiedViaTrustSetting);
+       /* subsequent errors to errOut: */
+
+       if(crtn) {
+               tpCrlDebug("TPCrlInfo::verifyWithContext buildCertGroup failure "
+                       "index %u",     index());
+               if(!forCert || forCert->addStatusCode(crtn)) {
+                       goto errOut;
+               }
+       }
+       if (verifiedToRoot && (tpVerifyContext.actionFlags & CSSM_TP_ACTION_IMPLICIT_ANCHORS))
+               verifiedToAnchor = CSSM_TRUE;
+       if(!verifiedToAnchor && !verifiedViaTrustSetting) {
+               /* required */
+               if(verifiedToRoot) {
+                       /* verified to root which is not an anchor */
+                       tpCrlDebug("TPCrlInfo::verifyWithContext root, no anchor, "
+                               "index %u",     index());
+                       crtn = CSSMERR_APPLETP_CRL_INVALID_ANCHOR_CERT;
+               }
+               else {
+                       /* partial chain, no root, not verifiable by anchor */
+                       tpCrlDebug("TPCrlInfo::verifyWithContext no root, no anchor, "
+                               "index %u",     index());
+                       crtn = CSSMERR_APPLETP_CRL_NOT_TRUSTED;
+               }
+               if(!forCert || forCert->addStatusCode(crtn)) {
+                       mVerifyState = CVS_Bad;
+                       goto errOut;
+               }
+       }
+       
+       /* 
+        * Step 4: policy verification on the returned cert group 
+        * We need to (temporarily) assert the "leaf cert is a CA" flag
+        * here. 
+        */
+       outCertGroup.certAtIndex(0)->isLeaf(true);
+       crtn = tp_policyVerify(kCrlPolicy,
+               tpVerifyContext.alloc,
+               tpVerifyContext.clHand,
+               tpVerifyContext.cspHand,
+               &outCertGroup,
+               verifiedToRoot,
+               verifiedViaTrustSetting,
+               tpVerifyContext.actionFlags | CSSM_TP_ACTION_LEAF_IS_CA,
+               NULL,                                                   // sslOpts
+               NULL);                                                  // policyOpts, not currently used 
+       if(crtn) {
+               tpCrlDebug("   ...verifyWithContext policy FAILURE CRL %u",
+                       index());
+               if(!forCert || forCert->addStatusCode(CSSMERR_APPLETP_CRL_POLICY_FAIL)) {
+                       mVerifyState = CVS_Bad;
+                       goto errOut;
+               }
+       }
+       
+       /*
+        * Step 5: recursively perform CRL verification on the certs 
+        * gathered to verify this CRL. 
+        * Only performed if this CRL is an indirect CRL or the caller
+        * explicitly told us to do this (i.e., caller is verifying a
+        * CRL, not a cert chain).
+        */
+       if(isIndirectCrl || doCrlVerify) {
+               tpCrlDebug("verifyWithContext recursing to "
+                       "tpVerifyCertGroupWithCrls");
+               crtn = tpVerifyCertGroupWithCrls(tpVerifyContext,
+                       outCertGroup);
+               if(crtn) {
+                       tpCrlDebug("   ...verifyWithContext CRL reverify FAILURE CRL %u",
+                               index());
+                       if(!forCert || forCert->addStatusCode(crtn)) {
+                               mVerifyState = CVS_Bad;
+                               goto errOut;
+                       }
+               }
+       }
+
+       tpCrlDebug("   ...verifyWithContext CRL %u SUCCESS", index());
+       mVerifyState = CVS_Good;
+errOut:
+       /* we own these, we free the DB records */
+       certsToBeFreed.freeDbRecords();
+       return crtn;
+}
+
+/*
+ * Wrapper for verifyWithContext for use when evaluating a CRL
+ * "now" instead of at the time in TPVerifyContext.verifyTime.
+ * In this case, on entry, TPVerifyContext.verifyTime is the 
+ * time at which a cert is being evaluated.
+ */
+CSSM_RETURN TPCrlInfo::verifyWithContextNow(
+       TPVerifyContext         &tpVerifyContext,
+       TPCertInfo                      *forCert,                       // optional
+       bool                            doCrlVerify)
+{
+       CSSM_TIMESTRING ctxTime = tpVerifyContext.verifyTime;
+       CSSM_RETURN crtn = verifyWithContext(tpVerifyContext, forCert, doCrlVerify);
+       tpVerifyContext.verifyTime = ctxTime;
+       return crtn;
+}
+
+/*
+ * Do I have the same issuer as the specified subject cert? Returns 
+ * true if so.
+ */
+bool TPCrlInfo::hasSameIssuer(
+       const TPCertInfo        &subject)
+{
+       assert(subject.issuerName() != NULL);
+       if(tpCompareCssmData(issuerName(), subject.issuerName())) {
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/*
+ * Determine if specified cert has been revoked as of the
+ * provided time; a NULL timestring indicates "now". 
+ *
+ * Assumes current CRL is verified good and that issuer names of 
+ * the cert and CRL match.
+ *
+ * This duplicates similar logic in the CL, but to avoid re-parsing
+ * the subject cert (which we have parsed and cached), we just do it 
+ * here.
+ *
+ * Possible errors are 
+ *     CSSMERR_TP_CERT_REVOKED
+ *     CSSMERR_TP_CERT_SUSPENDED
+ *  TBD
+ *
+ * Error status is added to subjectCert. 
+ */
+CSSM_RETURN TPCrlInfo::isCertRevoked(
+       TPCertInfo &subjectCert,
+       CSSM_TIMESTRING verifyTime)
+{
+       assert(mVerifyState == CVS_Good);
+       CSSM_X509_TBS_CERTLIST_PTR tbs = &mX509Crl->tbsCertList;
+       
+       /* trivial case - empty CRL */
+       if((tbs->revokedCertificates == NULL) ||
+          (tbs->revokedCertificates->numberOfRevokedCertEntries == 0)) {
+          tpCrlDebug("   isCertRevoked: empty CRL at index %u", index());
+          return CSSM_OK;
+       }
+       
+       /* is subject cert's serial number in this CRL? */
+       CSSM_DATA_PTR subjSerial = NULL;
+       CSSM_RETURN crtn;
+       crtn = subjectCert.fetchField(&CSSMOID_X509V1SerialNumber, &subjSerial);
+       if(crtn) {
+               /* should never happen */
+               tpErrorLog("TPCrlInfo:isCertRevoked: error fetching serial number\n");
+               if(subjectCert.addStatusCode(crtn)) {
+                       return crtn;
+               }
+               else {
+                       /* allowed error - can't proceed; punt with success */
+                       return CSSM_OK;
+               }
+       }
+       /* subsequent errors to errOut: */
+       
+       uint32 numEntries = tbs->revokedCertificates->numberOfRevokedCertEntries;
+       CSSM_X509_REVOKED_CERT_ENTRY_PTR entries = 
+               tbs->revokedCertificates->revokedCertEntry;
+       crtn = CSSM_OK;
+       CFDateRef cfRevokedTime = NULL;
+       CFDateRef cfVerifyTime = NULL;
+
+       for(uint32 dex=0; dex<numEntries; dex++) {
+               CSSM_X509_REVOKED_CERT_ENTRY_PTR entry = &entries[dex];
+               if(tpCompareCssmData(subjSerial, &entry->certificateSerialNumber)) {
+                       /* 
+                        * It's in there. Compare revocation time in the CRL to 
+                        * our caller-specified verifyTime.
+                        */
+                       CSSM_X509_TIME_PTR xTime = &entry->revocationDate;
+                       int rtn;
+                       rtn = timeStringToCfDate((char *)xTime->time.Data, (unsigned)xTime->time.Length,
+                               &cfRevokedTime);
+                       if(rtn) {
+                               tpErrorLog("fetchNotBeforeAfter: malformed revocationDate\n");
+                       }
+                       else {
+                               if(verifyTime != NULL) {
+                                       rtn = timeStringToCfDate((char *)verifyTime, (unsigned)strlen(verifyTime),
+                                                                                        &cfVerifyTime);
+                               }
+                               else {
+                                       /* verify right now */
+                                       cfVerifyTime = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
+                               }
+                               if((rtn == 0) && cfVerifyTime != NULL) {
+                                       CFComparisonResult res = CFDateCompare(cfVerifyTime, cfRevokedTime, NULL);
+                                       if(res == kCFCompareLessThan) {
+                                               /* cfVerifyTime < cfRevokedTime; I guess this one's OK */
+                                               tpCrlDebug("   isCertRevoked: cert %u NOT YET REVOKED by CRL %u", 
+                                                                  subjectCert.index(), index());
+                                               break;
+                                       }
+                               }
+                       }
+
+                       /*
+                        * REQUIRED TBD: parse the entry's extensions, specifically to 
+                        * get a reason. This will entail a bunch of new TP/cert specific
+                        * CSSM_RETURNS.
+                        * For now, just flag it revoked. 
+                        */
+                       crtn = CSSMERR_TP_CERT_REVOKED;
+                       tpCrlDebug("   isCertRevoked: cert %u REVOKED by CRL %u", 
+                               subjectCert.index(), index());
+                       break;
+               }
+       }
+       
+       subjectCert.freeField(&CSSMOID_X509V1SerialNumber, subjSerial);
+       if(crtn && !subjectCert.addStatusCode(crtn)) {
+               return CSSM_OK;
+       }
+       if(cfRevokedTime) {
+               CFRelease(cfRevokedTime);
+       }
+       if(cfVerifyTime) {
+               CFRelease(cfVerifyTime);
+       }
+       return crtn;
+}
+
+/***
+ *** TPCrlGroup class
+ ***/
+/* build empty group */
+TPCrlGroup::TPCrlGroup(
+       Allocator                       &alloc,
+       TPGroupOwner            whoOwns) :
+               mAlloc(alloc),
+               mCrlInfo(NULL),
+               mNumCrls(0),
+               mSizeofCrlInfo(0),
+               mWhoOwns(whoOwns)
+{
+       /* nothing for now */
+}
+       
+/*
+ * Construct from unordered, untrusted CSSM_CRLGROUP. Resulting
+ * TPCrlInfos are more or less in the same order as the incoming
+ * CRLs, though incoming CRLs are discarded if they don't parse.
+ * No verification of any sort is performed. 
+ */
+TPCrlGroup::TPCrlGroup(
+       const CSSM_CRLGROUP     *cssmCrlGroup,                  // optional
+       CSSM_CL_HANDLE                  clHand,
+       CSSM_CSP_HANDLE                 cspHand,
+       Allocator                               &alloc,
+       const char                              *verifyTime,                    // may be NULL
+       TPGroupOwner                    whoOwns) :
+               mAlloc(alloc),
+               mCrlInfo(NULL),
+               mNumCrls(0),
+               mSizeofCrlInfo(0),
+               mWhoOwns(whoOwns)
+{
+       /* verify input args */
+       if((cssmCrlGroup == NULL) || (cssmCrlGroup->NumberOfCrls == 0)) {
+               return;
+       }
+       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(cssmCrlGroup->CrlGroupType != CSSM_CRLGROUP_DATA) {
+               CssmError::throwMe(CSSMERR_TP_INVALID_CERTGROUP);
+       }
+       switch(cssmCrlGroup->CrlType) {
+               case CSSM_CRL_TYPE_X_509v1:
+               case CSSM_CRL_TYPE_X_509v2:
+                       break;
+               default:
+                       CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT);
+       }
+       switch(cssmCrlGroup->CrlEncoding) {
+               case CSSM_CRL_ENCODING_BER:
+               case CSSM_CRL_ENCODING_DER:
+                       break;
+               default:
+                       CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT);
+       }
+       
+       /* 
+        * Add remaining input certs to mCrlInfo.
+        */
+       TPCrlInfo *crlInfo = NULL;
+       for(unsigned crlDex=0; crlDex<cssmCrlGroup->NumberOfCrls; crlDex++) {
+               try {
+                       crlInfo = new TPCrlInfo(clHand,
+                               cspHand,
+                               &cssmCrlGroup->GroupCrlList.CrlList[crlDex],
+                               TIC_NoCopy,                     // don't copy data 
+                               verifyTime);
+               }
+               catch (...) {
+                       /* just ignore this CRL */
+                       continue;
+               }
+               crlInfo->index(crlDex);
+               appendCrl(*crlInfo);
+       }
+}
+
+/*
+ * Deletes all TPCrlInfo's if appropriate.
+ */
+TPCrlGroup::~TPCrlGroup()
+{
+       if(mWhoOwns == TGO_Group) {
+               unsigned i;
+               for(i=0; i<mNumCrls; i++) {
+                       delete mCrlInfo[i];
+               }
+       }
+       mAlloc.free(mCrlInfo);
+}
+
+/* add/remove/access TPTCrlInfo'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 TPCrlGroup::appendCrl(
+       TPCrlInfo                       &crlInfo)
+{
+       if(mNumCrls == mSizeofCrlInfo) {
+               if(mSizeofCrlInfo == 0) {
+                       /* appending to empty array */
+                       mSizeofCrlInfo = 1;
+               }
+               else {
+                       mSizeofCrlInfo *= 2;
+               }
+               mCrlInfo = (TPCrlInfo **)mAlloc.realloc(mCrlInfo, 
+                       mSizeofCrlInfo * sizeof(TPCrlInfo *));
+       }
+       mCrlInfo[mNumCrls++] = &crlInfo;
+}
+
+TPCrlInfo *TPCrlGroup::crlAtIndex(
+       unsigned                        index)
+{
+       if(index > (mNumCrls - 1)) {
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       return mCrlInfo[index];
+}
+
+TPCrlInfo &TPCrlGroup::removeCrlAtIndex(
+       unsigned                        index)                          // doesn't delete the cert, just 
+                                                                                       // removes it from our list
+{
+       if(index > (mNumCrls - 1)) {
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       TPCrlInfo &rtn = *mCrlInfo[index];
+       
+       /* removed requested element and compact remaining array */
+       unsigned i;
+       for(i=index; i<(mNumCrls - 1); i++) {
+               mCrlInfo[i] = mCrlInfo[i+1];
+       }
+       mNumCrls--;
+       return rtn;
+}
+
+void TPCrlGroup::removeCrl(
+       TPCrlInfo                       &crlInfo)
+{
+       for(unsigned dex=0; dex<mNumCrls; dex++) {
+               if(mCrlInfo[dex] == &crlInfo) {
+                       removeCrlAtIndex(dex);
+                       return;
+               }
+       }
+       tpErrorLog("TPCrlGroup::removeCrl: CRL NOT FOUND\n");
+       assert(0);
+}
+
+TPCrlInfo *TPCrlGroup::firstCrl()
+{
+       if(mNumCrls == 0) {
+               /* the caller really should not do this... */
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       else {
+               return mCrlInfo[0];
+       }
+}
+
+TPCrlInfo *TPCrlGroup::lastCrl()
+{
+       if(mNumCrls == 0) {
+               /* the caller really should not do this... */
+               CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
+       }
+       else {
+               return mCrlInfo[mNumCrls - 1];
+       }
+}
+
+       /* 
+        * Find a CRL whose issuer matches specified subject cert.
+        * Returned CRL has not necessarily been verified.
+        */
+TPCrlInfo *TPCrlGroup::findCrlForCert(
+               TPCertInfo                      &subject)
+{
+       for(unsigned dex=0; dex<mNumCrls; dex++) {
+               TPCrlInfo *crl = mCrlInfo[dex];
+               if(crl->hasSameIssuer(subject)) {
+                       return crl;
+               }
+       }
+       return NULL;
+}