symTest.c   [plain text]


/* Copyright (c) 1998,2003-2006,2008 Apple Inc.
 *
 * symTest.c - test CSP symmetric encrypt/decrypt.
 *
 * Revision History
 * ----------------
 *   4 May 2000 Doug Mitchell
 *		Ported to X/CDSA2. 
 *  20 May 1998 Doug Mitchell at Apple
 *		Ported to CDSA1.2, new Apple CSP
 *  15 Aug 1997	Doug Mitchell at Apple
 *		Ported from CryptKit ObjC version
 *  26 Aug 1996	Doug Mitchell at NeXT
 *		Created.
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <Security/cssm.h>
#include <Security/cssmapple.h>
#include "cspwrap.h"
#include "common.h"
#include "cspdlTesting.h"

/*
 * Defaults.
 */
#define LOOPS_DEF		50
#define MIN_PTEXT_SIZE	8
#define MAX_PTEXT_SIZE	0x10000

/*
 * Enumerate algs our own way to allow iteration.
 */
typedef enum {
	ALG_ASC = 1,
	ALG_DES,
	ALG_RC2,
	ALG_RC4,
	ALG_RC5,
	ALG_3DES,
	ALG_AES,
	ALG_BFISH,
	ALG_CAST,
	ALG_NULL					/* normally not used */
} SymAlg;
#define ALG_FIRST			ALG_ASC
#define ALG_LAST			ALG_CAST

#define PBE_ENABLE			0
#define PWD_LENGTH_MAX		64
#define MAX_DATA_SIZE		(100000 + 100)	/* bytes */
#define LOOP_NOTIFY			20

#define LOG_SIZE			0
#if		LOG_SIZE
#define logSize(s)	printf s
#else
#define logSize(s)
#endif

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("   Options:\n");
	printf("   a=algorithm (s=ASC; d=DES; 3=3DES; 2=RC2; 4=RC4; 5=RC5; a=AES;\n"); 
	printf("                b=Blowfish; c=CAST; n=Null; default=all)\n");
	printf("   l=loops (default=%d; 0=forever)\n", LOOPS_DEF);
	printf("   n=minPtextSize (default=%d)\n", MIN_PTEXT_SIZE);
	printf("   x=maxPtextSize (default=%d)\n", MAX_PTEXT_SIZE);
	printf("   k=keySizeInBits\n");
	printf("   r(eference keys only)\n");
	printf("   e(xport)\n");
	printf("   d (no DB open)\n");
	printf("   p=pauseInterval (default=0, no pause)\n");
	printf("   o (no padding, well-aligned plaintext)\n");
	printf("   u (no multi-update ops)\n");
	printf("   U (only multi-update ops)\n");
	printf("   m (CSP mallocs out bufs)\n");
	printf("   D (CSP/DL; default = bare CSP)\n");
	printf("   K (key gen only)\n");
	printf("   v(erbose)\n");
	printf("   q(uiet)\n");
	printf("   h(elp)\n");
	exit(1);
}

/* constant seed data */
static CSSM_DATA seedData = {8, (uint8 *)"12345678"};

/* alternate between two derivation algs, with different digest sizes */
#define PBE_DERIVE_ALG_ODD	CSSM_ALGID_PKCS5_PBKDF1_MD5
#define PBE_DERIVE_ALG_EVEN	CSSM_ALGID_PKCS5_PBKDF1_SHA1

/*
 * When expectEqualText is true, encrypt/decrypt in place. 
 */
#define EQUAL_TEXT_IN_PLACE		1

static int doTest(CSSM_CSP_HANDLE cspHand,
	CSSM_DATA_PTR ptext,
	uint32 keyAlg,						// CSSM_ALGID_xxx of the key
	uint32 encrAlg,						// encrypt/decrypt
	uint32 mode,
	uint32 padding,
	uint32 effectiveKeySizeInBits,
	CSSM_BOOL refKey,
	CSSM_DATA_PTR pwd,				// password- NULL means use a random key data
	CSSM_BOOL stagedEncr,
	CSSM_BOOL stagedDecr,
	CSSM_BOOL mallocPtext,			// only meaningful if !stagedDecr
	CSSM_BOOL mallocCtext,			// only meaningful if !stagedEncr
	CSSM_BOOL quiet,
	CSSM_BOOL keyGenOnly,
	CSSM_BOOL expectEqualText)		// ptext size must == ctext size
{
	CSSM_KEY_PTR	symKey = NULL;
	CSSM_DATA 		ctext = {0, NULL};
	CSSM_DATA		rptext = {0, NULL};
	CSSM_RETURN		crtn;
	int				rtn = 0;
	uint32			keySizeInBits;
	CSSM_DATA 		initVector;
	uint32			rounds = 0;
	
	/* generate keys with well aligned sizes; effectiveKeySize specified in encrypt
	 * only if not well aligned */
	keySizeInBits = (effectiveKeySizeInBits + 7) & ~7;
	if(keySizeInBits == effectiveKeySizeInBits) {
		effectiveKeySizeInBits = 0;
	}
	
	if(encrAlg == CSSM_ALGID_RC5) {
		/* roll the dice, pick one of three values for rounds */
		unsigned die = genRand(1,3);
		switch(die) {
			case 1:
				rounds = 8;
				break;
			case 2:
				rounds = 12;
				break;
			case 3:
				rounds = 16;
				break;
		}
	}

	if(pwd == NULL) {
		/* random key */
		symKey = cspGenSymKey(cspHand,
				keyAlg,
				"noLabel",
				7,
				CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
				keySizeInBits,
				refKey);
	}
	else {
		/* this code isn't tested */
		uint32	  pbeAlg;
		initVector.Data = NULL;		// we're going to ignore this
		initVector.Length = 0;
		/* one of two random PBE algs */
		if(ptext->Data[0] & 1) {
			pbeAlg = PBE_DERIVE_ALG_ODD;
		}
		else {
			pbeAlg = PBE_DERIVE_ALG_EVEN;
		}
		symKey = cspDeriveKey(cspHand,
			pbeAlg,
			keyAlg,
			"noLabel",
			7,
			CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
			keySizeInBits,
			refKey, 
			pwd,
			&seedData,
			1,			// iteration count
			&initVector);
		if(initVector.Data != NULL) {
			CSSM_FREE(initVector.Data);
		}
	}
	if(symKey == NULL) {
		rtn = testError(quiet);
		goto abort;
	}
	if(keyGenOnly) {
		rtn = 0;
		goto abort;
	}
	
	/* not all algs need this, pass it in anyway */
	initVector.Data = (uint8 *)"someStrangeInitVect";
	switch(encrAlg) {
		case CSSM_ALGID_AES:
		case CSSM_ALGID_NONE:
			initVector.Length = 16;
			break;
		default:
			initVector.Length = 8;
			break;
	}
	if(stagedEncr) {
		crtn = cspStagedEncrypt(cspHand,
			encrAlg,
			mode,
			padding,
			symKey,
			NULL,		// second key unused
			effectiveKeySizeInBits,
			0,			// cipherBlockSize
			rounds,
			&initVector,
			ptext,
			&ctext,
			CSSM_TRUE);	// multi
	}
	else {
		const CSSM_DATA *ptextPtr = ptext;
		if(expectEqualText && mallocCtext && CSPDL_NOPAD_ENFORCE_SIZE) {
			/* 
			 * !pad test: ensure this works when ctextlen == ptextlen by 
			 * mallocing ourself right now (instead of cspEncrypt doing it 
			 * after doing a CSSM_QuerySize())
			 */
			ctext.Data = (uint8 *)appMalloc(ptext->Length, NULL);
			if(ctext.Data == NULL) {
				printf("memmory failure\n");
				rtn = testError(quiet);
				goto abort;
			}
			ctext.Length = ptext->Length;
			#if	EQUAL_TEXT_IN_PLACE
			/* encrypt in place */
			memmove(ctext.Data, ptext->Data, ptext->Length);
			ptextPtr = &ctext;
			#endif
		}
		crtn = cspEncrypt(cspHand,
			encrAlg,
			mode,
			padding,
			symKey,
			NULL,		// second key unused
			effectiveKeySizeInBits,
			rounds,
			&initVector,
			ptextPtr,
			&ctext,
			mallocCtext);
	}
	if(crtn) {
		rtn = testError(quiet);
		goto abort;
	}
	if(expectEqualText && (ptext->Length != ctext.Length)) {
		printf("***ctext/ptext length mismatch: ptextLen %lu  ctextLen %lu\n",
			ptext->Length, ctext.Length);
		rtn = testError(quiet);
		if(rtn) {
			goto abort;
		}
	}
	logSize(("###ctext len %lu\n", ctext.Length)); 
	if(stagedDecr) {
		crtn = cspStagedDecrypt(cspHand,
			encrAlg,
			mode,
			padding,
			symKey,
			NULL,		// second key unused
			effectiveKeySizeInBits,
			0,			// cipherBlockSize
			rounds,
			&initVector,
			&ctext,
			&rptext,
			CSSM_TRUE);	// multi
	}
	else {
		const CSSM_DATA *ctextPtr = &ctext;
		if(expectEqualText && mallocPtext && CSPDL_NOPAD_ENFORCE_SIZE) {
			/* 
			 * !pad test: ensure this works when ctextlen == ptextlen by 
			 * mallocing ourself right now (instead of cspDecrypt doing it 
			 * after doing a CSSM_QuerySize())
			 */
			rptext.Data = (uint8 *)appMalloc(ctext.Length, NULL);
			if(rptext.Data == NULL) {
				printf("memmory failure\n");
				rtn = testError(quiet);
				goto abort;
			}
			rptext.Length = ctext.Length;
			#if	EQUAL_TEXT_IN_PLACE
			/* decrypt in place */
			memmove(rptext.Data, ctext.Data, ctext.Length);
			ctextPtr = &rptext;
			#endif
		}
		crtn = cspDecrypt(cspHand,
			encrAlg,
			mode,
			padding,
			symKey,
			NULL,		// second key unused
			effectiveKeySizeInBits,
			rounds,
			&initVector,
			ctextPtr,
			&rptext,
			mallocPtext);
	}
	if(crtn) {
		rtn = testError(quiet);
		goto abort;
	}
	logSize(("###rptext len %lu\n", rptext.Length)); 
	/* compare ptext, rptext */
	if(ptext->Length != rptext.Length) {
		printf("Ptext length mismatch: expect %lu, got %lu\n", ptext->Length, rptext.Length);
		rtn = testError(quiet);
		if(rtn) {
			goto abort;
		}
	}
	if(memcmp(ptext->Data, rptext.Data, ptext->Length)) {
		printf("***data miscompare\n");
		rtn = testError(quiet);
	}
abort:
	/* free key if we have it*/
	if(symKey != NULL) {
		if(cspFreeKey(cspHand, symKey)) {
			printf("Error freeing privKey\n");
			rtn = 1;
		}
		CSSM_FREE(symKey);
	}
	/* free rptext, ctext */
	appFreeCssmData(&rptext, CSSM_FALSE);
	appFreeCssmData(&ctext, CSSM_FALSE);
	return rtn;
}

int main(int argc, char **argv)
{
	int					arg;
	char				*argp;
	unsigned			loop;
	CSSM_DATA			ptext;
	CSSM_CSP_HANDLE 	cspHand;
	CSSM_BOOL			stagedEncr;
	CSSM_BOOL 			stagedDecr;
	CSSM_BOOL			mallocCtext;
	CSSM_BOOL			mallocPtext;
	CSSM_BOOL			refKey;
	const char			*algStr;
	uint32				keyAlg;			// CSSM_ALGID_xxx of the key
	uint32				encrAlg;		// CSSM_ALGID_xxx of the encrypt/decrypt/sign
	int					i;
	int					currAlg;		// ALG_xxx
	CSSM_DATA_PTR		pPwd;
	CSSM_DATA			pwd;
	uint32				actKeySizeInBits;
	int					rtn = 0;
	uint32				blockSize;		// for noPadding case
	CSSM_BOOL 			expectEqualText;
	
	/*
	 * User-spec'd params
	 */
	CSSM_BOOL	keySizeSpec = CSSM_FALSE;		// false: use rand key size
	SymAlg		minAlg = ALG_FIRST;
	SymAlg		maxAlg = ALG_LAST;
	unsigned	loops = LOOPS_DEF;
	CSSM_BOOL	verbose = CSSM_FALSE;
	unsigned	minPtextSize = MIN_PTEXT_SIZE;
	unsigned	maxPtextSize = MAX_PTEXT_SIZE;
	CSSM_BOOL	quiet = CSSM_FALSE;
	unsigned	pauseInterval = 0;
	uint32		mode;
	uint32		padding;
	CSSM_BOOL	noDbOpen = CSSM_FALSE;
	CSSM_BOOL	bareCsp = CSSM_TRUE;
	CSSM_BOOL 	keyGenOnly = CSSM_FALSE;
	CSSM_BOOL	noPadding = CSSM_FALSE;
	CSSM_BOOL	multiEnable = CSSM_TRUE;
	CSSM_BOOL	multiOnly = CSSM_FALSE;
	CSSM_BOOL	refKeysOnly = CSSM_FALSE;
	CSSM_BOOL	cspMallocs = CSSM_FALSE;
	
	#if	macintosh
	argc = ccommand(&argv);
	#endif
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'a':
				if(argp[1] != '=') {
					usage(argv);
				}
				switch(argp[2]) {
					case 's':
						minAlg = maxAlg = ALG_ASC;
						break;
					case 'd':
						minAlg = maxAlg = ALG_DES;
						break;
					case '3':
						minAlg = maxAlg = ALG_3DES;
						break;
					case '2':
						minAlg = maxAlg = ALG_RC2;
						break;
					case '4':
						minAlg = maxAlg = ALG_RC4;
						break;
					case '5':
						minAlg = maxAlg = ALG_RC5;
						break;
					case 'a':
						minAlg = maxAlg = ALG_AES;
						break;
					case 'b':
						minAlg = maxAlg = ALG_BFISH;
						break;
					case 'c':
						minAlg = maxAlg = ALG_CAST;
						break;
					case 'n':
						minAlg = maxAlg = ALG_NULL;
						break;
					default:
						usage(argv);
				}
				break;
		    case 'l':
				loops = atoi(&argp[2]);
				break;
		    case 'n':
				minPtextSize = atoi(&argp[2]);
				break;
		    case 'x':
				maxPtextSize = atoi(&argp[2]);
				break;
			case 'r':
				refKeysOnly = CSSM_TRUE;
				break;
		    case 'k':
		    	actKeySizeInBits = atoi(&argp[2]);
		    	keySizeSpec = CSSM_TRUE;
				break;
		    case 'v':
		    	verbose = CSSM_TRUE;
				break;
			case 'D':
				bareCsp = CSSM_FALSE;
				#if CSPDL_ALL_KEYS_ARE_REF
		    	refKeysOnly = CSSM_TRUE;
				#endif
				break;
		    case 'q':
		    	quiet = CSSM_TRUE;
				break;
		    case 'p':
		    	pauseInterval = atoi(&argp[2]);;
				break;
			case 'o':
				noPadding = CSSM_TRUE;
				break;
		    case 'd':
		    	noDbOpen = CSSM_TRUE;
				break;
		    case 'K':
		    	keyGenOnly = CSSM_TRUE;
				break;
		    case 'u':
		    	multiEnable = CSSM_FALSE;
				break;
		    case 'U':
		    	multiOnly = CSSM_TRUE;
				break;
		    case 'm':
		    	cspMallocs = CSSM_TRUE;
				break;
		    case 'h':
		    default:
				usage(argv);
		}
	}
	if(multiOnly && !multiEnable) {
		printf("***can't specify multi disable and multi only\n");
		exit(1);
	}
	if(minPtextSize > maxPtextSize) {
		printf("***minPtextSize must be <= maxPtextSize\n");
		usage(argv);
	}
	pwd.Data = (uint8 *)CSSM_MALLOC(PWD_LENGTH_MAX);
	ptext.Data = (uint8 *)CSSM_MALLOC(maxPtextSize);
	if(ptext.Data == NULL) {
		printf("Insufficient heap space\n");
		exit(1);
	}
	/* ptext length set in test loop */
	printf("Starting symTest; args: ");
	for(i=1; i<argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n");
	cspHand = cspDlDbStartup(bareCsp, NULL);
	if(cspHand == 0) {
		exit(1);
	}
	if(pauseInterval) {
		fpurge(stdin);
		printf("Top of test; hit CR to proceed: ");
		getchar();
	}
	for(currAlg=minAlg; currAlg<=maxAlg; currAlg++) {
		/* some default values... */
		mode = CSSM_ALGMODE_NONE;
		padding = CSSM_PADDING_NONE;
		blockSize = 0;			// i.e., don't align
		expectEqualText = CSSM_FALSE;
		switch(currAlg) {
			case ALG_ASC:
				encrAlg = keyAlg = CSSM_ALGID_ASC;
				algStr = "ASC";
				break;
			case ALG_DES:
				encrAlg = keyAlg = CSSM_ALGID_DES;
				algStr = "DES";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 8;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS1;
				}
				break;
			case ALG_3DES:
				/* currently the only one with different key and encr algs */
				/* Though actually these two consts are equivalent...for now... */
				keyAlg  = CSSM_ALGID_3DES_3KEY;
				encrAlg = CSSM_ALGID_3DES_3KEY_EDE;
				algStr = "3DES";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 8;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS1;
				}
				break;
			case ALG_RC2:
				encrAlg = keyAlg = CSSM_ALGID_RC2;
				algStr = "RC2";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 8;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS1;		// what does padding do here?
				}
				break;
			case ALG_RC4:
				encrAlg = keyAlg = CSSM_ALGID_RC4;
				algStr = "RC4";
				mode = CSSM_ALGMODE_NONE;
				expectEqualText = CSSM_TRUE;			// always for RC4
				break;
			case ALG_RC5:
				encrAlg = keyAlg = CSSM_ALGID_RC5;
				algStr = "RC5";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 8;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS1;		// eh?
				}
				break;
			case ALG_AES:
				encrAlg = keyAlg = CSSM_ALGID_AES;
				algStr = "AES";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 16;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS5;
				}
				break;
			case ALG_BFISH:
				encrAlg = keyAlg = CSSM_ALGID_BLOWFISH;
				algStr = "Blowfish";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 8;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS5;
				}
				break;
			case ALG_CAST:
				encrAlg = keyAlg = CSSM_ALGID_CAST;
				algStr = "CAST";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 8;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS5;
				}
				break;
			case ALG_NULL:
				encrAlg = keyAlg = CSSM_ALGID_NONE;
				algStr = "NULL";
				if(noPadding) {
					mode = CSSM_ALGMODE_CBC_IV8;
					blockSize = 16;
					expectEqualText = CSSM_TRUE;
				}
				else {
					mode = CSSM_ALGMODE_CBCPadIV8;
					padding = CSSM_PADDING_PKCS5;
				}
				break;
		}
		if(!quiet || verbose) {
			printf("Testing alg %s\n", algStr);
		}
		for(loop=1; ; loop++) {
			simpleGenData(&ptext, minPtextSize, maxPtextSize);
			if(blockSize) {
				/* i.e., no padding --> align ptext */
				ptext.Length = ((ptext.Length + blockSize - 1) / blockSize) * blockSize;
			}
			if(!keySizeSpec) {
				actKeySizeInBits = randKeySizeBits(keyAlg, OT_Encrypt);
			}
			/* else constant, spec'd by user, may be 0 (default per alg) */
			/* mix up some random and derived keys, as well as staging and "who does
			 * the malloc?" */
			pPwd       = (loop & 1) ? &pwd : NULL;
			if(multiEnable) {
				if(multiOnly) {
					stagedEncr = stagedDecr = CSSM_TRUE;
				}
				else {
					stagedEncr = (loop & 2) ? CSSM_TRUE : CSSM_FALSE;
					stagedDecr = (loop & 4) ? CSSM_TRUE : CSSM_FALSE;
				}
			}
			else {
				stagedEncr = CSSM_FALSE;
				stagedDecr = CSSM_FALSE;
			}
			if(!stagedEncr && !cspMallocs) {
				mallocCtext = (ptext.Data[0] & 1) ? CSSM_TRUE : CSSM_FALSE;
			}
			else {
				mallocCtext = CSSM_FALSE;
			}
			if(!stagedDecr && !cspMallocs) {
				mallocPtext = (ptext.Data[0] & 2) ? CSSM_TRUE : CSSM_FALSE;
			}
			else {
				mallocPtext = CSSM_FALSE;
			}
			if(refKeysOnly) {
				refKey = CSSM_TRUE;
			}
			else {
				refKey = (ptext.Data[0] & 4) ? CSSM_TRUE : CSSM_FALSE;
			}
			#if !PBE_ENABLE
			pPwd = NULL;
		 	#endif
			if(!quiet) {
			   	if(verbose || ((loop % LOOP_NOTIFY) == 0)) {
					printf("..loop %d text size %lu keySizeBits %u\n",
						loop, (unsigned long)ptext.Length, (unsigned)actKeySizeInBits);
					if(verbose) {
						printf("  refKey %d derive %d stagedEncr %d  stagedDecr %d mallocCtext %d "
							"mallocPtext %d\n",
					 	(int)refKey, (pPwd == NULL) ? 0 : 1, (int)stagedEncr, (int)stagedDecr,
					 	(int)mallocCtext, (int)mallocPtext);
					 }
				}
			}
			#if		PBE_ENABLE
			if(pPwd != NULL) {
				/* PBE - cook up random password */
				simpleGenData(pPwd, APPLE_PBE_MIN_PASSWORD, PWD_LENGTH_MAX);
			}
			#endif
			
			if(doTest(cspHand,
					&ptext,
					keyAlg,
					encrAlg,
					mode,
					padding,
					actKeySizeInBits,
					refKey,
					pPwd,
					stagedEncr,
					stagedDecr,
					mallocPtext,
					mallocCtext,
					quiet,
					keyGenOnly,
					expectEqualText)) {
				rtn = 1;
				break;
			}
			if(pauseInterval && ((loop % pauseInterval) == 0)) {
				char c;
				fpurge(stdin);
				printf("Hit CR to proceed, q to abort: ");
				c = getchar();
				if(c == 'q') {
					goto testDone;
				}
			}
			if(loops && (loop == loops)) {
				break;
			}
		}	/* main loop */
		if(rtn) {
			break;
		}
		
	}	/* for algs */
	
testDone:
	cspShutdown(cspHand, bareCsp);
	if(pauseInterval) {
		fpurge(stdin);
		printf("ModuleDetach/Unload complete; hit CR to exit: ");
		getchar();
	}
	if((rtn == 0) && !quiet) {
		printf("%s test complete\n", argv[0]);
	}
	CSSM_FREE(pwd.Data);
	CSSM_FREE(ptext.Data);
	return rtn;
}