]> git.saurik.com Git - apple/security.git/blobdiff - SecurityTests/clxutils/clAppUtils/certVerify.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / SecurityTests / clxutils / clAppUtils / certVerify.cpp
diff --git a/SecurityTests/clxutils/clAppUtils/certVerify.cpp b/SecurityTests/clxutils/clAppUtils/certVerify.cpp
new file mode 100644 (file)
index 0000000..4095425
--- /dev/null
@@ -0,0 +1,692 @@
+/*
+ * certVerify.cpp - execute cert/CRL verify; display results
+ */
+#include "certVerify.h"
+#include "tpUtils.h"
+#include <utilLib/common.h>
+#include <clAppUtils/clutils.h>
+#include <clAppUtils/tpUtils.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <Security/cssm.h>
+#include <Security/oidsalg.h>
+#include <Security/SecTrust.h>
+#include <Security/SecPolicySearch.h>
+#include <Security/cssmapplePriv.h>
+#include <security_cdsa_utils/cuCdsaUtils.h>
+#include <Security/TrustSettingsSchema.h>
+
+static int vfyCertErrors(
+       const CSSM_TP_VERIFY_CONTEXT_RESULT *vfyResult, 
+       unsigned numCertErrors, 
+       const char **certErrors,        // e.g., "2:CSSMERR_TP_CERT_EXPIRED"
+       CSSM_BOOL quiet)
+{
+       if(numCertErrors == 0) {
+               return 0;
+       }
+       if(vfyResult->NumberOfEvidences != 3) {
+               printf("***vfyCertErrors: NumberOfEvidences is %u, expect 3\n",
+                       (unsigned)vfyResult->NumberOfEvidences);
+               return 1;
+       }
+       
+       /* numCerts from evidence[1] */
+       const CSSM_EVIDENCE *ev = &vfyResult->Evidence[1];
+       const CSSM_CERTGROUP *grp = (const CSSM_CERTGROUP *)ev->Evidence;
+       unsigned numCerts = grp->NumCerts;
+       /* array of Apple-specific info from evidence[2] */
+       ev = &vfyResult->Evidence[2];
+       const CSSM_TP_APPLE_EVIDENCE_INFO *info = 
+                               (const CSSM_TP_APPLE_EVIDENCE_INFO *)ev->Evidence;
+       int ourRtn = 0;
+       
+       for(unsigned dex=0; dex<numCertErrors; dex++) {
+               const char *str = certErrors[dex];
+               char buf[8];
+               unsigned i;
+               
+               /* 
+                * Format is certNum:errorString
+                * first get the cert number 
+                */
+               for(i=0; *str != '\0'; i++, str++) {
+                       if(*str == ':') {
+                               break;
+                       }
+                       buf[i] = *str;
+               }
+               if(*str != ':') {
+                       printf("***Bad certerror value, format is certNum:errorString\n");
+                       return 1;
+               }
+               buf[i] = '\0';
+               unsigned certNum = atoi(buf);
+               if(certNum > (numCerts-1)) {
+                       printf("***certerror specified for cert %u, but only %u certs"
+                               " available\n", certNum, numCerts);
+                       return 1;
+               }
+               str++;                  // pts to actual desired error string now
+               
+               /*
+                * There may be multiple per-cert statuses in the evidence; search all
+                * looking for a match 
+                */
+               const CSSM_TP_APPLE_EVIDENCE_INFO *thisInfo = &info[certNum];
+               char *found = NULL;
+               for(unsigned i=0; i<thisInfo->NumStatusCodes; i++) {
+                       CSSM_RETURN actRtn = thisInfo->StatusCodes[i];
+                       const char *actRtnStr = cssmErrToStr(actRtn);
+                       found = strstr(actRtnStr, str);
+                       if(found) {
+                               break;
+                       }
+               }
+               if(found) {
+                       if(!quiet) {
+                               printf("...%s per-cert status received as expected\n", str);
+                       }
+               }
+               else {
+                       printf("***Per cert status %s not found\n", str);
+                       ourRtn = 1;
+                       /* might as well keep going */
+               }
+       }
+       return ourRtn;
+}
+
+static int vfyCertStatus(
+       const CSSM_TP_VERIFY_CONTEXT_RESULT *vfyResult, 
+       unsigned numCertStatus,
+       const char **certStatus,        // e.g., "1:0x18", leading 0x optional
+       CSSM_BOOL quiet)
+{
+       if(numCertStatus == 0) {
+               return 0;
+       }
+       if(vfyResult->NumberOfEvidences != 3) {
+               printf("***vfyCertStatus: NumberOfEvidences is %u, expect 3\n",
+                       (unsigned)vfyResult->NumberOfEvidences);
+               return 1;
+       }
+       
+       /* numCerts from evidence[1] */
+       const CSSM_EVIDENCE *ev = &vfyResult->Evidence[1];
+       const CSSM_CERTGROUP *grp = (const CSSM_CERTGROUP *)ev->Evidence;
+       unsigned numCerts = grp->NumCerts;
+       /* array of Apple-specific info from evidence[2] */
+       ev = &vfyResult->Evidence[2];
+       const CSSM_TP_APPLE_EVIDENCE_INFO *info = 
+                               (const CSSM_TP_APPLE_EVIDENCE_INFO *)ev->Evidence;
+       int ourRtn = 0;
+       
+       for(unsigned dex=0; dex<numCertStatus; dex++) {
+               const char *str = certStatus[dex];
+               char buf[8];
+               unsigned i;
+               
+               /* 
+                * Format is certNum:status_in_hex
+                * first get the cert number 
+                */
+               for(i=0; *str != '\0'; i++, str++) {
+                       if(*str == ':') {
+                               break;
+                       }
+                       buf[i] = *str;
+               }
+               if(*str != ':') {
+                       printf("***Bad certstatus value, format is certNum:status_in_hex\n");
+                       return 1;
+               }
+               buf[i] = '\0';
+               unsigned certNum = atoi(buf);
+               if(certNum > (numCerts-1)) {
+                       printf("***certerror specified for cert %u, but only %u certs"
+                               " available\n", certNum, numCerts);
+                       return 1;
+               }
+               str++;                  // pts to actual desired status string now
+               unsigned certStat = hexToBin(str);
+               const CSSM_TP_APPLE_EVIDENCE_INFO *thisInfo = &info[certNum];
+               if(certStat == thisInfo->StatusBits) {
+                       if(!quiet) {
+                               printf("...0x%x per-cert status received as expected\n", certStat);
+                       }
+               }
+               else {
+                       printf("***Expected per cert status 0x%x, got 0x%x\n",
+                               (unsigned)certStat, (unsigned)thisInfo->StatusBits);
+                       ourRtn = 1;
+               }
+       }
+       return ourRtn;
+}
+
+/*
+ * Ensure that the policy being evaluated is accessible via 
+ * SecPolicySearch*(). Not really part of the test, but a handy place
+ * to catch this common error before checking in TP changes. 
+ */ 
+static int verifySecPolicy(
+       const CSSM_OID *oid)
+{
+       SecPolicySearchRef srchRef = NULL;
+       OSStatus ortn;
+       
+       ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3, oid, NULL, &srchRef);
+       if(ortn) {
+               cssmPerror("SecPolicySearchCreate", ortn);
+               return -1;
+       }
+       SecPolicyRef policyRef = NULL;
+       ortn = SecPolicySearchCopyNext(srchRef, &policyRef);
+       if(ortn) {
+               cssmPerror("SecPolicySearchCopyNext", ortn);
+               printf("***The TP policy used in this test is not accessible via SecPolicySearchCopyNext().\n");
+               printf("   You probably forgot to add the policy to the theOidList table in PolicyCursor.cpp\n");
+               printf("   in the libsecurity_keychain project.\n");
+       }
+       CFRelease(srchRef);
+       if(policyRef) {
+               CFRelease(policyRef);
+       }
+       return ortn;
+}
+
+int certVerify(CertVerifyArgs *vfyArgs)
+{
+       if(vfyArgs->version != CERT_VFY_ARGS_VERS) {
+               printf("***CertVerifyArgs.Version mismatch. Clean and rebuild.\n");
+               return -1;
+       }
+       
+       /* main job is building a CSSM_TP_VERIFY_CONTEXT and its components */
+       CSSM_TP_VERIFY_CONTEXT                  vfyCtx;
+       CSSM_TP_CALLERAUTH_CONTEXT              authCtx;
+       CSSM_TP_VERIFY_CONTEXT_RESULT   vfyResult;
+       CSSM_APPLE_TP_SSL_OPTIONS               sslOpts;
+       CSSM_APPLE_TP_SMIME_OPTIONS             smimeOpts;
+       
+       memset(&vfyCtx, 0, sizeof(CSSM_TP_VERIFY_CONTEXT));
+       memset(&authCtx, 0, sizeof(CSSM_TP_CALLERAUTH_CONTEXT));
+       
+       /* CSSM_TP_CALLERAUTH_CONTEXT components */
+       /* 
+               typedef struct cssm_tp_callerauth_context {
+                       CSSM_TP_POLICYINFO Policy;
+                       CSSM_TIMESTRING VerifyTime;
+                       CSSM_TP_STOP_ON VerificationAbortOn;
+                       CSSM_TP_VERIFICATION_RESULTS_CALLBACK CallbackWithVerifiedCert;
+                       uint32 NumberOfAnchorCerts;
+                       CSSM_DATA_PTR AnchorCerts;
+                       CSSM_DL_DB_LIST_PTR DBList;
+                       CSSM_ACCESS_CREDENTIALS_PTR CallerCredentials;
+               } CSSM_TP_CALLERAUTH_CONTEXT, *CSSM_TP_CALLERAUTH_CONTEXT_PTR;
+       */
+       /* up to 3 policies */
+       CSSM_FIELD      policyIds[3];
+       CSSM_FIELD  *policyPtr = &policyIds[0];
+       uint32 numPolicies = 0;
+       memset(policyIds, 0, 3 * sizeof(CSSM_FIELD));
+
+       switch(vfyArgs->vfyPolicy) {
+               case CVP_SSL:
+               case CVP_IPSec:
+                       if(vfyArgs->vfyPolicy == CVP_SSL) {
+                               policyPtr->FieldOid = CSSMOID_APPLE_TP_SSL;
+                       }
+                       else {
+                               policyPtr->FieldOid = CSSMOID_APPLE_TP_IP_SEC;
+                       }
+                       /* otherwise these policies are identical */
+                       /* sslOpts is optional */
+                       if((vfyArgs->sslHost != NULL) || vfyArgs->sslClient) {
+                               memset(&sslOpts, 0, sizeof(sslOpts));
+                               sslOpts.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION;
+                               sslOpts.ServerName = vfyArgs->sslHost;
+                               if(vfyArgs->sslHost != NULL) {
+                                       sslOpts.ServerNameLen = strlen(vfyArgs->sslHost) + 1;
+                               }
+                               if(vfyArgs->sslClient) {
+                                       sslOpts.Flags |= CSSM_APPLE_TP_SSL_CLIENT;
+                               }
+                               policyPtr->FieldValue.Data = (uint8 *)&sslOpts;
+                               policyPtr->FieldValue.Length = sizeof(sslOpts);
+                       }
+                       break;
+               case CVP_SMIME:
+               case CVP_iChat:
+                       if(vfyArgs->vfyPolicy == CVP_SMIME) {
+                               policyPtr->FieldOid = CSSMOID_APPLE_TP_SMIME;
+                       }
+                       else {
+                               policyPtr->FieldOid = CSSMOID_APPLE_TP_ICHAT;
+                       }
+                       /* otherwise these policies are identical */
+                       /* smimeOpts is optional */
+                       if(vfyArgs->senderEmail != NULL) {
+                               smimeOpts.Version = CSSM_APPLE_TP_SMIME_OPTS_VERSION;
+                               smimeOpts.IntendedUsage = vfyArgs->intendedKeyUse;
+                               smimeOpts.SenderEmail = vfyArgs->senderEmail;
+                               smimeOpts.SenderEmailLen = strlen(vfyArgs->senderEmail) + 1;
+                               policyPtr->FieldValue.Data = (uint8 *)&smimeOpts;
+                               policyPtr->FieldValue.Length = sizeof(smimeOpts);
+                       }
+                       break;
+               case CVP_Basic:
+                       policyPtr->FieldOid = CSSMOID_APPLE_X509_BASIC;
+                       break;
+               case CVP_SWUpdateSign:
+                       /* no options */
+                       policyPtr->FieldOid = CSSMOID_APPLE_TP_SW_UPDATE_SIGNING;
+                       break;
+               case CVP_ResourceSigning:
+                       /* no options */
+                       policyPtr->FieldOid = CSSMOID_APPLE_TP_RESOURCE_SIGN;
+                       break;
+               case CVP_PKINIT_Server:
+                       /* no options */
+                       policyPtr->FieldOid = CSSMOID_APPLE_TP_PKINIT_SERVER;
+                       break;
+               case CVP_PKINIT_Client:
+                       /* no options */
+                       policyPtr->FieldOid = CSSMOID_APPLE_TP_PKINIT_CLIENT;
+                       break;
+               case CVP_AppleCodeSigning:
+                       /* no options */
+                       policyPtr->FieldOid = CSSMOID_APPLE_TP_CODE_SIGNING;
+                       break;
+               case CVP_PackageSigning:
+                       /* no options */
+                       policyPtr->FieldOid = CSSMOID_APPLE_TP_PACKAGE_SIGNING;
+                       break;
+               default:
+                       printf("***certVerify: bogus vfyPolicy\n");
+                       return 1;
+       }
+       if(verifySecPolicy(&policyPtr->FieldOid)) {
+               return -1;
+       }
+       policyPtr++;
+       numPolicies++;
+       
+       CSSM_APPLE_TP_CRL_OPTIONS crlOpts;
+       if((vfyArgs->revokePolicy == CRP_CRL) || (vfyArgs->revokePolicy == CRP_CRL_OCSP)) {
+               memset(&crlOpts, 0, sizeof(crlOpts));
+               policyPtr->FieldOid = CSSMOID_APPLE_TP_REVOCATION_CRL;
+
+               crlOpts.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
+               crlOpts.CrlFlags = 0;
+               crlOpts.crlStore = NULL;
+               policyPtr->FieldValue.Data = (uint8 *)&crlOpts;
+               policyPtr->FieldValue.Length = sizeof(crlOpts);
+               if(vfyArgs->requireCrlForAll) {
+                       crlOpts.CrlFlags |= CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT;
+               }
+               if(vfyArgs->crlNetFetchEnable) {
+                       crlOpts.CrlFlags |= CSSM_TP_ACTION_FETCH_CRL_FROM_NET;
+               }
+               if(vfyArgs->requireCrlIfPresent) {
+                       crlOpts.CrlFlags |= CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT;
+               }
+               crlOpts.crlStore = vfyArgs->crlDlDb;
+               policyPtr++;
+               numPolicies++;
+       }
+       
+       CSSM_APPLE_TP_OCSP_OPTIONS ocspOpts;
+       CSSM_DATA respUriData;
+       CSSM_DATA respCertData = {vfyArgs->responderCertLen, 
+                       (uint8 *)vfyArgs->responderCert};
+       if((vfyArgs->revokePolicy == CRP_OCSP) || (vfyArgs->revokePolicy == CRP_CRL_OCSP)) {
+               memset(&ocspOpts, 0, sizeof(ocspOpts));
+               policyPtr->FieldOid = CSSMOID_APPLE_TP_REVOCATION_OCSP;
+
+               crlOpts.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
+               policyPtr->FieldValue.Data = (uint8 *)&ocspOpts;
+               policyPtr->FieldValue.Length = sizeof(ocspOpts);
+               if(vfyArgs->requireOcspForAll) {
+                       ocspOpts.Flags |= CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT;
+               }
+               if(vfyArgs->requireOcspIfPresent) {
+                       ocspOpts.Flags |= CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT;
+               }
+               if(vfyArgs->disableCache) {
+                       ocspOpts.Flags |= (CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE |
+                                                      CSSM_TP_ACTION_OCSP_CACHE_WRITE_DISABLE);
+               }
+               if(vfyArgs->disableOcspNet) {
+                       ocspOpts.Flags |= CSSM_TP_ACTION_OCSP_DISABLE_NET;
+               }
+               if(vfyArgs->generateOcspNonce) {
+                       ocspOpts.Flags |= CSSM_TP_OCSP_GEN_NONCE;
+               }
+               if(vfyArgs->requireOcspRespNonce) {
+                       ocspOpts.Flags |= CSSM_TP_OCSP_REQUIRE_RESP_NONCE;
+               }
+               if(vfyArgs->responderURI != NULL) {
+                       respUriData.Data = (uint8 *)vfyArgs->responderURI;
+                       respUriData.Length = strlen(vfyArgs->responderURI);
+                       ocspOpts.LocalResponder = &respUriData;
+               }
+               if(vfyArgs->responderCert != NULL) {
+                       ocspOpts.LocalResponderCert = &respCertData;
+               }
+               /* other OCSP options here */
+               policyPtr++;
+               numPolicies++;
+       }
+       
+       authCtx.Policy.NumberOfPolicyIds = numPolicies;
+
+       authCtx.Policy.PolicyIds = policyIds;
+       authCtx.Policy.PolicyControl = NULL;
+       
+       authCtx.VerifyTime = vfyArgs->vfyTime;                  // may be NULL
+       authCtx.VerificationAbortOn = CSSM_TP_STOP_ON_POLICY;
+       authCtx.CallbackWithVerifiedCert = NULL;
+       
+       /*
+        * DLDBs - the caller's optional set, plus two more we open
+        * if trust settings are enabled and we're told to use system
+        * anchors. (System anchors are normally passed in via 
+        * authCtx.AnchorCerts; they're passed in as DLDBs when
+        * using TrustSettings.)
+        */
+       uint32 totalNumDbs = 0;
+       uint32 numCallerDbs = 0;
+       CSSM_BOOL weOpenedDbs = CSSM_FALSE;
+       if(vfyArgs->dlDbList != NULL) {
+               totalNumDbs = numCallerDbs = vfyArgs->dlDbList->NumHandles;
+       }
+       if(vfyArgs->useTrustSettings && vfyArgs->useSystemAnchors) {
+               /* we'll cook up two more DBs and possible append them */       
+               totalNumDbs += 2;
+               weOpenedDbs = CSSM_TRUE;
+       }
+       CSSM_DL_DB_HANDLE dlDbHandles[totalNumDbs];
+       CSSM_DL_DB_LIST dlDbList;
+       CSSM_DL_HANDLE dlHand = 0;
+       for(unsigned dex=0; dex<numCallerDbs; dex++) {
+               dlDbHandles[dex] = vfyArgs->dlDbList->DLDBHandle[dex];
+       }
+       if(weOpenedDbs) {
+               /* get a DL handle, somehow */
+               if(numCallerDbs == 0) {
+                       /* new DL handle */
+                       dlHand = cuDlStartup();
+                       dlDbHandles[0].DLHandle = dlHand;
+                       dlDbHandles[1].DLHandle = dlHand;
+               }
+               else {
+                       /* use the same one caller passed in */
+                       dlDbHandles[numCallerDbs].DLHandle     = dlDbHandles[0].DLHandle;
+                       dlDbHandles[numCallerDbs + 1].DLHandle = dlDbHandles[0].DLHandle;
+               }
+               /* now open two DBs */
+               dlDbHandles[numCallerDbs].DBHandle = 
+                       cuDbStartupByName(dlDbHandles[numCallerDbs].DLHandle,
+                               (char *)ADMIN_CERT_STORE_PATH, CSSM_FALSE, CSSM_TRUE);
+               dlDbHandles[numCallerDbs + 1].DBHandle = 
+                       cuDbStartupByName(dlDbHandles[numCallerDbs].DLHandle,
+                               (char *)SYSTEM_ROOT_STORE_PATH, CSSM_FALSE, CSSM_TRUE);
+       }
+       dlDbList.DLDBHandle = dlDbHandles;
+       dlDbList.NumHandles = totalNumDbs;
+       authCtx.DBList = &dlDbList;
+
+       CFArrayRef cfAnchors = NULL;
+       CSSM_DATA *cssmAnchors = NULL;
+       unsigned numAnchors = 0;
+
+       if(vfyArgs->useSystemAnchors) {
+               if(!vfyArgs->useTrustSettings) {
+                       /* standard system anchors - ingore error, I'm sure the
+                        * current test will eventually fail */
+                       getSystemAnchors(&cfAnchors, &cssmAnchors, &numAnchors);
+                       authCtx.NumberOfAnchorCerts = numAnchors;
+                       authCtx.AnchorCerts = cssmAnchors;
+               }
+       }
+       else {
+               /* anchors are our caller's roots */
+               if(vfyArgs->roots) {
+                       authCtx.NumberOfAnchorCerts = vfyArgs->roots->numBlobs();
+                       authCtx.AnchorCerts = vfyArgs->roots->blobList();
+               }
+       }
+       authCtx.CallerCredentials = NULL;
+       
+       if(vfyArgs->crls) {
+               /* cook up CRL group */
+               CSSM_CRLGROUP_PTR cssmCrls = &vfyCtx.Crls;
+               cssmCrls->CrlType = CSSM_CRL_TYPE_X_509v1;
+               cssmCrls->CrlEncoding = CSSM_CRL_ENCODING_DER;
+               cssmCrls->NumberOfCrls = vfyArgs->crls->numBlobs();
+               cssmCrls->GroupCrlList.CrlList = vfyArgs->crls->blobList();
+               cssmCrls->CrlGroupType = CSSM_CRLGROUP_DATA;
+       }
+       
+       /* CSSM_APPLE_TP_ACTION_DATA */
+       CSSM_APPLE_TP_ACTION_DATA tpAction;
+       tpAction.Version = CSSM_APPLE_TP_ACTION_VERSION;
+       tpAction.ActionFlags = 0;
+       if(vfyArgs->leafCertIsCA) {
+               tpAction.ActionFlags |= CSSM_TP_ACTION_LEAF_IS_CA;
+       }
+       if(vfyArgs->certNetFetchEnable) {
+               tpAction.ActionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET;
+       }
+       if(vfyArgs->allowExpiredRoot) {
+               tpAction.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED_ROOT;
+       }
+       if(!vfyArgs->allowUnverified) {
+               tpAction.ActionFlags |= CSSM_TP_ACTION_REQUIRE_REV_PER_CERT;
+       }
+       if(vfyArgs->useTrustSettings) {
+               tpAction.ActionFlags |= CSSM_TP_ACTION_TRUST_SETTINGS;
+       }
+       if(vfyArgs->implicitAnchors) {
+               tpAction.ActionFlags |= CSSM_TP_ACTION_IMPLICIT_ANCHORS;
+       }
+       
+       /* CSSM_TP_VERIFY_CONTEXT */
+       vfyCtx.ActionData.Data   = (uint8 *)&tpAction;
+       vfyCtx.ActionData.Length = sizeof(tpAction);
+
+       vfyCtx.Action = CSSM_TP_ACTION_DEFAULT;
+       vfyCtx.Cred = &authCtx;
+       
+       /* cook up cert group */
+       CSSM_CERTGROUP cssmCerts;
+       cssmCerts.CertType = CSSM_CERT_X_509v3;
+       cssmCerts.CertEncoding = CSSM_CERT_ENCODING_DER;
+       cssmCerts.NumCerts = vfyArgs->certs->numBlobs();
+       cssmCerts.GroupList.CertList = vfyArgs->certs->blobList();
+       cssmCerts.CertGroupType = CSSM_CERTGROUP_DATA;
+       
+       int ourRtn = 0;
+       CSSM_RETURN crtn = CSSM_TP_CertGroupVerify(vfyArgs->tpHand,
+               vfyArgs->clHand,
+               vfyArgs->cspHand,
+               &cssmCerts,
+               &vfyCtx,
+               &vfyResult);
+       if(vfyArgs->expectedErrStr != NULL) {
+               const char *actRtn;
+               if(crtn == CSSM_OK) {
+                       /* cssmErrorString munges this to "[ok]" */
+                       actRtn = "CSSM_OK";
+               }
+               else {
+                       actRtn = cssmErrToStr(crtn);
+               }
+               char *found = strstr(actRtn, vfyArgs->expectedErrStr);
+               if(found) {
+                       if(!vfyArgs->quiet) {
+                               printf("...%s received as expected\n", vfyArgs->expectedErrStr);
+                       }
+               }
+               else {
+                       printf("***CSSM_TP_CertGroupVerify error\n");
+                       printf("   expected rtn : %s\n", vfyArgs->expectedErrStr);
+                       printf("     actual rtn : %s\n", actRtn);
+                       ourRtn = 1;
+               }
+       }
+       else {
+               if(crtn) {
+                       if(!vfyArgs->quiet) {
+                               printError("CSSM_TP_CertGroupVerify", crtn);
+                       }
+                       ourRtn = 1;
+               }
+               else if(!vfyArgs->quiet) {
+                       printf("...verify successful\n");
+               }
+       }
+       if(vfyArgs->certErrors) {
+               if(vfyCertErrors(&vfyResult, vfyArgs->numCertErrors, vfyArgs->certErrors, 
+                               vfyArgs->quiet)) {
+                       ourRtn = 1;
+               }
+       }
+       if(vfyArgs->certStatus) {
+               if(vfyCertStatus(&vfyResult, vfyArgs->numCertStatus, vfyArgs->certStatus,
+                               vfyArgs->quiet)) {
+                       ourRtn = 1;
+               }
+       }
+       if(vfyArgs->verbose) {
+               dumpVfyResult(&vfyResult);
+       }
+       freeVfyResult(&vfyResult);
+       if(weOpenedDbs) {
+               /* close the DBs and maybe the DL we opened */
+               CSSM_DL_DbClose(dlDbHandles[numCallerDbs]);
+               CSSM_DL_DbClose(dlDbHandles[numCallerDbs + 1]);
+               if(dlHand != 0) {
+                       cuDlDetachUnload(dlHand);
+               }
+       }
+       if(cfAnchors) {
+               CFRelease(cfAnchors);
+       }
+       if(cssmAnchors) {
+               free(cssmAnchors);
+       }
+       return ourRtn;
+}
+
+unsigned hexDigit(char digit)
+{
+       if((digit >= '0') && (digit <= '9')) {
+               return digit - '0';
+       }
+       if((digit >= 'a') && (digit <= 'f')) {
+               return 10 + digit - 'a';
+       }
+       if((digit >= 'A') && (digit <= 'F')) {
+               return 10 + digit - 'A';
+       }
+       printf("***BAD HEX DIGIT (%c)\n", digit);
+       return 0;
+}
+
+/* convert ASCII string in hex to unsigned */
+unsigned hexToBin(const char *hex)
+{
+       unsigned rtn = 0;
+       const char *cp = hex;
+       if((cp[0] == '0') && (cp[1] == 'x')) {
+               cp += 2;
+       }
+       if(strlen(cp) > 8) {
+               printf("***BAD HEX STRING (%s)\n", cp);
+               return 0;
+       }
+       while(*cp) {
+               rtn <<= 4;
+               rtn += hexDigit(*cp);
+               cp++;
+       }
+       return rtn;
+}
+
+/*
+ * A slightly simplified version of certVerify: 
+ *             -- no CRLs (includes allowUnverified = CSSM_FALSE)
+ *             -- revokePOlicy = None
+ *             -- no DlDbs
+ *             -- no net fetch
+ *             -- time = now. 
+ */
+int certVerifySimple(
+       CSSM_TP_HANDLE                  tpHand, 
+       CSSM_CL_HANDLE                  clHand,
+       CSSM_CSP_HANDLE                 cspHand,
+       BlobList                                &certs,
+       BlobList                                &roots,
+       CSSM_BOOL                               useSystemAnchors,
+       CSSM_BOOL                               leafCertIsCA,
+       CSSM_BOOL                               allowExpiredRoot,
+       CertVerifyPolicy                vfyPolicy,
+       const char                              *sslHost,               // optional, SSL policy
+       CSSM_BOOL                               sslClient,              // normally server side
+       const char                              *senderEmail,   // optional, SMIME
+       CE_KeyUsage                             intendedKeyUse, // optional, SMIME only
+       const char                              *expectedErrStr,// e.g.,
+                                                                                       // "CSSMERR_APPLETP_CRL_NOT_TRUSTED"
+                               
+       /* 
+        * expected per-cert errors
+        * format is certNum:errorString
+        * e.g., "1:CSSMERR_APPLETP_CRL_NOT_TRUSTED"
+        */
+       unsigned                                numCertErrors,
+       const char                              **certErrors,   // per-cert status
+       
+       /*
+        * Expected per-cert status (CSSM_TP_APPLE_EVIDENCE_INFO.StatusBits)
+        * format is certNum:status_in_hex
+        * e.g., "1:0x18", leading 0x optional
+        */
+       unsigned                                numCertStatus,
+       const char                              **certStatus,
+       CSSM_BOOL                               useTrustSettings,
+       CSSM_BOOL                               quiet,
+       CSSM_BOOL                               verbose)
+{
+       CertVerifyArgs vfyArgs;
+       memset(&vfyArgs, 0, sizeof(vfyArgs));
+       vfyArgs.version = CERT_VFY_ARGS_VERS;
+       vfyArgs.tpHand = tpHand;
+       vfyArgs.clHand = clHand;
+       vfyArgs.cspHand = cspHand;
+       vfyArgs.certs = &certs;
+       vfyArgs.roots = &roots;
+       vfyArgs.useSystemAnchors = useSystemAnchors;
+       vfyArgs.useTrustSettings = useTrustSettings;
+       vfyArgs.leafCertIsCA = leafCertIsCA;
+       vfyArgs.allowExpiredRoot = allowExpiredRoot;
+       vfyArgs.vfyPolicy = vfyPolicy;
+       vfyArgs.sslHost = sslHost;
+       vfyArgs.sslClient = sslClient;
+       vfyArgs.senderEmail = senderEmail;
+       vfyArgs.intendedKeyUse = intendedKeyUse;
+       vfyArgs.allowUnverified = CSSM_TRUE;
+       vfyArgs.expectedErrStr = expectedErrStr;
+       vfyArgs.numCertErrors = numCertErrors;
+       vfyArgs.certErrors = certErrors;
+       vfyArgs.numCertStatus = numCertStatus;
+       vfyArgs.certStatus = certStatus;
+       vfyArgs.quiet = quiet;
+       vfyArgs.verbose = verbose;
+       return certVerify(&vfyArgs);
+}
+