contextReuse.cpp   [plain text]


/* 
 * contextReuse.cpp 
 *
 * Verify proper operation of symmetric CSP algorithms when CSSM_CC_HANDLE 
 * (crypto context) is reused. Tests specifically for Radar 4551700, which 
 * dealt with a problem with the Gladman AES implementation handling the 
 * same context for an encrypt followed by a decrypt; other situations
 * are tested here (e.g. encrypt followed by another encrypt including CBC)
 * as well as all CSP symmetric algorithms. 
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <Security/cssm.h>
#include <Security/cssmapple.h>
#include "cspwrap.h"
#include "common.h"
#include <string.h>
#include "cspdlTesting.h"

/*
 * Defaults.
 */
#define LOOPS_DEF		200

#define MIN_DATA_SIZE	8
#define MAX_DATA_SIZE	10000							/* bytes */
#define MAX_KEY_SIZE	MAX_KEY_SIZE_RC245_BYTES		/* bytes */
#define LOOP_NOTIFY		20

#define RAW_MODE			CSSM_ALGMODE_ECB		/* doesn't work for BSAFE */
#define RAW_MODE_STREAM		CSSM_ALGMODE_NONE
#define COOKED_MODE			CSSM_ALGMODE_CBCPadIV8

#define RAW_MODE_STR		"ECB"
#define RAW_MODE_STREAM_STR	"None"
#define COOKED_MODE_STR		"CBC/Pad"

/*
 * Enumerate algs our own way to allow iteration.
 */
typedef enum {
	ALG_ASC = 1,			// not tested - no reference available
	ALG_DES = 1,
	ALG_RC2,
	ALG_RC4,
	ALG_RC5,
	ALG_3DES,
	ALG_AES,
	ALG_AES192,		/* 192 bit block */
	ALG_AES256,		/* 256 bit block */
	ALG_BFISH,
	ALG_CAST
} SymAlg;
#define ALG_FIRST			ALG_ASC
#define ALG_LAST			ALG_CAST

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("   Options:\n");
	printf("   a=algorithm (d=DES; 3=3DES3; 2=RC2; 4=RC4; 5=RC5; a=AES; A=AES192; \n");
	printf("                6=AES256; b=Blowfish; c=CAST; s=ASC; default=all)\n");
	printf("   l=loops (default=%d; 0=forever)\n", LOOPS_DEF);
	printf("   k=keySizeInBits\n");
	printf("   m=maxPtextSize (default=%d)\n", MAX_DATA_SIZE);
	printf("   n=minPtextSize (default=%d)\n", MIN_DATA_SIZE);
	printf("   p=pauseInterval (default=0, no pause)\n");
	printf("   D (CSP/DL; default = bare CSP)\n");
	printf("   v(erbose)\n");
	printf("   q(uiet)\n");
	printf("   h(elp)\n");
	exit(1);
}

#define LOG_STAGED_OPS				0
#if		LOG_STAGED_OPS
#define soprintf(s)	printf s
#else
#define soprintf(s)
#endif

/* 
 * Multipurpose encrypt. Like cspStagedEncrypt(), but it takes a
 * context handle and doesn't have as many options. 
 */
static CSSM_RETURN stagedEncrypt(
	CSSM_CSP_HANDLE	cspHand,
	CSSM_CC_HANDLE 	cryptHand,
	uint32			algorithm,			// CSSM_ALGID_FEED, etc.
	uint32			cipherBlockSizeBytes,// optional
	const CSSM_DATA *iv,				// init vector, optional
	const CSSM_DATA *ptext,
	CSSM_DATA_PTR	ctext,				// mallocd by caller, must be big enough!
	CSSM_BOOL		multiUpdates)		// false:single update, true:multi updates
{
	CSSM_RETURN		crtn;
	CSSM_SIZE		bytesEncrypted;			// per update
	CSSM_SIZE		bytesEncryptedTotal = 0;
	CSSM_RETURN		ocrtn = CSSM_OK;		// 'our' crtn
	unsigned		toMove;					// remaining
	unsigned		thisMove;				// bytes to encrypt on this update
	CSSM_DATA		thisPtext;				// running ptr into ptext
	CSSM_DATA		thisCtext;				// running ptr into ctext
	CSSM_BOOL		restoreErr = CSSM_FALSE;
	CSSM_RETURN		savedErr = CSSM_OK;
	CSSM_SIZE		ctextLen;
	
	if(cipherBlockSizeBytes) {
		crtn = AddContextAttribute(cryptHand,
			CSSM_ATTRIBUTE_BLOCK_SIZE,
			sizeof(uint32),
			CAT_Uint32,
			NULL,
			cipherBlockSizeBytes);
		if(crtn) {
			printError("CSSM_UpdateContextAttributes", crtn);
			ocrtn = crtn;
			goto abort;
		}
	}
	
	thisPtext = *ptext;
	thisCtext = *ctext;
	memset(ctext->Data, 0, ctext->Length);
	ctextLen = ctext->Length;
	
	crtn = CSSM_EncryptDataInit(cryptHand);
	if(crtn) {
		printError("CSSM_EncryptDataInit", crtn);
		ocrtn = crtn;
		goto abort;
	}
	
	toMove = ptext->Length;
	while(toMove) {
		if(multiUpdates) {
			thisMove = genRand(1, toMove);
		}
		else {
			/* just do one pass thru this loop */
			thisMove = toMove;
		}
		thisPtext.Length = thisMove;
		crtn = CSSM_EncryptDataUpdate(cryptHand,
			&thisPtext,
			1,
			&thisCtext,
			1,
			&bytesEncrypted);
		if(crtn) {
			printError("CSSM_EncryptDataUpdate", crtn);
			ocrtn = crtn;
			goto abort;
		}
		soprintf(("*** EncryptDataUpdate: ptextLen 0x%x  bytesEncrypted 0x%x\n",
			(unsigned)thisMove, (unsigned)bytesEncrypted));

		// NOTE: We return the proper length in ctext....
		ctextLen            -= bytesEncrypted;		// bump out ptr
		thisCtext.Length     = ctextLen;
		thisCtext.Data      += bytesEncrypted;
		bytesEncryptedTotal += bytesEncrypted;
		thisPtext.Data      += thisMove;			// bump in ptr
		toMove              -= thisMove;
	}
	/* OK, one more */
	crtn = CSSM_EncryptDataFinal(cryptHand, &thisCtext);
	if(crtn) {
		printError("CSSM_EncryptDataFinal", crtn);
		savedErr = crtn;
		restoreErr = CSSM_TRUE;
		goto abort;
	}
	soprintf(("*** EncryptDataFinal: bytesEncrypted 0x%x\n",
		(unsigned)thisCtext.Length));
	bytesEncryptedTotal += thisCtext.Length;
	ctext->Length = bytesEncryptedTotal;
abort:
	if(restoreErr) {
		/* give caller the error from the encrypt */
		ocrtn = savedErr;
	}
	return ocrtn;
}

/* 
 * Multipurpose decrypt. Like cspStagedDecrypt(), but it takes a
 * context handle and doesn't have as many options. 
 */
CSSM_RETURN stagedDecrypt(
	CSSM_CSP_HANDLE cspHand,
	CSSM_CC_HANDLE 	cryptHand,
	uint32			algorithm,			// CSSM_ALGID_FEED, etc.
	uint32			cipherBlockSizeBytes,// optional
	const CSSM_DATA *iv,				// init vector, optional
	const CSSM_DATA *ctext,
	CSSM_DATA_PTR	ptext,				// mallocd by caller, must be big enough!
	CSSM_BOOL		multiUpdates)		// false:single update, true:multi updates
{
	CSSM_RETURN		crtn;
	CSSM_SIZE		bytesDecrypted;			// per update
	CSSM_SIZE		bytesDecryptedTotal = 0;
	CSSM_RETURN		ocrtn = CSSM_OK;		// 'our' crtn
	unsigned		toMove;					// remaining
	unsigned		thisMove;				// bytes to decrypt on this update
	CSSM_DATA		thisCtext;				// running ptr into ptext
	CSSM_DATA		thisPtext;				// running ptr into ctext
	CSSM_SIZE		ptextLen;
	
	if(cipherBlockSizeBytes) {
		crtn = AddContextAttribute(cryptHand,
			CSSM_ATTRIBUTE_BLOCK_SIZE,
			sizeof(uint32),
			CAT_Uint32,
			NULL,
			cipherBlockSizeBytes);
		if(crtn) {
			printError("CSSM_UpdateContextAttributes", crtn);
			ocrtn = crtn;
			goto abort;
		}
	}
	
	thisCtext = *ctext;
	thisPtext = *ptext;
	memset(ptext->Data, 0, ptext->Length);
	ptextLen = ptext->Length;
	
	crtn = CSSM_DecryptDataInit(cryptHand);
	if(crtn) {
		printError("CSSM_DecryptDataInit", crtn);
		ocrtn = crtn;
		goto abort;
	}
	
	toMove = ctext->Length;
	while(toMove) {
		if(multiUpdates) {
			thisMove = genRand(1, toMove);
		}
		else {
			/* just do one pass thru this loop */
			thisMove = toMove;
		}
		thisCtext.Length = thisMove;
		crtn = CSSM_DecryptDataUpdate(cryptHand,
			&thisCtext,
			1,
			&thisPtext,
			1,
			&bytesDecrypted);
		if(crtn) {
			printError("CSSM_DecryptDataUpdate", crtn);
			ocrtn = crtn;
			goto abort;
		}
		soprintf(("*** DecryptDataUpdate: ctextLen 0x%x  bytesDecrypted 0x%x\n",
			(unsigned)thisMove, (unsigned)bytesDecrypted));

		// NOTE: We return the proper length in ptext....
		ptextLen            -= bytesDecrypted;		// bump out ptr
		thisPtext.Length     = ptextLen;
		thisPtext.Data      += bytesDecrypted;
		bytesDecryptedTotal += bytesDecrypted;
		thisCtext.Data      += thisMove;			// bump in ptr
		toMove              -= thisMove;
	}
	/* OK, one more */
	crtn = CSSM_DecryptDataFinal(cryptHand, &thisPtext);
	if(crtn) {
		printError("CSSM_DecryptDataFinal", crtn);
		ocrtn = crtn;
		goto abort;
	}
	soprintf(("*** DecryptDataFinal: bytesEncrypted 0x%x\n",
		(unsigned)thisPtext.Length));
	bytesDecryptedTotal += thisPtext.Length;
	ptext->Length = bytesDecryptedTotal;
abort:
	return ocrtn;
}

static int doTest(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		*ptext,
	const CSSM_DATA		*ctext1,
	const CSSM_DATA		*ctext2,
	const CSSM_DATA		*rptext,
	const CSSM_DATA		*keyData,
	const CSSM_DATA		*iv,
	uint32 				keyAlg,					// CSSM_ALGID_xxx of the key
	uint32 				encrAlg,				// encrypt/decrypt
	uint32 				encrMode,
	uint32 				padding,
	uint32				keySizeInBits,
	uint32				cipherBlockSizeBytes,
	CSSM_BOOL 			quiet)
{
	CSSM_DATA		lctext1;
	CSSM_DATA		lctext2;
	CSSM_DATA		lrptext;
	int				rtn = 0;
	CSSM_RETURN		crtn;
	CSSM_CC_HANDLE	ccHand1 = 0;
	CSSM_CC_HANDLE	ccHand2 = 0;
	CSSM_KEY		key1;
	CSSM_KEY		key2;
	uint8			dummy[cipherBlockSizeBytes];
	CSSM_DATA		dummyData = {cipherBlockSizeBytes, dummy};
	
	/* 
	 * generate two equivalent keys key1 and key2;
	 * generate two CC handles ccHand1, ccHand2;
	 * encrypt dummy data with ccHand1 to get it cooked;
	 * encrypt ptext with ccHand1 ==> ctext1;
	 * encrypt ptext with ccHand2 ==> ctext2;
	 * Compare ctext1 and ctext2;
	 * decrypt ctext1 with ccHand1, compare with ptext;
	 */
	crtn = cspGenSymKeyWithBits(cspHand, keyAlg, CSSM_KEYUSE_ANY,
		keyData, keySizeInBits / 8, &key1);
	if(crtn) {
		return crtn;
	}
	crtn = cspGenSymKeyWithBits(cspHand, keyAlg, CSSM_KEYUSE_ANY,
		keyData, keySizeInBits / 8, &key2);
	if(crtn) {
		return crtn;
	}
	ccHand1 = genCryptHandle(cspHand, 
		encrAlg, 
		encrMode, 
		padding,
		&key1, 
		NULL,		// pubKey
		iv,
		0,			// effectiveKeySizeInBits
		0);			// rounds
	if(ccHand1 == 0) {
		return CSSMERR_CSP_INTERNAL_ERROR;
	}
	ccHand2 = genCryptHandle(cspHand, 
		encrAlg, 
		encrMode, 
		padding,
		&key2, 
		NULL,		// pubKey
		iv,
		0,			// effectiveKeySizeInBits
		0);			// rounds
	if(ccHand2 == 0) {
		return CSSMERR_CSP_INTERNAL_ERROR;
	}
	 
	/* dummy encrypt to heat up ccHand1 */
	appGetRandomBytes(dummy, sizeof(dummy));
	lctext1 = *ctext1;
	crtn = stagedEncrypt(cspHand, ccHand1, encrAlg, cipherBlockSizeBytes, 
		iv, &dummyData, &lctext1, CSSM_FALSE);
	if(crtn) {
		return crtn;
	}
	
	/* encrypt ptext with ccHand1 and ccHand2, compare ctext */
	lctext1 = *ctext1;
	crtn = stagedEncrypt(cspHand, ccHand1, encrAlg, cipherBlockSizeBytes, 
		iv, ptext, &lctext1, CSSM_TRUE);
	if(crtn) {
		return crtn;
	}
	lctext2 = *ctext2;
	crtn = stagedEncrypt(cspHand, ccHand2, encrAlg, cipherBlockSizeBytes, 
		iv, ptext, &lctext2, CSSM_TRUE);
	if(crtn) {
		return crtn;
	}
	if(!appCompareCssmData(&lctext1, &lctext2)) {
		printf("***Ciphertext miscompare\n");
		if(testError(quiet)) {
			return 1;
		}
	}

	/* decrypt with ccHand1, compare with ptext */
	lrptext = *rptext;
	crtn = stagedDecrypt(cspHand, ccHand1, encrAlg, cipherBlockSizeBytes, 
		iv, &lctext1, &lrptext, CSSM_TRUE);
	if(crtn) {
		return crtn;
	}
	if(!appCompareCssmData(&lctext1, &lctext2)) {
		printf("***Plaintext miscompare\n");
		if(testError(quiet)) {
			return 1;
		}
	}
	
	if(ccHand1) {
		CSSM_DeleteContext(ccHand1);
	}
	if(ccHand2) {
		CSSM_DeleteContext(ccHand2);
	}
	return rtn;
}


int main(int argc, char **argv)
{
	int					arg;
	char				*argp;
	unsigned			loop;
	CSSM_DATA			ptext;
	CSSM_DATA			ctext1;
	CSSM_DATA			ctext2;
	CSSM_DATA			rptext;
	CSSM_CSP_HANDLE 	cspHand;
	const char			*algStr;
	uint32				keyAlg;					// CSSM_ALGID_xxx of the key
	uint32				encrAlg;				// CSSM_ALGID_xxx of encr/decr
	unsigned			currAlg;				// ALG_xxx
	uint32				keySizeInBits;
	int					rtn = 0;
	CSSM_DATA			keyData;
	CSSM_DATA			initVector;
	uint32				minTextSize;
	uint32				rawMode;
	uint32				cookedMode;
	const char			*rawModeStr;
	const char			*cookedModeStr;
	uint32				algBlockSizeBytes;
	
	/*
	 * User-spec'd params
	 */
	CSSM_BOOL	keySizeSpec = CSSM_FALSE;		// false: use rand key size
	unsigned	minAlg = ALG_FIRST;
	unsigned	maxAlg = ALG_LAST;
	unsigned	loops = LOOPS_DEF;
	CSSM_BOOL	verbose = CSSM_FALSE;
	CSSM_BOOL	quiet = CSSM_FALSE;
	unsigned	pauseInterval = 0;
	uint32		padding;
	CSSM_BOOL	bareCsp = CSSM_TRUE;
	unsigned 	maxPtextSize = MAX_DATA_SIZE;
	unsigned	minPtextSize = MIN_DATA_SIZE;
	
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'a':
				if(argp[1] != '=') {
					usage(argv);
				}
				switch(argp[2]) {
					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 'A':
						minAlg = maxAlg = ALG_AES192;
						break;
					case '6':
						minAlg = maxAlg = ALG_AES256;
						break;
					case 'b':
						minAlg = maxAlg = ALG_BFISH;
						break;
					case 'c':
						minAlg = maxAlg = ALG_CAST;
						break;
					case 's':
						minAlg = maxAlg = ALG_ASC;
						break;
					default:
						usage(argv);
				}
				break;
		    case 'l':
				loops = atoi(&argp[2]);
				break;
		    case 'k':
		    	keySizeInBits = atoi(&argp[2]);
		    	keySizeSpec = CSSM_TRUE;
				break;
		    case 'v':
		    	verbose = CSSM_TRUE;
				break;
			case 'D':
				bareCsp = CSSM_FALSE;
				break;
			case 'm':
				maxPtextSize = atoi(&argp[2]);
				break;
			case 'n':
				minPtextSize = atoi(&argp[2]);
				break;
		    case 'q':
		    	quiet = CSSM_TRUE;
				break;
		    case 'p':
		    	pauseInterval = atoi(&argp[2]);;
				break;
		    case 'h':
		    default:
				usage(argv);
		}
	}
	ptext.Data = (uint8 *)CSSM_MALLOC(maxPtextSize);
	if(ptext.Data == NULL) {
		printf("Insufficient heap space\n");
		exit(1);
	}
	/* ptext length set in test loop */
	appSetupCssmData(&ctext1, 2 * maxPtextSize);
	appSetupCssmData(&ctext2, 2 * maxPtextSize);
	appSetupCssmData(&rptext, 2 * maxPtextSize);
	
	keyData.Data = (uint8 *)CSSM_MALLOC(MAX_KEY_SIZE);
	if(keyData.Data == NULL) {
		printf("Insufficient heap space\n");
		exit(1);
	}
	keyData.Length = MAX_KEY_SIZE;

	initVector.Data = (uint8 *)"someStrangeInitVect";	
	
	testStartBanner("contextReuse", argc, argv);

	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... */
		padding		  = CSSM_PADDING_PKCS5;
		rawMode       = RAW_MODE;
		cookedMode    = COOKED_MODE;
		rawModeStr	  = RAW_MODE_STR;
		cookedModeStr = COOKED_MODE_STR;
		padding		  = CSSM_PADDING_PKCS5;
		
		switch(currAlg) {
			case ALG_DES:
				encrAlg = keyAlg = CSSM_ALGID_DES;
				algStr = "DES";
				algBlockSizeBytes  = 8;
				break;
			case ALG_3DES:
				/* currently the only one with different key and encr algs */
				keyAlg  = CSSM_ALGID_3DES_3KEY;
				encrAlg = CSSM_ALGID_3DES_3KEY_EDE;
				algStr = "3DES";
				algBlockSizeBytes = 8;
				break;
			case ALG_RC2:
				encrAlg = keyAlg = CSSM_ALGID_RC2;
				algStr = "RC2";
				algBlockSizeBytes  = 8;
				break;
			case ALG_RC4:
				encrAlg = keyAlg = CSSM_ALGID_RC4;
				algStr = "RC4";
				algBlockSizeBytes = 0;
				rawMode       = RAW_MODE_STREAM;
				cookedMode    = RAW_MODE_STREAM;
				rawModeStr	  = RAW_MODE_STREAM_STR;
				cookedModeStr = RAW_MODE_STREAM_STR;
				break;
			case ALG_RC5:
				encrAlg = keyAlg = CSSM_ALGID_RC5;
				algStr = "RC5";
				algBlockSizeBytes = 8;
				break;
			case ALG_AES:
				encrAlg = keyAlg = CSSM_ALGID_AES;
				algStr = "AES";
				algBlockSizeBytes = 16;
				break;
			case ALG_AES192:
				encrAlg = keyAlg = CSSM_ALGID_AES;
				algStr = "AES192";
				algBlockSizeBytes = 24;
				break;
			case ALG_AES256:
				encrAlg = keyAlg = CSSM_ALGID_AES;
				algStr = "AES256";
				algBlockSizeBytes = 32;
				break;
			case ALG_BFISH:
				encrAlg = keyAlg = CSSM_ALGID_BLOWFISH;
				algStr = "Blowfish";
				algBlockSizeBytes = 8;
				break;
			case ALG_CAST:
				encrAlg = keyAlg = CSSM_ALGID_CAST;
				algStr = "CAST";
				algBlockSizeBytes = 8;
				break;
		}
		
		/* assume for now all algs require IV */
		initVector.Length = algBlockSizeBytes ? algBlockSizeBytes : 8;
		
		if(!quiet || verbose) {
			printf("Testing alg %s\n", algStr);
		}
		for(loop=1; ; loop++) {
			/* mix up raw/cooked */
			uint32 mode;
			const char *modeStr;
			CSSM_BOOL paddingEnabled;
			
			if(loop & 1) {
				mode = rawMode;
				modeStr = rawModeStr;
			}
			else {
				mode = cookedMode;
				modeStr = cookedModeStr;
			}
			switch(mode) {
				case CSSM_ALGMODE_CBCPadIV8:
					paddingEnabled = CSSM_TRUE;
					break;
				default:
					/* all others - right? */
					paddingEnabled = CSSM_FALSE;
					break;
			}
			minTextSize = minPtextSize;	// default
			if(!paddingEnabled && algBlockSizeBytes && (minTextSize < algBlockSizeBytes)) {
				/* i.e., no padding, adjust min ptext size */
				minTextSize = algBlockSizeBytes;
			}
			simpleGenData(&ptext, minTextSize, maxPtextSize);
			if(!paddingEnabled && algBlockSizeBytes) {
				/* align ptext */
				ptext.Length = (ptext.Length / algBlockSizeBytes) * algBlockSizeBytes;
			}
			
			simpleGenData(&keyData, MAX_KEY_SIZE, MAX_KEY_SIZE);

			if(!keySizeSpec) {
				/* random but byte-aligned */
				keySizeInBits = randKeySizeBits(keyAlg, OT_Encrypt);
				keySizeInBits = (keySizeInBits + 7) & ~7;
			}
			/* else constant, spec'd by user, may be 0 (default per alg) */
			if(!quiet) {
			   	if(verbose || ((loop % LOOP_NOTIFY) == 0)) {
					if(algBlockSizeBytes) {
						printf("..loop %d text size %lu keySizeBits %u"
							" blockSize %u mode %s\n",
							loop, (unsigned long)ptext.Length, (unsigned)keySizeInBits,
							(unsigned)algBlockSizeBytes, modeStr);
					}
					else {
						printf("..loop %d text size %lu keySizeBits %u"
							" mode %s\n",
							loop, (unsigned long)ptext.Length, (unsigned)keySizeInBits,
							modeStr);
					}
				}
			}
			
			if(doTest(cspHand,
					&ptext,
					&ctext1,
					&ctext2,
					&rptext,
					&keyData,
					&initVector,
					keyAlg,
					encrAlg,
					mode,
					padding,
					keySizeInBits,
					algBlockSizeBytes,
					quiet)) {
				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(ptext.Data);
	CSSM_FREE(keyData.Data);
	return rtn;
}