secTime.cpp   [plain text]


/*
 * secTime.cpp - measure performance of Sec* ops
 */
 
#include <Security/Security.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <CoreFoundation/CoreFoundation.h>
#include <security_utilities/devrandom.h>
#include <clAppUtils/certVerify.h>
#include <clAppUtils/clutils.h>
#include <utilLib/common.h>

/*
 * Hard coded test params
 */
 
/* 
 * The keychain we open and search for certs.
 * MUST exist in ~/Library/Keychains.
 * YOU can create it with 
 *  % certtool c k=secTimeKc c p=secTimeKc Z
 */
#define ST_KC_NAME		"secTimeKc"

/*
 * Certs to verify with SecTrust. We have a variety because the timing on
 * this test is highly dependent on the position of the verifying anchor
 * within the system anchors list. (This does not apply to Trust Settings). 
 */
typedef struct {
	const char *certFileName;
	const char *hostName;
} CertToVerify;

static const CertToVerify certsToVerify[] = 
{
	{ 
		/* issuer: Secure Server Certification Authority */
		"amazon_v3.100.cer",
		"www.amazon.com"
	},
	{
		/* issuer: Equifax Secure Certificate Authority */
		"firstamlink.cer",		
		"www.firstamlink.com"
	},
};

#define NUM_ST_CERTS	(sizeof(certsToVerify) / sizeof(certsToVerify[0]))

/* explicit anchor for certsToVerify[0] */
#define ST_ANCHOR_NAME	"SecureServer.509.cer"		/* verifies ST_CERT_HOST */

/*
 * Cert chain for cgv3*()
 */
#define THAWTE_LEAF	"dmitchThawte.cer"
#define THAWTE_CA	"ThawteCA.cer"
#define THAWTE_ROOT	"ThawteRoot.cer"

static void usage(char **argv)
{
    printf("Usage: %s [option ...]\n", argv[0]);
    printf("Options:\n");
	printf("   t=testspec; default=all\n");
	printf("     test specs: o keychainOpen\n");
	printf("                 s keychainSearch\n");
	printf("                 e secTrustEvaluate\n");
	printf("                 k SecKeychainCopySearchList\n");
	printf("                 v TP CertGroupVerify, system anchor\n");
	printf("                 V TP CertGroupVerify, explicit anchor\n");
	printf("                 3 TP CertGroupVerify, 3 certs w/anchor\n");
	printf("   l=loops (only valid if testspec is given)\n");
	exit(1);
}

static void printSecErr(
	const char *op,
	OSStatus ortn)
{
	printf("%s returned %ld\n", op, (unsigned long)ortn);
}

/*
 * Struct passed around to test-specific functions.
 */
typedef struct {
	const char 			*testName;
	void				*testPriv;
} TestParams;

/*
 * Each subtest has three functions - init, run, cleanup, called out 
 * from main().
 *
 * Init's job is one-time setup - open files, setup buffers, etc. 
 * PErsistent state can be saved in TestParams->testPriv.
 */
typedef OSStatus (*testInitFcn)(TestParams *testParams);

/*
 * Run's job is to perform 1 iteration with as 
 * little overhead as possible. Return the time spect actually
 * doing the deed in *timeSpent.
 */
typedef OSStatus (*testRunFcn)(TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent);
	
/*
 * CLeanu up any resources allocd in init.
 */
typedef OSStatus (*testCleanupFcn)(TestParams *testParams);

/*
 * Static declaration of a test
 */
typedef struct {
	const char 			*testName;
	unsigned			loops;
	testInitFcn			init;
	testRunFcn			run;
	testCleanupFcn		cleanup;
	char				testSpec;		// for t=xxx cmd line opt
} TestDefs;

#pragma mark ---- Individual tests ----

#ifdef	use_these_as_a_template

static OSStatus xxxInit(
	TestParams *testParams)
{
	return noErr;
}

static OSStatus xxRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	/* do the op here */
	*timeSpent = CFAbsoluteTimeGetCurrent() - startTime;
	return noErr;
}

static OSStatus xxxCleanup(
	TestParams *testParams)
{
	return noErr;
}
#endif	/* template */

#pragma mark -- keychain open --

static OSStatus kcOpenInit(
	TestParams *testParams)
{
	return noErr;
}

static OSStatus kcOpenRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	SecKeychainRef kcRef;
	SecKeychainStatus status;
	
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	OSStatus ortn = SecKeychainOpen(ST_KC_NAME, &kcRef);
	if(ortn) {
		printSecErr("SecKeychainOpen", ortn);
		return ortn;
	}
	ortn = SecKeychainGetStatus(kcRef, &status);
	if(ortn) {
		printSecErr("SecKeychainGetStatus", ortn);
		CFRelease(kcRef);
		if(errSecNoSuchKeychain == ortn) {
			printf("The keychain %s does not exist. Please create it"
				" and populate it like so:\n", ST_KC_NAME);
			printf(" certtool c k=%s c p=%s Z\n",
				ST_KC_NAME, ST_KC_NAME);
		}
		return ortn;
	}
	CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
	*timeSpent = endTime - startTime;
	CFRelease(kcRef);
	return noErr;
}

static OSStatus kcOpenCleanup(
	TestParams *testParams)
{
	return noErr;
}

#pragma mark -- keychain lookup --

/* 
 * Private *testPriv is a kcRef.
 */
static OSStatus kcSearchInit(
	TestParams *testParams)
{
	SecKeychainRef kcRef;
	OSStatus ortn = SecKeychainOpen(ST_KC_NAME, &kcRef);
	if(ortn) {
		printSecErr("SecKeychainOpen", ortn);
		return ortn;
	}
	testParams->testPriv = (void *)kcRef;
	return noErr;
}

static OSStatus kcSearchRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	SecKeychainRef kcRef = (SecKeychainRef)testParams->testPriv;
	SecKeychainSearchRef srchRef = NULL;
	SecKeychainItemRef certRef = NULL;
	CFAbsoluteTime endTime;
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	
	/* search for any cert */
	OSStatus ortn = SecKeychainSearchCreateFromAttributes(kcRef,
		kSecCertificateItemClass,
		NULL,		// no attrs
		&srchRef);
	if(ortn) {
		printSecErr("SecKeychainSearchCreateFromAttributes", ortn);
		return ortn;
	}
	ortn = SecKeychainSearchCopyNext(srchRef, &certRef);
	if(ortn) {
		printSecErr("SecKeychainSearchCopyNext", ortn);
		goto done;
	}
	endTime = CFAbsoluteTimeGetCurrent();
	*timeSpent = endTime - startTime;
done:
	if(srchRef) {
		CFRelease(srchRef);
	}
	if(certRef) {
		CFRelease(certRef);
	}
	return ortn;
}


static OSStatus kcSearchCleanup(
	TestParams *testParams)
{
	SecKeychainRef kcRef = (SecKeychainRef)testParams->testPriv;
	CFRelease(kcRef);
	return noErr;
}

#pragma mark -- SecTrustEvaluate --

/* 
 * Priv data is an array of SecCertificateRef containing certs to evaluate. 
 * Each run evaluates one of them.
 */
static OSStatus secTrustInit(
	TestParams *testParams)
{
	unsigned char *certData;
	unsigned certLen;
	CSSM_DATA cdata;
	
	SecCertificateRef *certRefs;
	
	certRefs = (SecCertificateRef *)malloc(
		sizeof(SecCertificateRef) * NUM_ST_CERTS);
		
	for(unsigned dex=0; dex<NUM_ST_CERTS; dex++) {
		if(readFile(certsToVerify[dex].certFileName, &certData, &certLen)) {
			printf("***Can not find cert file %s. Aborting.\n",
				certsToVerify[dex].certFileName);
			return -1;
		}
		cdata.Data = certData;
		cdata.Length = certLen;
		SecCertificateRef certRef;
		OSStatus ortn = SecCertificateCreateFromData(&cdata,
			CSSM_CERT_X_509v3,
			CSSM_CERT_ENCODING_DER, 
			&certRef);
		if(ortn) {
			printSecErr("SecCertificateCreateFromData", ortn);
			return ortn;
		}
		free(certData);			// mallocd by readFile() 
		certRefs[dex] = certRef;
	}
	testParams->testPriv = certRefs;
	return noErr;
}

static OSStatus secTrustRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	unsigned whichDex = loopNum % NUM_ST_CERTS;
	SecCertificateRef *certRefs = (SecCertificateRef *)testParams->testPriv;
	SecCertificateRef certRef = certRefs[whichDex];
	
	/* 
	 * We measure the whole enchilada, that's what SecureTransport
	 * has to do
	 */
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

	CFMutableArrayRef certs;
	certs = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
	CFArrayInsertValueAtIndex(certs, 0, certRef);

	SecPolicyRef		policy = NULL;
	SecPolicySearchRef	policySearch = NULL;

	OSStatus ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
		&CSSMOID_APPLE_TP_SSL,
		NULL,				// policy opts
		&policySearch);
	if(ortn) {
		printSecErr("SecPolicySearchCreate", ortn);
		return ortn;
	}
	
	ortn = SecPolicySearchCopyNext(policySearch, &policy);
	if(ortn) {
		printSecErr("SecPolicySearchCopyNext", ortn);
		return ortn;
	}
	CFRelease(policySearch);
	
	SecTrustRef secTrust;
	ortn = SecTrustCreateWithCertificates(certs, policy, &secTrust);
	if(ortn) {
		printSecErr("SecTrustCreateWithCertificates", ortn);
		return ortn;
	}
	/* no action data for now */

	SecTrustResultType secTrustResult;
	ortn = SecTrustEvaluate(secTrust, &secTrustResult);
	if(ortn) {
		printSecErr("SecTrustEvaluate", ortn);
		return ortn;
	}
	
	CFRelease(certs);
	CFRelease(secTrust);
	CFRelease(policy);
	
	CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
	*timeSpent = endTime - startTime;

	return noErr;
}

static OSStatus secTrustCleanup(
	TestParams *testParams)
{
	SecCertificateRef *certRefs = (SecCertificateRef*)testParams->testPriv;
	for(unsigned dex=0; dex<NUM_ST_CERTS; dex++) {
		CFRelease(certRefs[dex]);
	}
	free(certRefs);
	return noErr;
}

#pragma mark -- SecKeychainCopySearchList --

static OSStatus kcCSLInit(
	TestParams *testParams)
{
	return noErr;
}

static OSStatus kcCSLRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	CFArrayRef sl;
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	OSStatus ortn = SecKeychainCopySearchList(&sl);
	if(ortn) {
		printSecErr("SecKeychainCopySearchList", ortn);
		return ortn;
	}
	CFRelease(sl);
	*timeSpent = CFAbsoluteTimeGetCurrent() - startTime;
	return noErr;
}

static OSStatus kcCSLCleanup(
	TestParams *testParams)
{
	return noErr;
}

#pragma mark -- CSSM_TP_CertGroupVerify, system anchors --

/* private data allocated in cgvInit */
typedef struct {
	CSSM_TP_HANDLE			tpHand;
	CSSM_CL_HANDLE 			clHand;
	CSSM_CSP_HANDLE 		cspHand;
	BlobList				*certs[NUM_ST_CERTS];
	BlobList				*anchors;		/* cgvAnchor* only */
} CgvParams;

static OSStatus cgvInit(
	TestParams *testParams)
{
	CgvParams *cgvParams = (CgvParams *)malloc(sizeof(CgvParams));
	memset(cgvParams, 0, sizeof(CgvParams));
	cgvParams->tpHand    = tpStartup();
	cgvParams->clHand    = clStartup();
	cgvParams->cspHand   = cspStartup();
	for(unsigned dex=0; dex<NUM_ST_CERTS; dex++) {
		cgvParams->certs[dex] = new BlobList();
		cgvParams->certs[dex]->addFile(certsToVerify[dex].certFileName);
	}
	cgvParams->anchors   = NULL;
	testParams->testPriv = cgvParams;
	return noErr;
}

static OSStatus cgvRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	CgvParams *cgvParams = (CgvParams *)testParams->testPriv;
	BlobList nullList;
	unsigned whichDex = loopNum % NUM_ST_CERTS;
	BlobList *certBlob = cgvParams->certs[whichDex];
	
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	int rtn = certVerifySimple(
		cgvParams->tpHand,
		cgvParams->clHand,
		cgvParams->cspHand,
		*certBlob,				// contains one cert, the subject
		nullList,				// roots
		CSSM_TRUE,				// useSystemAnchors
		CSSM_FALSE,				// leafCertIsCa
		CSSM_FALSE,				// allow expired root
		CVP_SSL,
		certsToVerify[whichDex].hostName,
		CSSM_FALSE,				// sslClient
		NULL,					// senderEmail
		0,						// key use
		NULL,					// expected error str
		0,						// numCertErrors
		NULL,
		0,						// numCertStatus
		NULL,
		CSSM_FALSE,				// trustSettings
		CSSM_TRUE,				// quiet
		CSSM_FALSE);			// verbose
	*timeSpent = CFAbsoluteTimeGetCurrent() - startTime;
	if(rtn) {
		printf("***certVerify error\n");
		return (OSStatus)rtn;
	} 
	return noErr;
}

static OSStatus cgvCleanup(
	TestParams *testParams)
{
	CgvParams *cgvParams = (CgvParams *)testParams->testPriv;
	CSSM_ModuleDetach(cgvParams->cspHand);
	CSSM_ModuleDetach(cgvParams->tpHand);
	CSSM_ModuleDetach(cgvParams->clHand);
	for(unsigned dex=0; dex<NUM_ST_CERTS; dex++) {
		delete cgvParams->certs[dex];
	}
	if(cgvParams->anchors) {
		delete(cgvParams->anchors);
	}
	free(cgvParams);
	return noErr;
}

#pragma mark -- CSSM_TP_CertGroupVerify, explicit anchors --

static OSStatus cgvAnchorInit(
	TestParams *testParams)
{
	CgvParams *cgvParams = (CgvParams *)malloc(sizeof(CgvParams));
	memset(cgvParams, 0, sizeof(CgvParams));
	cgvParams->tpHand    = tpStartup();
	cgvParams->clHand    = clStartup();
	cgvParams->cspHand   = cspStartup();
	cgvParams->certs[0]  = new BlobList();
	cgvParams->certs[0]->addFile(certsToVerify[0].certFileName);
	cgvParams->anchors   = new BlobList();
	cgvParams->anchors->addFile(ST_ANCHOR_NAME);
	testParams->testPriv = cgvParams;
	return noErr;
}

static OSStatus cgvAnchorRun(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	CgvParams *cgvParams = (CgvParams *)testParams->testPriv;
	BlobList nullList;

	CertVerifyArgs vfyArgs;
	memset(&vfyArgs, 0, sizeof(vfyArgs));
	vfyArgs.version = CERT_VFY_ARGS_VERS;
	
	vfyArgs.tpHand = cgvParams->tpHand;
	vfyArgs.clHand = cgvParams->clHand;
	vfyArgs.cspHand = cgvParams->cspHand;
	vfyArgs.certs = cgvParams->certs[0];
	vfyArgs.roots = cgvParams->anchors;
	vfyArgs.allowUnverified = CSSM_TRUE;
	vfyArgs.vfyPolicy = CVP_SSL;
	vfyArgs.revokePolicy = CRP_None;
	vfyArgs.sslHost = certsToVerify[0].hostName;
	vfyArgs.revokePolicy = CRP_None;
	vfyArgs.quiet = CSSM_TRUE;
	
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	int rtn = certVerify(&vfyArgs);
	*timeSpent = CFAbsoluteTimeGetCurrent() - startTime;
	if(rtn) {
		printf("***certVerify error\n");
		return (OSStatus)rtn;
	} 
	return noErr;
}

/* cleanup - use cgvCleanup() */

#pragma mark -- CSSM_TP_CertGroupVerify, 3 certs with anchor --

static OSStatus cgv3Init(
	TestParams *testParams)
{
	CgvParams *cgvParams = (CgvParams *)malloc(sizeof(CgvParams));
	memset(cgvParams, 0, sizeof(CgvParams));
	cgvParams->tpHand    = tpStartup();
	cgvParams->clHand    = clStartup();
	cgvParams->cspHand   = cspStartup();
	cgvParams->certs[0]  = new BlobList();
	cgvParams->certs[0]->addFile(THAWTE_LEAF);
	cgvParams->certs[0]->addFile(THAWTE_CA);
	cgvParams->certs[0]->addFile(THAWTE_ROOT);
	cgvParams->anchors   = new BlobList();
	cgvParams->anchors->addFile(THAWTE_ROOT);
	testParams->testPriv = cgvParams;
	return noErr;
}

static OSStatus cgv3Run(
	TestParams *testParams,
	unsigned loopNum,
	CFAbsoluteTime *timeSpent)
{
	CgvParams *cgvParams = (CgvParams *)testParams->testPriv;
	BlobList nullList;

	CertVerifyArgs vfyArgs;
	memset(&vfyArgs, 0, sizeof(vfyArgs));
	vfyArgs.version = CERT_VFY_ARGS_VERS;
	
	vfyArgs.tpHand = cgvParams->tpHand;
	vfyArgs.clHand = cgvParams->clHand;
	vfyArgs.cspHand = cgvParams->cspHand;
	vfyArgs.certs = cgvParams->certs[0];	/* that's three certs */
	vfyArgs.roots = cgvParams->anchors;
	vfyArgs.allowUnverified = CSSM_TRUE;
	vfyArgs.vfyPolicy = CVP_Basic;
	vfyArgs.revokePolicy = CRP_None;
	vfyArgs.sslHost = certsToVerify[0].hostName;
	vfyArgs.revokePolicy = CRP_None;
	vfyArgs.quiet = CSSM_TRUE;
	
	CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
	int rtn = certVerify(&vfyArgs);
	*timeSpent = CFAbsoluteTimeGetCurrent() - startTime;
	if(rtn) {
		printf("***certVerify error\n");
		return (OSStatus)rtn;
	} 
	return noErr;
}

static OSStatus cgv3Cleanup(
	TestParams *testParams)
{
	CgvParams *cgvParams = (CgvParams *)testParams->testPriv;
	CSSM_ModuleDetach(cgvParams->cspHand);
	CSSM_ModuleDetach(cgvParams->tpHand);
	CSSM_ModuleDetach(cgvParams->clHand);
	delete cgvParams->certs[0];
	delete(cgvParams->anchors);
	free(cgvParams);
	return noErr;
}

#pragma mark ---- Static array of all tests ----

static TestDefs testDefs[] = 
{
	{ 	"Keychain open",
		100,
		kcOpenInit,
		kcOpenRun,
		kcOpenCleanup,
		'o',
	},
	{ 	"Keychain cert search",
		100,
		kcSearchInit,
		kcSearchRun,
		kcSearchCleanup,
		's',
	},
	{ 	"SecTrustEvaluate",
		100,
		secTrustInit,
		secTrustRun,
		secTrustCleanup,
		'e',
	},
	{ 	"TP CertGroupVerify, system anchors",
		100,
		cgvInit,
		cgvRun,
		cgvCleanup,
		'v',
	},
	{ 	"TP CertGroupVerify, explicit anchor",
		100,
		cgvAnchorInit,
		cgvAnchorRun,
		cgvCleanup,
		'V',
	},
	{ 	"TP CertGroupVerify, 3 certs with anchor",
		100,
		cgv3Init,
		cgv3Run,
		cgv3Cleanup,
		'3',
	},
	{ 	"SecKeychainCopySearchList",
		100,
		kcCSLInit,
		kcCSLRun,
		kcCSLCleanup,
		'k',
	},
};

#define NUM_TESTS	(sizeof(testDefs) / sizeof(testDefs[0]))

int main(int argc, char **argv)
{
	TestParams 	testParams;
	TestDefs	*testDef;
	OSStatus 	ortn;
	int 		arg;
	char		*argp;
	unsigned	cmdLoops = 0;		// can be specified in cmd line
									// if not, use TestDefs.loops
	char		testSpec = '\0';	// allows specification of one test
									// otherwise run all
									
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 't':
				testSpec = argp[2];
				break;
			case 'l':
				cmdLoops = atoi(&argp[2]);
				break;
			default:
				usage(argv);
		}
	}

	for(unsigned testNum=0; testNum<NUM_TESTS; testNum++) {
		testDef = &testDefs[testNum];
		unsigned loopCount;
		
		if(testSpec && (testDef->testSpec != testSpec)) {
			continue;
		}
		printf("%s:\n", testDef->testName);
		ortn = testDef->init(&testParams);
		if(ortn) {
			exit(1);
		}
		if(cmdLoops) {
			/* user specified */
			loopCount = cmdLoops;
		}	
		else {
			/* default */
			loopCount = testDef->loops;
		}
		CFAbsoluteTime totalTime = 0;
		CFAbsoluteTime thisTime;
		for(unsigned loop=0; loop<loopCount; loop++) {
			ortn = testDef->run(&testParams, loop, &thisTime);
			if(ortn) {
				exit(1);
			}
			totalTime += thisTime;
		}
		testDef->cleanup(&testParams);
		printf("   %3.2f ms per op\n", (totalTime / loopCount) * 1000.0);
	}
	return 0;
}