1/*
2 * kcTime.cpp - measure performance of keychain ops
3 */
4
5#include <Security/Security.h>
6#include <security_cdsa_utils/cuFileIo.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11#include <CoreFoundation/CoreFoundation.h>
12#include <utilLib/common.h>
13#include <utilLib/cputime.h>
14#include <Security/SecImportExport.h>
15
16/* Hard coded test params */
17
18/*
19 * Number of keychains to create/search
20 */
21#define KT_NUM_KEYCHAINS		10
22
23/*
24 * Certs and P12 blobs to add to keychain
25 */
26#define KT_CERT0_NAME			"amazon_v3.100.cer"
27#define KT_CERT1_NAME			"SecureServer.509.cer"
28#define KT_P12_PFX				"test1.p12"
29#define KT_P12_PASSWORD			"password"
30
31/*
32 * Base name of keychains we create. We delete them before we start.
33 * They're in the user's home directory to faciliate testing NFS vs. local.
34 */
35#define KT_KC_NAME				"kcTime_test_"
36
37static void usage(char **argv)
38{
39    printf("Usage: %s [option ...]\n", argv[0]);
40    printf("Options:\n");
41	printf("   v   verbose\n");
42	printf("   h   help\n");
43	exit(1);
44}
45
46static void printAllTimes(
47	bool verbose,
48	double *delta,
49	unsigned numSamples)
50{
51	if(!verbose) {
52		return;
53	}
54	for(unsigned dex=0; dex<numSamples; dex++) {
55		printf("   sample[%u] %8.4f\n", dex, delta[dex]);
56	}
57}
58
59static void printSecErr(
60	const char *op,
61	OSStatus ortn)
62{
63	cssmPerror(op, ortn);
64}
65
66int main(int argc, char **argv)
67{
68	SecKeychainRef		kcRefs[KT_NUM_KEYCHAINS];
69	OSStatus 			ortn;
70	int 				arg;
71	char				*argp;
72	char				kcName[KT_NUM_KEYCHAINS][80];
73	unsigned			dex;
74	unsigned char		*cert0Data;
75	unsigned			cert0Len;
76	unsigned char		*cert1Data;
77	unsigned			cert1Len;
78	SecCertificateRef	certRef0[KT_NUM_KEYCHAINS];
79	SecCertificateRef	certRef1[KT_NUM_KEYCHAINS];
80	CFArrayRef			savedSearchList;
81	CFMutableArrayRef	ourSearchList;
82	int					irtn;
83	CPUTime				startTime, endTime;
84	double				deltaMs[KT_NUM_KEYCHAINS * 2];
85	double				deltaMs2[KT_NUM_KEYCHAINS];
86	double				deltaMs3[KT_NUM_KEYCHAINS];
87	SecKeychainSearchRef srchRef;
88	CFStringRef			p12Pwd;
89	CFDataRef			p12Data;
90	bool				verbose = false;
91
92	memset(kcRefs, 0, sizeof(SecKeychainRef) * KT_NUM_KEYCHAINS);
93
94	for(arg=1; arg<argc; arg++) {
95		argp = argv[arg];
96		switch(argp[0]) {
97			case 'v':
98				verbose = true;
99				break;
100			default:
101				usage(argv);
102		}
103	}
104
105	printf("Starting kcTime test using %d keychains\n", KT_NUM_KEYCHAINS);
106
107	/* generate keychain names */
108	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
109		sprintf(kcName[dex], "%s%d", KT_KC_NAME, dex);
110	}
111
112	/* delete existing keychains we created in the past */
113	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
114		/*
115		 * It may or may not exist, and may or may not be in the search
116		 * list. Brute force and ignore errors.
117		 */
118		SecKeychainRef kc;
119		ortn = SecKeychainOpen(kcName[dex], &kc);
120		if(ortn == noErr) {
121			SecKeychainDelete(kc);
122		}
123
124		/* brute force UNIX file delete too */
125		char *userHome = getenv("HOME");
126		char fullPath[1024];
127		if(userHome == NULL) {
128			userHome = (char *)"";
129		}
130		sprintf(fullPath, "%s/Library/Keychains/%s",
131			userHome, kcName[dex]);
132		unlink(fullPath);
133	}
134
135	/* read in certificate data */
136	irtn = readFile(KT_CERT0_NAME, &cert0Data, &cert0Len);
137	if(irtn) {
138		printf("I cannot find file %s in cwd.\n", KT_CERT0_NAME);
139		exit(1);
140	}
141	irtn = readFile(KT_CERT1_NAME, &cert1Data, &cert1Len);
142	if(irtn) {
143		printf("I cannot find file %s in cwd.\n", KT_CERT1_NAME);
144		exit(1);
145	}
146
147	/* save KC search list; we'll restore it at end */
148	ortn = SecKeychainCopySearchList(&savedSearchList);
149	if(ortn) {
150		printSecErr("SecKeychainCopySearchList", ortn);
151		return 1;
152	}
153	/* subsequent errors to errOut: to restore searchList! */
154
155	/* TIME: create n keychains */
156	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
157		unsigned pwdLen = strlen(kcName[dex]);
158		startTime = CPUTimeRead();
159		ortn = SecKeychainCreate(kcName[dex],
160			pwdLen,
161			kcName[dex],
162			false,		// promptUser,
163			nil,		// initialAccess
164			&kcRefs[dex]);
165		endTime = CPUTimeRead();
166		if(ortn) {
167			printSecErr("SecKeychainCreate", ortn);
168			goto errOut;
169		}
170		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
171	}
172	printf("Create     : %7.3f ms per op\n",
173		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
174	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
175
176	/* set KC search list to only the ones we just created */
177	ourSearchList = CFArrayCreateMutable(NULL, KT_NUM_KEYCHAINS, NULL);
178	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
179		CFArrayInsertValueAtIndex(ourSearchList, dex, kcRefs[dex]);
180	}
181	ortn = SecKeychainSetSearchList(ourSearchList);
182	if(ortn) {
183		printSecErr("SecKeychainSetSearchList", ortn);
184		goto errOut;
185	}
186	CFRelease(ourSearchList);
187
188	/* TIME: close all of them */
189	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
190		startTime = CPUTimeRead();
191		CFRelease(kcRefs[dex]);
192		endTime = CPUTimeRead();
193		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
194	}
195	printf("Close      : %7.3f ms per op\n",
196		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
197	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
198
199	/* TIME: open all of them */
200	/* This is so fast that we just measure the total time */
201	startTime = CPUTimeRead();
202	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
203		ortn = SecKeychainOpen(kcName[dex], &kcRefs[dex]);
204		if(ortn) {
205			printSecErr("SecKeychainOpen", ortn);
206			goto errOut;
207		}
208	}
209	endTime = CPUTimeRead();
210	deltaMs[0] = CPUTimeDeltaMs(startTime, endTime);
211	printf("Open       : %7.3f ms per op\n",
212		deltaMs[0] / KT_NUM_KEYCHAINS);
213	if(verbose) {
214		printf("   total time %7.3f ms\n", deltaMs[0]);
215	}
216
217	/* TIME: get status on all of them */
218	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
219		SecKeychainStatus status;
220		startTime = CPUTimeRead();
221		ortn = SecKeychainGetStatus(kcRefs[dex], &status);
222		endTime = CPUTimeRead();
223		if(ortn) {
224			printSecErr("SecKeychainGetStatus", ortn);
225			goto errOut;
226		}
227		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
228	}
229	printf("Get Status : %7.3f ms per op\n",
230		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
231	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
232
233	/* TIME: unlock each keychain */
234	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
235		unsigned pwdLen = strlen(kcName[dex]);
236		startTime = CPUTimeRead();
237		ortn = SecKeychainUnlock(kcRefs[dex],
238			pwdLen,	kcName[dex], true);
239		endTime = CPUTimeRead();
240		if(ortn) {
241			printSecErr("SecKeychainUnlock", ortn);
242			goto errOut;
243		}
244		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
245	}
246	printf("Unlock     : %7.3f ms per op\n",
247		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
248	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
249
250	/* TIME: create two SecCertificateRefs for each KC */
251	/* this is fast, just measure total time */
252	startTime = CPUTimeRead();
253	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
254		CSSM_DATA cdata = {cert0Len, cert0Data};
255		ortn = SecCertificateCreateFromData(&cdata,
256			CSSM_CERT_X_509v3,
257			CSSM_CERT_ENCODING_DER,
258			&certRef0[dex]);
259		if(ortn) {
260			printSecErr("SecCertificateCreateFromData", ortn);
261			goto errOut;
262		}
263		cdata.Length = cert1Len;
264		cdata.Data = cert1Data;
265		ortn = SecCertificateCreateFromData(&cdata,
266			CSSM_CERT_X_509v3,
267			CSSM_CERT_ENCODING_DER,
268			&certRef1[dex]);
269		if(ortn) {
270			printSecErr("SecCertificateCreateFromData", ortn);
271			goto errOut;
272		}
273	}
274	endTime = CPUTimeRead();
275	deltaMs[0] = CPUTimeDeltaMs(startTime, endTime);
276	/* we created KT_NUM_KEYCHAINS*2 certs in KT_NUM_KEYCHAINS samples */
277	printf("SecCertificateCreateFromData  : %7.3f ms per op\n",
278		deltaMs[0] / (KT_NUM_KEYCHAINS * 2.0));
279	if(verbose) {
280		printf("   total time %7.3f ms\n", deltaMs[0]);
281	}
282	free(cert0Data);
283	free(cert1Data);
284
285	/* TIME: add two certs to each keychain */
286	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
287		startTime = CPUTimeRead();
288		ortn = SecCertificateAddToKeychain(certRef0[dex], kcRefs[dex]);
289		if(ortn) {
290			printSecErr("SecCertificateAddToKeychain", ortn);
291			goto errOut;
292		}
293		ortn = SecCertificateAddToKeychain(certRef1[dex], kcRefs[dex]);
294		endTime = CPUTimeRead();
295		if(ortn) {
296			printSecErr("SecCertificateAddToKeychain", ortn);
297			goto errOut;
298		}
299		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
300	}
301	/* we added KT_NUM_KEYCHAINS*2 certs in KT_NUM_KEYCHAINS samples */
302	printf("SecCertificateAddToKeychain   : %7.3f ms per op\n",
303		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS) / 2.0);
304	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
305
306	/* free the certRefs */
307	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
308		CFRelease(certRef0[dex]);
309		CFRelease(certRef1[dex]);
310	}
311
312	/*
313	�* Three TIMES:
314	 *  -- search for the first cert
315	 *  -- search for the next one
316	 *  -- search once more to end (not found)
317	 */
318	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
319		SecKeychainItemRef certRef = NULL;
320
321		/* step 1. set up search, get first item */
322		startTime = CPUTimeRead();
323		ortn = SecKeychainSearchCreateFromAttributes(kcRefs[dex],
324			kSecCertificateItemClass,
325			NULL,		// no attrs
326			&srchRef);
327		if(ortn) {
328			printSecErr("SecKeychainSearchCreateFromAttributes", ortn);
329			goto errOut;
330		}
331		ortn = SecKeychainSearchCopyNext(srchRef, &certRef);
332		/* might be necessary to actually bring in the cert */
333		if(SecCertificateGetTypeID() != CFGetTypeID(certRef)) {
334			printf("***Unexpected CFType on cert search\n");
335			goto errOut;
336		}
337		endTime = CPUTimeRead();
338		if(ortn) {
339			printSecErr("SecKeychainSearchCopyNext", ortn);
340			goto errOut;
341		}
342		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
343		CFRelease(certRef);
344
345		/* step 2. get next item */
346		startTime = CPUTimeRead();
347		ortn = SecKeychainSearchCopyNext(srchRef, &certRef);
348		if(SecCertificateGetTypeID() != CFGetTypeID(certRef)) {
349			printf("***Unexpected CFType on cert search\n");
350			goto errOut;
351		}
352		endTime = CPUTimeRead();
353		if(ortn) {
354			printSecErr("SecKeychainSearchCreateFromAttributes", ortn);
355			goto errOut;
356		}
357		deltaMs2[dex] = CPUTimeDeltaMs(startTime, endTime);
358		CFRelease(certRef);
359
360		/* step 3. one more search for nonexistent item */
361		startTime = CPUTimeRead();
362		ortn = SecKeychainSearchCopyNext(srchRef, &certRef);
363		endTime = CPUTimeRead();
364		if(ortn != errSecItemNotFound) {
365			if(ortn == noErr) {
366				printf("***SecKeychainSearchCopyNext got noErr, "
367					"expected notFound\n");
368			}
369			else {
370				printSecErr("SecKeychainSearchCopyNext", ortn);
371			}
372			goto errOut;
373		}
374		deltaMs3[dex] = CPUTimeDeltaMs(startTime, endTime);
375		CFRelease(srchRef);
376	}
377	printf("SecKeychainSearch first item  : %7.3f ms per op\n",
378		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
379	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
380	printf("SecKeychainSearch next item   : %7.3f ms per op\n",
381		CPUTimeAvg(deltaMs2, KT_NUM_KEYCHAINS));
382	printAllTimes(verbose, deltaMs2, KT_NUM_KEYCHAINS);
383	printf("SecKeychainSearch end of list : %7.3f ms per op\n",
384		CPUTimeAvg(deltaMs3, KT_NUM_KEYCHAINS));
385	printAllTimes(verbose, deltaMs3, KT_NUM_KEYCHAINS);
386
387	/*
388	 * TIME: search all certs in all keychains
389	 * This search create is outside the loop....
390	 */
391	ortn = SecKeychainSearchCreateFromAttributes(NULL,
392		kSecCertificateItemClass,
393		NULL,		// no attrs
394		&srchRef);
395	if(ortn) {
396		printSecErr("SecKeychainSearchCreateFromAttributes", ortn);
397		goto errOut;
398	}
399	/* we have 2 certs per keychain, search all of them */
400	for(dex=0; dex<KT_NUM_KEYCHAINS*2; dex++) {
401		SecKeychainItemRef certRef = NULL;
402
403		startTime = CPUTimeRead();
404		ortn = SecKeychainSearchCopyNext(srchRef, &certRef);
405		/* might be necessary to actually bring in the cert */
406		if(SecCertificateGetTypeID() != CFGetTypeID(certRef)) {
407			printf("***Unexpected CFType on cert search\n");
408			goto errOut;
409		}
410		endTime = CPUTimeRead();
411		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
412		CFRelease(certRef);
413	}
414	printf("SecKeychainSearch all KCs     : %7.3f ms per op\n",
415		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS * 2));
416	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS * 2);
417
418	/*
419	 * TIME: import a p12 identity into each keychain
420	 *
421	 * First, read the file
422	 */
423	irtn = readFile(KT_P12_PFX, &cert0Data, &cert0Len);
424	if(irtn) {
425		printf("I cannot find file %s in cwd.\n", KT_P12_PFX);
426		exit(1);
427	}
428	p12Data = CFDataCreate(NULL, cert0Data, cert0Len);
429	p12Pwd = CFStringCreateWithCString(NULL, KT_P12_PASSWORD,
430					kCFStringEncodingASCII);
431
432	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
433		SecExternalFormat format = kSecFormatPKCS12;
434		SecExternalItemType itemType = kSecItemTypeAggregate;
435		SecKeyImportExportParameters keyParams;
436		memset(&keyParams, 0, sizeof(keyParams));
437		keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
438		keyParams.flags = kSecKeyNoAccessControl;
439		keyParams.passphrase = p12Pwd;
440
441		keyParams.keyAttributes = CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE |
442			CSSM_KEYATTR_EXTRACTABLE;
443
444		startTime = CPUTimeRead();
445		ortn = SecKeychainItemImport(p12Data,
446			NULL,		// fileNameOrExtension
447			&format, &itemType,
448			0,			// flags
449			&keyParams,
450			kcRefs[dex],
451			NULL);		// outItems
452		if(ortn) {
453			printSecErr("SecKeychainItemImport(p12)", ortn);
454			goto errOut;
455		}
456		endTime = CPUTimeRead();
457		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
458	}
459	CFRelease(p12Data);
460	CFRelease(p12Pwd);
461	printf("P12 Import                    : %7.3f ms per op\n",
462		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
463	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
464
465	/* TIME: identity search in each keychain */
466	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
467		SecIdentitySearchRef idSrch;
468		SecIdentityRef idRef;
469		startTime = CPUTimeRead();
470		ortn = SecIdentitySearchCreate(kcRefs[dex], 0, &idSrch);
471		if(ortn) {
472			printSecErr("SecIdentitySearchCreate", ortn);
473			goto errOut;
474		}
475		ortn = SecIdentitySearchCopyNext(idSrch, &idRef);
476		endTime = CPUTimeRead();
477		if(ortn) {
478			printSecErr("SecIdentitySearchCopyNext", ortn);
479			goto errOut;
480		}
481		deltaMs[dex] = CPUTimeDeltaMs(startTime, endTime);
482		CFRelease(idSrch);
483		CFRelease(idRef);
484	}
485	printf("SecIdentity search            : %7.3f ms per op\n",
486		CPUTimeAvg(deltaMs, KT_NUM_KEYCHAINS));
487	printAllTimes(verbose, deltaMs, KT_NUM_KEYCHAINS);
488
489errOut:
490	ortn = SecKeychainSetSearchList(savedSearchList);
491	if(ortn) {
492		printSecErr("SecKeychainSetSearchList", ortn);
493	}
494
495	/* delete all the keychains we created */
496	for(dex=0; dex<KT_NUM_KEYCHAINS; dex++) {
497		if(kcRefs[dex] != NULL) {
498			SecKeychainDelete(kcRefs[dex]);
499		}
500	}
501
502	/* other cleanup if anyone cares */
503	return 0;
504}
505