vfyCacCert.cpp   [plain text]


/*
 * Verify one CAC cert using standard system-wide cert and CRL keychains.
 */
#include <stdlib.h>
#include <stdio.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <Security/cuCdsaUtils.h>	/* private */
//#include "clutils.h"
#include <Security/cssmerrno.h> 	/* private cssmErrorString() */
#include <Security/cssm.h>
#include <Security/oidsalg.h>
#include <Security/SecTrust.h>

/* 
 * More-or-less hard-coded locations of system-wide keychains containing
 * intermediate certs (which are required for this operation) and
 * CRLs (which is optional; it's just a cache for performance reasons).
 */
#define X509_CERT_DB	"/System/Library/Keychains/X509Certificates"
#define X509_CRL_DB		"/private/var/db/crls/crlcache.db"

static void usage(char **argv)
{
	printf("Usage: %s certFileName [options]\n", argv[0]);
	printf("Options:\n");
	printf("   a   allow unverified certs\n");
	printf("   d   disable CRL verification\n");
	printf("   n   no network fetch of CRLs\n");
	exit(1);
}

/*** Display verify results ***/

static void statusBitTest(
	CSSM_TP_APPLE_CERT_STATUS certStatus, 
	uint32 bit,
	const char *str)
{
	if(certStatus & bit) {
		printf("%s  ", str);
	}
}

static void printCertInfo(
	unsigned numCerts,							// from CertGroup
	const CSSM_TP_APPLE_EVIDENCE_INFO *info)
{
	CSSM_TP_APPLE_CERT_STATUS cs;
	
	for(unsigned i=0; i<numCerts; i++) {
		const CSSM_TP_APPLE_EVIDENCE_INFO *thisInfo = &info[i];
		cs = thisInfo->StatusBits;
		printf("   cert %u:\n", i);
		printf("      StatusBits     : 0x%x", (unsigned)cs);
		if(cs) {
			printf(" ( ");
			statusBitTest(cs, CSSM_CERT_STATUS_EXPIRED, "EXPIRED");
			statusBitTest(cs, CSSM_CERT_STATUS_NOT_VALID_YET, 
				"NOT_VALID_YET");
			statusBitTest(cs, CSSM_CERT_STATUS_IS_IN_INPUT_CERTS, 
				"IS_IN_INPUT_CERTS");
			statusBitTest(cs, CSSM_CERT_STATUS_IS_IN_ANCHORS, 
				"IS_IN_ANCHORS");
			statusBitTest(cs, CSSM_CERT_STATUS_IS_ROOT, "IS_ROOT");
			statusBitTest(cs, CSSM_CERT_STATUS_IS_FROM_NET, "IS_FROM_NET");
			printf(")\n");
		}
		else {
			printf("\n");
		}
		printf("      NumStatusCodes : %u ",
			thisInfo->NumStatusCodes);
		for(unsigned j=0; j<thisInfo->NumStatusCodes; j++) {
			printf("%s  ", 
				cssmErrorString(thisInfo->StatusCodes[j]).c_str());
		}
		printf("\n");
		printf("      Index: %u\n", thisInfo->Index);
	}
	return;
}

/* we really only need CSSM_EVIDENCE_FORM_APPLE_CERT_INFO */
#define SHOW_ALL_VFY_RESULTS		0

static void dumpVfyResult(
	const CSSM_TP_VERIFY_CONTEXT_RESULT *vfyResult)
{
	unsigned numEvidences = vfyResult->NumberOfEvidences;
	unsigned numCerts = 0;
	printf("Returned evidence:\n");
	for(unsigned dex=0; dex<numEvidences; dex++) {
		CSSM_EVIDENCE_PTR ev = &vfyResult->Evidence[dex];
		#if SHOW_ALL_VFY_RESULTS
		printf("   Evidence %u:\n", dex);
		#endif
		switch(ev->EvidenceForm) {
			case CSSM_EVIDENCE_FORM_APPLE_HEADER:
			{
				#if SHOW_ALL_VFY_RESULTS
				const CSSM_TP_APPLE_EVIDENCE_HEADER *hdr = 
					(const CSSM_TP_APPLE_EVIDENCE_HEADER *)(ev->Evidence);
				printf("      Form = HEADER; Version = %u\n", hdr->Version);
				#endif
				break;
			}
			case CSSM_EVIDENCE_FORM_APPLE_CERTGROUP:
			{
				const CSSM_CERTGROUP *grp = (const CSSM_CERTGROUP *)ev->Evidence;
				numCerts = grp->NumCerts;
				#if SHOW_ALL_VFY_RESULTS
				/* parse the rest of this eventually */
				/* Note we depend on this coming before the CERT_INFO */
				printf("      Form = CERTGROUP; numCerts = %u\n", numCerts);
				#endif
				break;
			}
			case CSSM_EVIDENCE_FORM_APPLE_CERT_INFO:	
			{
				const CSSM_TP_APPLE_EVIDENCE_INFO *info = 
					(const CSSM_TP_APPLE_EVIDENCE_INFO *)ev->Evidence;
				printCertInfo(numCerts, info);
				break;
			}
			default:
				printf("***UNKNOWN Evidence form (%u)\n", 
					(unsigned)ev->EvidenceForm);
				break;
		}
	}
}

/* free a CSSM_CERT_GROUP */ 
void tpFreeCertGroup(
	CSSM_CERTGROUP_PTR	certGroup,
	CSSM_BOOL	 		freeCertData,	// free individual CertList.Data 
	CSSM_BOOL			freeStruct)		// free the overall CSSM_CERTGROUP
{
	unsigned dex;
	
	if(certGroup == NULL) {
		return;	
	}
	
	if(freeCertData) {
		/* free the individual cert Data fields */
		for(dex=0; dex<certGroup->NumCerts; dex++) {
			APP_FREE(certGroup->GroupList.CertList[dex].Data);
		}
	}

	/* and the array of CSSM_DATAs */
	if(certGroup->GroupList.CertList) {
		APP_FREE(certGroup->GroupList.CertList);
	}
	
	if(freeStruct) {
		APP_FREE(certGroup);
	}
}


/*
 * Free the contents of a CSSM_TP_VERIFY_CONTEXT_RESULT returned from
 * CSSM_TP_CertGroupVerify().
 */
CSSM_RETURN freeVfyResult(
	CSSM_TP_VERIFY_CONTEXT_RESULT *ctx)
{
	int numCerts = -1;
	CSSM_RETURN crtn = CSSM_OK;
	
	for(unsigned i=0; i<ctx->NumberOfEvidences; i++) {
		CSSM_EVIDENCE_PTR evp = &ctx->Evidence[i];
		switch(evp->EvidenceForm) {
			case CSSM_EVIDENCE_FORM_APPLE_HEADER:
				/* Evidence = (CSSM_TP_APPLE_EVIDENCE_HEADER *) */
				APP_FREE(evp->Evidence);
				evp->Evidence = NULL;
				break;
			case CSSM_EVIDENCE_FORM_APPLE_CERTGROUP:
			{
				/* Evidence = CSSM_CERTGROUP_PTR */
				CSSM_CERTGROUP_PTR cgp = (CSSM_CERTGROUP_PTR)evp->Evidence;
				numCerts = cgp->NumCerts;	
				tpFreeCertGroup(cgp, CSSM_TRUE, CSSM_TRUE);
				evp->Evidence = NULL;
				break;
			}
			case CSSM_EVIDENCE_FORM_APPLE_CERT_INFO:
			{
				/* Evidence = array of CSSM_TP_APPLE_EVIDENCE_INFO */
				if(numCerts < 0) {
					/* Haven't gotten a CSSM_CERTGROUP_PTR! */
					printf("***Malformed VerifyContextResult (2)\n");
					crtn = CSSMERR_TP_INTERNAL_ERROR;
					break;
				}
				CSSM_TP_APPLE_EVIDENCE_INFO *evInfo = 
					(CSSM_TP_APPLE_EVIDENCE_INFO *)evp->Evidence;
				for(unsigned k=0; k<(unsigned)numCerts; k++) {
					/* Dispose of StatusCodes, UniqueRecord */
					CSSM_TP_APPLE_EVIDENCE_INFO *thisEvInfo = 
						&evInfo[k];
					if(thisEvInfo->StatusCodes) {
						APP_FREE(thisEvInfo->StatusCodes);
					}
					if(thisEvInfo->UniqueRecord) {
						CSSM_RETURN crtn = 
							CSSM_DL_FreeUniqueRecord(thisEvInfo->DlDbHandle,
								thisEvInfo->UniqueRecord);
						if(crtn) {
							cuPrintError("CSSM_DL_FreeUniqueRecord", crtn);
							break;
						}
						thisEvInfo->UniqueRecord = NULL;
					}
				}	/* for each cert info */
				APP_FREE(evp->Evidence);
				evp->Evidence = NULL;
				break;
			}	/* CSSM_EVIDENCE_FORM_APPLE_CERT_INFO */
		}		/* switch(evp->EvidenceForm) */
	}			/* for each evidence */
	if(ctx->Evidence) {
		APP_FREE(ctx->Evidence);
		ctx->Evidence = NULL;
	}
	return crtn;
}

static int testError(CSSM_BOOL quiet)
{
	char resp;
	
	if(quiet) {
		printf("\n***Test aborting.\n");
		exit(1);
	}
	fpurge(stdin);
	printf("a to abort, c to continue: ");
	resp = getchar();
	return (resp == 'a');
}

/*
 * The heart of CAC certification verification.
 */
int vfyCert(
	CSSM_TP_HANDLE			tpHand, 
	CSSM_CL_HANDLE 			clHand,
	CSSM_CSP_HANDLE 		cspHand,
	const void *			certData,
	unsigned				certLength,
	
	/* these three booleans will eventually come from system preferences */
	bool					enableCrlCheck,
	bool					requireFullCrlVerify,
	bool					enableFetchFromNet,
	CSSM_DL_DB_HANDLE_PTR	certKeychain,
	CSSM_DL_DB_HANDLE_PTR	crlKeychain)
{
	/* 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;
	
	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;
	*/
	/* two policies */
	CSSM_FIELD	policyIds[2];
	CSSM_APPLE_TP_CRL_OPTIONS crlOpts;
	policyIds[0].FieldOid = CSSMOID_APPLE_X509_BASIC;
	policyIds[0].FieldValue.Data = NULL;
	policyIds[0].FieldValue.Length = 0;
	if(enableCrlCheck) {
		policyIds[1].FieldOid = CSSMOID_APPLE_TP_REVOCATION_CRL;
		policyIds[1].FieldValue.Data = (uint8 *)&crlOpts;
		policyIds[1].FieldValue.Length = sizeof(crlOpts);
		crlOpts.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
		crlOpts.CrlFlags = 0;
		if(requireFullCrlVerify) {
			crlOpts.CrlFlags |= CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT;
		}
		if(enableFetchFromNet) {
			crlOpts.CrlFlags |= CSSM_TP_ACTION_FETCH_CRL_FROM_NET;
		}
		/* optional, may well not exist */
		/* FIXME: as of 12/4/02 this field is ignored by the TP
		 * and may well go away from the CSSM_APPLE_TP_CRL_OPTIONS
		 * struct. */
		crlOpts.crlStore = crlKeychain;
	
		authCtx.Policy.NumberOfPolicyIds = 2;
	}
	else {
		/* No CRL checking */
		authCtx.Policy.NumberOfPolicyIds = 1;
	}
	authCtx.Policy.PolicyIds = policyIds;
	authCtx.Policy.PolicyControl = NULL;
	
	authCtx.VerifyTime = NULL;
	authCtx.VerificationAbortOn = CSSM_TP_STOP_ON_POLICY;
	authCtx.CallbackWithVerifiedCert = NULL;
	
	/* anchors */
	const CSSM_DATA *anchors;
	uint32 anchorCount;
	OSStatus ortn;
	ortn = SecTrustGetCSSMAnchorCertificates(&anchors, &anchorCount);
	if(ortn) {
		printf("SecTrustGetCSSMAnchorCertificates returned %u\n", ortn);
		return -1;
	}
	authCtx.NumberOfAnchorCerts = anchorCount;
	authCtx.AnchorCerts = const_cast<CSSM_DATA_PTR>(anchors);
	
	/* DBList of intermediate certs and CRLs */
	CSSM_DL_DB_HANDLE handles[2];
	unsigned numDbs = 0;
	if(certKeychain != NULL) {
		handles[0] = *certKeychain;
		numDbs++;
	}
	if(crlKeychain != NULL) {
		handles[numDbs] = *crlKeychain;
		numDbs++;
	}
	CSSM_DL_DB_LIST dlDbList;
	dlDbList.NumHandles = numDbs;
	dlDbList.DLDBHandle = &handles[0];
	
	authCtx.DBList = &dlDbList; 
	authCtx.CallerCredentials = NULL;
	
	/* CSSM_TP_VERIFY_CONTEXT */
	vfyCtx.ActionData.Data = NULL;
	vfyCtx.ActionData.Length = 0;
	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 = 1;
	CSSM_DATA cert;
	cert.Data = (uint8 *)certData;
	cert.Length = certLength;
	cssmCerts.GroupList.CertList = &cert;
	cssmCerts.CertGroupType = CSSM_CERTGROUP_DATA;
	
	CSSM_RETURN crtn = CSSM_TP_CertGroupVerify(tpHand,
		clHand,
		cspHand,
		&cssmCerts,
		&vfyCtx,
		&vfyResult);
	if(crtn) {
		cuPrintError("CSSM_TP_CertGroupVerify", crtn);
	}
	else {
		printf("...verify successful\n");
	}
	dumpVfyResult(&vfyResult);
	freeVfyResult(&vfyResult);
	if(crtn) {
		return testError(0);
	}
	else {
		return 0;
	}
}

int main(int argc, char **argv)
{
	int rtn;
	CSSM_RETURN crtn;
	CSSM_DL_HANDLE dlHand;
	CSSM_CSP_HANDLE cspHand;
	CSSM_CL_HANDLE clHand;
	CSSM_TP_HANDLE tpHand;
	CSSM_DL_DB_HANDLE certKeychain;
	CSSM_DL_DB_HANDLE crlKeychain;
	CSSM_DL_DB_HANDLE_PTR certKeychainPtr = NULL;
	CSSM_DL_DB_HANDLE_PTR crlKeychainPtr = NULL;
	unsigned char *certData;
	unsigned certLength;
	bool requireFullCrlVerify = true;
	bool enableFetchFromNet = true;
	bool enableCrlCheck = true;
	int arg;
	char *argp;
	
	if(argc < 2) {
		usage(argv);
	}
	for(arg=2; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'a':
				requireFullCrlVerify = false;
				break;
			case 'd':
				enableCrlCheck = false;
				break;
			case 'n':
				enableFetchFromNet = false;
				break;
			default:
				usage(argv);
		}
	}
	
	/* in the real world all these should be verified to be nonzero */
	cspHand = cuCspStartup(CSSM_TRUE);
	clHand = cuClStartup();
	tpHand = cuTpStartup();
	dlHand = cuDlStartup();
	
	/* get the cert */
	rtn = readFile(argv[1], &certData, &certLength);
	if(rtn) {
		printf("***Error reading cert file %s\n", argv[1]);
		exit(1);
	}
	
	/* get the intermediate certs */
	crtn = CSSM_DL_DbOpen(dlHand,
		X509_CERT_DB, 
		NULL,			// DbLocation
		CSSM_DB_ACCESS_READ,
		NULL, 			// CSSM_ACCESS_CREDENTIALS *AccessCred
		NULL,			// void *OpenParameters
		&certKeychain.DBHandle);
	if(crtn) {
		cuPrintError("CSSM_DL_DbOpen", crtn);
		printf("***Error opening intermediate cert file %s. This op will"
			"probably fail.n", X509_CERT_DB);
	}
	else {
		certKeychain.DLHandle = dlHand;
		certKeychainPtr = &certKeychain;
	}
	
	/* and the CRL cache */
	crtn = CSSM_DL_DbOpen(dlHand,
		X509_CRL_DB, 
		NULL,			// DbLocation
		CSSM_DB_ACCESS_READ,
		NULL, 			// CSSM_ACCESS_CREDENTIALS *AccessCred
		NULL,			// void *OpenParameters
		&crlKeychain.DBHandle);
	if(crtn == CSSM_OK) {
		crlKeychain.DLHandle = dlHand;
		crlKeychainPtr = &crlKeychain;
	}
	
	/* go for it */
	vfyCert(tpHand, clHand, cspHand, certData, certLength, 
		enableCrlCheck, requireFullCrlVerify, enableFetchFromNet,
		certKeychainPtr, crlKeychainPtr);
		
	/* Cleanup - release handles, free certData, etc. */
	free(certData);
	if(crlKeychainPtr != NULL) {
		CSSM_DL_DbClose(crlKeychain);
	}
	if(certKeychainPtr != NULL) {
		CSSM_DL_DbClose(certKeychain);
	}
	CSSM_ModuleDetach(dlHand);
	CSSM_ModuleDetach(clHand);
	CSSM_ModuleDetach(tpHand);
	CSSM_ModuleDetach(cspHand);
	
	return 0;

}