1/*
2 * dhFullTest - Diffie-Hellman exerciser.
3 */
4#include <stdlib.h>
5#include <stdio.h>
6#include <Security/cssm.h>
7#include "cspwrap.h"
8#include "common.h"
9#include <security_cdsa_utils/cuFileIo.h>
10#include <strings.h>
11#include "cspdlTesting.h"
12
13#define USAGE_DEF		"noUsage"
14#define LOOPS_DEF		10
15#define KEY_SIZE_DEF	512
16#define DERIVE_KEY_SIZE	128
17#define DERIVE_KEY_ALG	CSSM_ALGID_AES
18#define ENCR_ALG		CSSM_ALGID_AES
19#define ENCR_MODE		CSSM_ALGMODE_CBCPadIV8
20#define ENCR_PADDING	CSSM_PADDING_PKCS7
21#define MAX_PTEXT_SIZE	1024
22
23static void usage(char **argv)
24{
25	printf("usage: %s [options]\n", argv[0]);
26	printf("Options:\n");
27	printf("  k=keySize (default = %d)\n", KEY_SIZE_DEF);
28	printf("  l=loops (0=forever)\n");
29	printf("  p=pauseInterval (default=0, no pause)\n");
30	printf("  D (CSP/DL; default = bare CSP)\n");
31	printf("  o=fileName (dump key and param blobs to filename)\n");
32	printf("  i=filename (obtain param blobs from filename\n");
33	printf("  q(uiet)\n");
34	printf("  v(erbose))\n");
35	exit(1);
36}
37
38/*
39 * Translate blob format to strings.
40 */
41typedef struct {
42	CSSM_KEYBLOB_FORMAT form;
43	const char *str;
44} BlobDesc;
45
46static const BlobDesc BlobDescs[] = {
47	{ CSSM_KEYBLOB_RAW_FORMAT_NONE,  "NONE" },
48	{ CSSM_KEYBLOB_RAW_FORMAT_PKCS3, "PKCS3" },
49	{ CSSM_KEYBLOB_RAW_FORMAT_PKCS8, "PKCS8" },
50	{ CSSM_KEYBLOB_RAW_FORMAT_X509,  "X509" },
51};
52#define NUM_BLOB_DESCS	(sizeof(BlobDescs) / sizeof(BlobDesc))
53
54static const char *blobFormStr(
55	CSSM_KEYBLOB_FORMAT form)
56{
57	for(unsigned i=0; i<NUM_BLOB_DESCS; i++) {
58		const BlobDesc *bdp = &BlobDescs[i];
59		if(bdp->form == form) {
60			return bdp->str;
61		}
62	}
63	return "***UNKNOWN BLOB FORM""";
64}
65
66/*
67 * Generate a Diffie-Hellman key pair. Optionally allows specification of
68 * algorithm parameters, and optionally returns algorithm parameters if
69 * we generate them.
70 */
71static int dhKeyGen(
72	CSSM_CSP_HANDLE	cspHand,
73	CSSM_KEY_PTR	pubKey,
74	CSSM_BOOL		pubIsRef,
75	CSSM_KEYBLOB_FORMAT	pubForm,		// NONE, PKCS3, X509
76	CSSM_KEYBLOB_FORMAT	expectPubForm,	// PKCS3, X509
77	CSSM_KEY_PTR	privKey,
78	CSSM_BOOL		privIsRef,
79	CSSM_KEYBLOB_FORMAT	privForm,		// NONE, PKCS3, PKCS8
80	CSSM_KEYBLOB_FORMAT	expectPrivForm,	// PKCS3, PKCS8
81	const CSSM_DATA	*inParams,		// optional
82	CSSM_DATA_PTR	outParams,		// optional, we malloc
83	uint32			keySizeInBits,
84	CSSM_BOOL		quiet)
85{
86	CSSM_RETURN		crtn;
87	CSSM_CC_HANDLE 	ccHand;
88	CSSM_DATA		labelData = { strlen(USAGE_DEF), (uint8 *)USAGE_DEF };
89
90	if(inParams && outParams) {
91		printf("***dhKeyGen: inParams and outParams are mutually "
92			"exclusive.\n");
93		return -1;
94	}
95	memset(pubKey, 0, sizeof(CSSM_KEY));
96	memset(privKey, 0, sizeof(CSSM_KEY));
97
98	crtn = CSSM_CSP_CreateKeyGenContext(cspHand,
99		CSSM_ALGID_DH,
100		keySizeInBits,
101		NULL,					// Seed
102		NULL,					// Salt
103		NULL,					// StartDate
104		NULL,					// EndDate
105		inParams,				// Params, may be NULL
106		&ccHand);
107	if(crtn) {
108		printError("CSSM_CSP_CreateKeyGenContext", crtn);
109		return testError(quiet);
110	}
111
112	if((inParams == NULL) && (outParams != NULL)) {
113		/* explicitly generate params and return them to caller */
114		outParams->Data = NULL;
115		outParams->Length = 0;
116		crtn = CSSM_GenerateAlgorithmParams(ccHand,
117			keySizeInBits, outParams);
118		if(crtn) {
119			printError("CSSM_GenerateAlgorithmParams", crtn);
120			return testError(quiet);
121		}
122	}
123
124	if((privForm != CSSM_KEYBLOB_RAW_FORMAT_NONE) && !privIsRef) {
125		crtn = AddContextAttribute(ccHand,
126			CSSM_ATTRIBUTE_PRIVATE_KEY_FORMAT,
127			sizeof(uint32),
128			CAT_Uint32,
129			NULL,
130			privForm);
131		if(crtn) {
132			printError("AddContextAttribute(CSSM_ATTRIBUTE_PRIVATE_KEY_"
133				"FORMAT)", crtn);
134			return crtn;
135		}
136	}
137	if((pubForm != CSSM_KEYBLOB_RAW_FORMAT_NONE) && !pubIsRef) {
138		crtn = AddContextAttribute(ccHand,
139			CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT,
140			sizeof(uint32),
141			CAT_Uint32,
142			NULL,
143			pubForm);
144		if(crtn) {
145			printError("AddContextAttribute(CSSM_ATTRIBUTE_PUBLIC_KEY_"
146				"FORMAT)", crtn);
147			return crtn;
148		}
149	}
150
151	uint32 privAttr;
152	uint32 pubAttr = CSSM_KEYATTR_EXTRACTABLE;
153	if(pubIsRef) {
154		pubAttr |= CSSM_KEYATTR_RETURN_REF;
155	}
156	else {
157		pubAttr |= CSSM_KEYATTR_RETURN_DATA;
158	}
159	if(privIsRef) {
160		privAttr = CSSM_KEYATTR_RETURN_REF;
161	}
162	else {
163		privAttr = CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE;
164	}
165	crtn = CSSM_GenerateKeyPair(ccHand,
166		/*
167		 * Public key specification. We specify raw key format
168		 * here since we have to have access to the raw public key
169		 * bits in order to perform D-H key exchange.
170		 */
171		CSSM_KEYUSE_DERIVE,		// only legal use of a Diffie-Hellman key
172		pubAttr,
173		&labelData,
174		pubKey,
175		/* private key specification */
176		CSSM_KEYUSE_DERIVE,
177		privAttr,
178		&labelData,				// same labels
179		NULL,					// CredAndAclEntry
180		privKey);
181	if(crtn) {
182		printError("CSSM_GenerateKeyPair", crtn);
183			return testError(quiet);
184	}
185	if(!privIsRef && (privKey->KeyHeader.Format != expectPrivForm)) {
186		printf("***Expected priv format %s got %s\n",
187			blobFormStr(expectPrivForm),
188			blobFormStr(privKey->KeyHeader.Format));
189		return testError(quiet);
190	}
191	if(!pubIsRef && (pubKey->KeyHeader.Format != expectPubForm)) {
192		printf("***Expected pub format %s got %s\n",
193			blobFormStr(expectPubForm),
194			blobFormStr(pubKey->KeyHeader.Format));
195		return testError(quiet);
196	}
197	CSSM_DeleteContext(ccHand);
198	return crtn;
199}
200
201/*
202 * Perform Diffie-Hellman key exchange.
203 * Given "our" private key (in the form of a CSSM_KEY) and "their" public
204 * key (in the form of a raw blob of bytes), cook up a symmetric key.
205 */
206static int dhKeyExchange(
207	CSSM_CSP_HANDLE	cspHand,
208	CSSM_KEY_PTR	myPrivKey,
209	CSSM_KEY_PTR	theirPubKey,
210	CSSM_KEY_PTR	derivedKey,				// RETURNED
211	uint32			deriveKeySizeInBits,
212	CSSM_ALGORITHMS	derivedKeyAlg,
213	uint32			derivedKeyUsage,
214	uint32			derivedKeyAttr,
215	CSSM_BOOL		quiet)
216{
217	CSSM_RETURN 			crtn;
218	CSSM_ACCESS_CREDENTIALS	creds;
219	CSSM_CC_HANDLE			ccHand;
220	CSSM_DATA				labelData =
221								{ strlen(USAGE_DEF), (uint8 *)USAGE_DEF };
222
223	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
224	memset(derivedKey, 0, sizeof(CSSM_KEY));
225
226	crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
227		CSSM_ALGID_DH,
228		derivedKeyAlg,
229		deriveKeySizeInBits,
230		&creds,
231		myPrivKey,		// BaseKey
232		0,				// IterationCount
233		0,				// Salt
234		0,				// Seed
235		&ccHand);
236	if(crtn) {
237		printError("CSSM_CSP_CreateDeriveKeyContext", crtn);
238		return testError(quiet);
239	}
240
241	/*
242	 * Public key passed in as CSSM_DATA *Param - only if
243	 * the pub key is in raw PKCS3 form
244	 */
245	CSSM_DATA nullParam = {0, NULL};
246	CSSM_DATA_PTR paramPtr;
247	CSSM_KEYHEADER &hdr = theirPubKey->KeyHeader;
248	if((hdr.BlobType == CSSM_KEYBLOB_RAW) &&
249	   (hdr.Format == CSSM_KEYBLOB_RAW_FORMAT_PKCS3)) {
250		/* simple case */
251		paramPtr = &theirPubKey->KeyData;
252	}
253	else {
254		/* add this pub key as a context attr */
255		crtn = AddContextAttribute(ccHand,
256			CSSM_ATTRIBUTE_PUBLIC_KEY,
257			sizeof(CSSM_KEY),
258			CAT_Ptr,
259			(void *)theirPubKey,
260			0);
261		if(crtn) {
262			printError("AddContextAttribute(CSSM_ATTRIBUTE_PUBLIC_KEY)",
263				crtn);
264			return crtn;
265		}
266		paramPtr = &nullParam;
267	}
268
269	crtn = CSSM_DeriveKey(ccHand,
270		paramPtr,
271		derivedKeyUsage,
272		derivedKeyAttr,
273		&labelData,
274		NULL,				// cread/acl
275		derivedKey);
276	if(crtn) {
277		printError("CSSM_DeriveKey", crtn);
278	}
279	CSSM_DeleteContext(ccHand);
280	return crtn;
281}
282
283static CSSM_DATA someIv = {16, (uint8 *)"Some enchanted init vector" };
284
285/*
286 * Encrypt ptext with myDeriveKey ==> ctext
287 * decrypt ctext with theirDeriveKey ==> rptext
288 * ensure ptext == rptext
289 */
290static int doEncryptTest(
291	CSSM_CSP_HANDLE		cspHand,
292	const CSSM_DATA		*ptext,
293	CSSM_KEY_PTR		myDeriveKey,
294	CSSM_KEY_PTR		theirDeriveKey,
295	CSSM_ALGORITHMS		encrAlg,
296	uint32				encrMode,
297	CSSM_PADDING		encrPadding,
298	CSSM_BOOL			quiet)
299{
300	CSSM_DATA ctext = {0, NULL};
301	CSSM_DATA rptext = {0, NULL};
302	CSSM_RETURN crtn;
303
304	crtn = cspEncrypt(cspHand,
305		encrAlg,
306		encrMode,
307		encrPadding,
308		myDeriveKey,
309		NULL,			// 2nd key
310		0,				// effective key size
311		0,				// rounds
312		&someIv,
313		ptext,
314		&ctext,
315		CSSM_FALSE);	// mallocCtext
316	if(crtn) {
317		return testError(quiet);
318	}
319	crtn = cspDecrypt(cspHand,
320		encrAlg,
321		encrMode,
322		encrPadding,
323		theirDeriveKey,
324		NULL,			// 2nd key
325		0,				// effective key size
326		0,				// rounds
327		&someIv,
328		&ctext,
329		&rptext,
330		CSSM_FALSE);	// mallocCtext
331	if(crtn) {
332		return testError(quiet);
333	}
334	if(!appCompareCssmData(ptext, &rptext)) {
335		printf("***DATA MISCOMPARE***\n");
336		return testError(quiet);
337	}
338	appFree(ctext.Data, NULL);
339	appFree(rptext.Data, NULL);
340	return 0;
341}
342
343int doTest(
344	CSSM_CSP_HANDLE		cspHand,
345	const CSSM_DATA		*ptext,
346	CSSM_BOOL			pubIsRef,
347	CSSM_KEYBLOB_FORMAT	pubForm,		// NONE, PKCS3, X509
348	CSSM_KEYBLOB_FORMAT	expectPubForm,	// PKCS3, X509
349	CSSM_BOOL			privIsRef,
350	CSSM_KEYBLOB_FORMAT	privForm,		// NONE, PKCS3, PKCS8
351	CSSM_KEYBLOB_FORMAT	expectPrivForm,	// PKCS3, PKCS8
352	CSSM_BOOL			sym1IsRef,
353	CSSM_BOOL			sym2IsRef,
354	const CSSM_DATA		*inParams,		// optional
355	CSSM_DATA_PTR		outParams,		// optional
356	uint32				keySizeInBits,
357	CSSM_BOOL			quiet)
358{
359	CSSM_KEY	myPriv;
360	CSSM_KEY	myPub;
361	CSSM_KEY	theirPriv;
362	CSSM_KEY	theirPub;
363	int			rtn = 0;
364
365	/* generate two key pairs */
366	if(dhKeyGen(cspHand,
367		&myPub,
368		pubIsRef,
369		pubForm,
370		expectPubForm,
371		&myPriv,
372		privIsRef,
373		privForm,
374		expectPrivForm,
375		inParams,
376		outParams,
377		keySizeInBits,
378		quiet)) {
379			return 1;
380	}
381
382	/* note this MUST match the params either specified or generated
383	 * in previous call */
384	if((inParams == NULL) && (outParams == NULL)) {
385		printf("***BRRZAP! Must provide a way to match D-H parameters!\n");
386		exit(1);
387	}
388	const CSSM_DATA *theParams = inParams;
389	if(theParams == NULL) {
390		theParams = outParams;
391	}
392	if(dhKeyGen(cspHand,
393		&theirPub,
394		pubIsRef,
395		/* let's always use default for this pair */
396		CSSM_KEYBLOB_RAW_FORMAT_NONE,
397		CSSM_KEYBLOB_RAW_FORMAT_PKCS3,		// we know this is the default
398		&theirPriv,
399		privIsRef,
400		/* let's always use default for this pair */
401		CSSM_KEYBLOB_RAW_FORMAT_NONE,
402		CSSM_KEYBLOB_RAW_FORMAT_PKCS3,		// we know this is the default
403		theParams,
404		NULL,				// outParams
405		keySizeInBits,
406		quiet)) {
407			return 1;
408	}
409
410	/* derive two keys, ensure they match */
411	CSSM_KEY myDerive;
412	CSSM_KEY theirDerive;
413	uint32 myDeriveAttr;
414	uint32 theirDeriveAttr;
415	if(sym1IsRef) {
416		myDeriveAttr = CSSM_KEYATTR_RETURN_REF;
417	}
418	else {
419		myDeriveAttr =
420			CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE;
421	}
422	if(sym2IsRef) {
423		theirDeriveAttr = CSSM_KEYATTR_RETURN_REF;
424	}
425	else {
426		theirDeriveAttr =
427			CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE;
428	}
429	if(dhKeyExchange(cspHand,
430		&myPriv,
431		&theirPub,
432		&myDerive,
433		DERIVE_KEY_SIZE,
434		DERIVE_KEY_ALG,
435		CSSM_KEYUSE_ENCRYPT,
436		myDeriveAttr,
437		quiet)) {
438			return testError(quiet);
439	}
440	if(dhKeyExchange(cspHand,
441		&theirPriv,
442		&myPub,
443		&theirDerive,
444		DERIVE_KEY_SIZE,
445		DERIVE_KEY_ALG,
446		CSSM_KEYUSE_DECRYPT,
447		theirDeriveAttr,
448		quiet)) {
449			return testError(quiet);
450	}
451
452	if(doEncryptTest(cspHand,
453		ptext,
454		&myDerive,
455		&theirDerive,
456		ENCR_ALG,
457		ENCR_MODE,
458		ENCR_PADDING,
459		quiet)) {
460			return 1;
461	}
462
463	cspFreeKey(cspHand, &myPub);
464	cspFreeKey(cspHand, &myPriv);
465	cspFreeKey(cspHand, &theirPub);
466	cspFreeKey(cspHand, &theirPriv);
467	cspFreeKey(cspHand, &myDerive);
468	cspFreeKey(cspHand, &theirDerive);
469	return rtn;
470}
471
472int main(int argc, char **argv)
473{
474	int		 				arg;
475	char					*argp;
476	CSSM_CSP_HANDLE 		cspHand;
477	unsigned				loop;
478	int 					i;
479	CSSM_DATA				inParams = {0, NULL};
480	CSSM_DATA				outParams = {0, NULL};
481	CSSM_DATA_PTR			inParamPtr = NULL;
482	CSSM_DATA_PTR			outParamPtr = NULL;
483	CSSM_DATA				ptext;
484	CSSM_BOOL				pubIsRef;
485	CSSM_BOOL				privIsRef;
486	CSSM_BOOL				sym1IsRef;
487	CSSM_BOOL				sym2IsRef;
488	CSSM_KEYBLOB_FORMAT		pubForm;		// NONE, PKCS3, X509
489	CSSM_KEYBLOB_FORMAT		expectPubForm;	// PKCS3, X509
490	CSSM_KEYBLOB_FORMAT		privForm;		// NONE, PKCS3, PKCS8
491	CSSM_KEYBLOB_FORMAT		expectPrivForm;	// PKCS3, PKCS8
492
493	/* user-spec'd parameters */
494	unsigned				keySize = KEY_SIZE_DEF;
495	unsigned				pauseInterval = 0;
496	unsigned				loops = LOOPS_DEF;
497	CSSM_BOOL				quiet = CSSM_FALSE;
498	CSSM_BOOL				verbose = CSSM_FALSE;
499	CSSM_BOOL				bareCsp = CSSM_TRUE;
500	char					*inFileName = NULL;
501	char					*outFileName = NULL;
502
503	for(arg=1; arg<argc; arg++) {
504		argp = argv[arg];
505	    switch(argp[0]) {
506			case 'k':
507				keySize = atoi(&argp[2]);
508				break;
509		    case 'l':
510				loops = atoi(&argp[2]);
511				break;
512		    case 'p':
513				pauseInterval = atoi(&argp[2]);
514				break;
515			case 'i':
516				inFileName = &argp[2];
517				break;
518			case 'o':
519				outFileName = &argp[2];
520				break;
521			case 'D':
522				bareCsp = CSSM_FALSE;
523				break;
524			case 'q':
525				quiet = CSSM_TRUE;
526				break;
527		    case 'v':
528		    	verbose = CSSM_TRUE;
529				break;
530			default:
531				usage(argv);
532		}
533	}
534
535	cspHand = cspDlDbStartup(bareCsp, NULL);
536	if(cspHand == 0) {
537		exit(1);
538	}
539
540	if(!bareCsp &&
541	   !CSPDL_DSA_GEN_PARAMS &&
542	   (inFileName == NULL)) {
543		/*
544		 * For now, CSPDL can not do gen parameters. This only
545		 * works if we're supplied params externally (which most
546		 * likely were generated from the bare CSP).
547		 */
548		printf("*** %s can't run with CSPDL unless you supply DH "
549			"parameters.\n", argv[0]);
550		exit(1);
551	}
552
553	/* optionally fetch algorithm parameters from a file */
554	if(inFileName) {
555		unsigned len;
556		int r = readFile(inFileName, &inParams.Data, &len);
557		if(r) {
558			printf("***Can't read parameters from %s; aborting.\n",
559				inFileName);
560			exit(1);
561		}
562		inParams.Length = len;
563		/* constant from now on */
564		inParamPtr = &inParams;
565	}
566	else {
567		/* first time thru, no user-supplied parameters; generate them and
568		 * save in outParams */
569		outParamPtr = &outParams;
570	}
571
572	ptext.Data = (uint8 *)appMalloc(MAX_PTEXT_SIZE, NULL);
573	/* ptext length set in test loop */
574
575	printf("Starting %s; args: ", argv[0]);
576	for(i=1; i<argc; i++) {
577		printf("%s ", argv[i]);
578	}
579	printf("\n");
580	for(loop=1; ; loop++) {
581		if(!quiet) {
582			printf("...Loop %d\n", loop);
583		}
584		simpleGenData(&ptext, 10, MAX_PTEXT_SIZE);
585
586		/* mix up raw and ref keys, except for CSPDL which
587		 * requires all ref keys */
588		if(bareCsp) {
589			pubIsRef  = (loop & 1) ? CSSM_TRUE : CSSM_FALSE;
590			privIsRef = (loop & 2) ? CSSM_TRUE : CSSM_FALSE;
591			sym1IsRef = (loop & 4) ? CSSM_TRUE : CSSM_FALSE;
592			sym2IsRef = (loop & 8) ? CSSM_TRUE : CSSM_FALSE;
593		}
594		else {
595			pubIsRef = privIsRef = sym1IsRef = sym2IsRef = CSSM_TRUE;
596		}
597		if(!privIsRef) {
598			int die = genRand(1,3);
599			switch(die) {
600				case 1:
601					privForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
602					expectPrivForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
603					break;
604				case 2:
605					privForm = expectPrivForm =
606						CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
607					break;
608				case 3:
609					privForm = expectPrivForm =
610						CSSM_KEYBLOB_RAW_FORMAT_PKCS8;
611					break;
612			}
613			if(verbose) {
614				printf("...privIsRef false; form %s, expectForm %s\n",
615					blobFormStr(privForm), blobFormStr(expectPrivForm));
616			}
617		}
618		else {
619			privForm = expectPrivForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
620			if(verbose) {
621				printf("...privIsRef true\n");
622			}
623		}
624		if(!pubIsRef) {
625			int die = genRand(1,3);
626			switch(die) {
627				case 1:
628					pubForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
629					expectPubForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
630					break;
631				case 2:
632					pubForm = expectPubForm =
633						CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
634					break;
635				case 3:
636					pubForm = expectPubForm =
637						CSSM_KEYBLOB_RAW_FORMAT_X509;
638					break;
639			}
640			if(verbose) {
641				printf("...pubIsRef false; form %s, expectForm %s\n",
642					blobFormStr(pubForm), blobFormStr(expectPubForm));
643			}
644		}
645		else {
646			pubForm = expectPubForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
647			if(verbose) {
648				printf("...pubIsRef true\n");
649			}
650		}
651		i = doTest(cspHand,
652			&ptext,
653			pubIsRef,
654			pubForm,
655			expectPubForm,
656			privIsRef,
657			privForm,
658			expectPrivForm,
659			sym1IsRef,
660			sym2IsRef,
661			inParamPtr,
662			outParamPtr,
663			keySize,
664			quiet);
665		if(i) {
666			break;
667		}
668		if(loop == 1) {
669			/* first time thru */
670			if(outFileName) {
671				/* save parameters for another run */
672				i = writeFile(outFileName, outParams.Data, outParams.Length);
673				if(i) {
674					printf("***Error writing params to %s; continuing.\n",
675						outFileName);
676				}
677				else {
678					if(!quiet) {
679						printf("...wrote %lu bytes to %s\n",
680							outParams.Length, outFileName);
681					}
682				}
683			}
684			if(!inFileName) {
685				/* from now on, use the parameters we just generated */
686				inParamPtr = &outParams;
687			}
688			/* and in any case don't fetch any more params */
689			outParamPtr = NULL;
690		}
691		if(loops && (loop == loops)) {
692			break;
693		}
694		if(pauseInterval && ((loop % pauseInterval) == 0)) {
695			char inch;
696
697			fpurge(stdin);
698			printf("Hit CR to proceed, q to quit: ");
699			inch = getchar();
700			if(inch == 'q') {
701				break;
702			}
703		}
704	}
705	appFree(ptext.Data, NULL);
706	CSSM_ModuleDetach(cspHand);
707	if(!quiet) {
708		printf("OK\n");
709	}
710	return 0;
711}
712