ccHmacClone.cpp   [plain text]


/* 
 * ccHmacClone - test CommonCrypto's clone context for HMAC.  
 *
 * Written 3/30/2006 by Doug Mitchell. 
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "common.h"
#include <string.h>
#include <CommonCrypto/CommonHMAC.h>

/*
 * Defaults.
 */
#define LOOPS_DEF		200

#define MIN_DATA_SIZE	8
#define MAX_DATA_SIZE	10000			/* bytes */
#define MIN_KEY_SIZE	1
#define MAX_KEY_SIZE	256				/* bytes */
#define LOOP_NOTIFY		20

/*
 * Enumerate algs our own way to allow iteration.
 */
typedef enum {
	ALG_MD5 = 1,
	ALG_SHA1,
	ALG_SHA224,
	ALG_SHA256,
	ALG_SHA384,
	ALG_SHA512,
} HmacAlg;
#define ALG_FIRST			ALG_MD5
#define ALG_LAST			ALG_SHA512

#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 (5=MD5; s=SHA1; 4=SHA224; 2=SHA256; 3=SHA384; 1=SHA512; default=all)\n");
	printf("   l=loops (default=%d; 0=forever)\n", LOOPS_DEF);
	printf("   k=keySizeInBytes\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("   s (all ops single-shot, not staged)\n");
	printf("   S (all ops staged)\n");
	printf("   z (keys and plaintext all zeroes)\n");
	printf("   v(erbose)\n");
	printf("   q(uiet)\n");
	printf("   h(elp)\n");
	exit(1);
}

/* 
 * Given an initialized CCHmacContext, feed it some data and get the result.
 */
static void hmacRun(
	CCHmacContext *ctx,
	bool randomUpdates,
	const unsigned char *ptext,
	size_t ptextLen,
	void *dataOut)
{
	while(ptextLen) {
		size_t thisMoveIn;			/* input to CCryptUpdate() */
		
		if(randomUpdates) {
			thisMoveIn = genRand(1, ptextLen);
		}
		else {
			thisMoveIn = ptextLen;
		}
		logSize(("###ptext segment (1) len %lu\n", (unsigned long)thisMoveIn)); 
		CCHmacUpdate(ctx, ptext, thisMoveIn);
		ptext	 += thisMoveIn;
		ptextLen -= thisMoveIn;
	}
	CCHmacFinal(ctx, dataOut);
}


#define MAX_HMAC_SIZE	CC_SHA512_DIGEST_LENGTH

static int doTest(const uint8_t *ptext,
	size_t ptextLen,
	CCHmacAlgorithm hmacAlg,			
	uint32 keySizeInBytes,
	bool stagedOrig,
	bool stagedClone,
	bool quiet,
	bool verbose)
{
	uint8_t			keyBytes[MAX_KEY_SIZE];
	uint8_t			hmacOrig[MAX_HMAC_SIZE];
	uint8_t			hmacClone[MAX_HMAC_SIZE];
	int				rtn = 0;
	CCHmacContext	ctxOrig;
	CCHmacContext	ctxClone;
	unsigned		die;		/* 0..3 indicates when to clone */
	unsigned		loopNum = 0;
	size_t			hmacLen;
	bool			didClone = false;
	
	switch(hmacAlg) {
		case kCCHmacAlgSHA1:
			hmacLen = CC_SHA1_DIGEST_LENGTH;
			break;
		case kCCHmacAlgMD5:
			hmacLen = CC_MD5_DIGEST_LENGTH;
			break;
		case kCCHmacAlgSHA224:
			hmacLen = CC_SHA224_DIGEST_LENGTH;
			break;
		case kCCHmacAlgSHA256:
			hmacLen = CC_SHA256_DIGEST_LENGTH;
			break;
		case kCCHmacAlgSHA384:
			hmacLen = CC_SHA384_DIGEST_LENGTH;
			break;
		case kCCHmacAlgSHA512:
			hmacLen = CC_SHA512_DIGEST_LENGTH;
			break;
		default:
			printf("***BRRRZAP!\n");
			exit(1);
	}
	
	/* random key */
	appGetRandomBytes(keyBytes, keySizeInBytes);
	
	/* cook up first context */
	CCHmacInit(&ctxOrig, hmacAlg, keyBytes, keySizeInBytes);
	
	/* roll the dice */
	die = genRand(0, 3);
	
	/* 
	 * In this loop we do updates to the ctxOrig up until we
	 * clone it, then we use hmacRun to finish both of them.
	 */
	while(ptextLen) {
		if((die == loopNum) || !stagedOrig) {
			/* make the clone now */
			if(verbose) {
				printf("   ...cloning at loop %u\n", loopNum);
			}
			ctxClone = ctxOrig;
			didClone = true;
			
			/* do all of the clone's updates and final here */
			hmacRun(&ctxClone, stagedClone, ptext, ptextLen, hmacClone);
			
			/* now do all remaining updates and final for original */
			hmacRun(&ctxOrig, stagedOrig, ptext, ptextLen, hmacOrig);
			
			/* we're all done, time to check the HMAC values */
			break;
		}	/* making clone */
		
		/* feed some data into cryptorOrig */
		size_t thisMove;
		if(stagedOrig) {
			thisMove = genRand(1, ptextLen);
		}
		else {
			thisMove = ptextLen;
		}
		logSize(("###ptext segment (2) len %lu\n", (unsigned long)thisMove)); 
		CCHmacUpdate(&ctxOrig, ptext, thisMove);
		ptext += thisMove;
		ptextLen -= thisMove;
		loopNum++;
	}
		
	/* 
	 * It's possible to get here without cloning or doing any finals,
	 * if we ran thru multiple updates and finished ptextLen for cryptorOrig
	 * before we hit the cloning spot.
	 */
	if(!didClone) {
		if(!quiet) {
			printf("...ctxOrig finished before we cloned; skipping test\n");
		}
		return 0;
	}
	if(memcmp(hmacOrig, hmacClone, hmacLen)) {
		printf("***data miscompare\n");
		rtn = testError(quiet);
	}
	return rtn;
}

bool isBitSet(unsigned bit, unsigned word) 
{
	if(bit > 31) {
		printf("We don't have that many bits\n");
		exit(1);
	}
	unsigned mask = 1 << bit;
	return (word & mask) ? true : false;
}

int main(int argc, char **argv)
{
	int					arg;
	char				*argp;
	unsigned			loop;
	uint8				*ptext;
	size_t				ptextLen;
	bool				stagedOrig;
	bool				stagedClone;
	const char			*algStr;
	CCHmacAlgorithm		hmacAlg;	
	int					i;
	int					currAlg;		// ALG_xxx
	uint32				keySizeInBytes;
	int					rtn = 0;
	
	/*
	 * User-spec'd params
	 */
	bool		keySizeSpec = false;		// false: use rand key size
	HmacAlg		minAlg = ALG_FIRST;
	HmacAlg		maxAlg = ALG_LAST;
	unsigned	loops = LOOPS_DEF;
	bool		verbose = false;
	size_t		minPtextSize = MIN_DATA_SIZE;
	size_t		maxPtextSize = MAX_DATA_SIZE;
	bool		quiet = false;
	unsigned	pauseInterval = 0;
	bool		stagedSpec = false;		// true means caller fixed stagedOrig and stagedClone
	
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'a':
				if(argp[1] != '=') {
					usage(argv);
				}
				switch(argp[2]) {
					case '5':
						minAlg = maxAlg = ALG_MD5;
						break;
					case 's':
						minAlg = maxAlg = ALG_SHA1;
						break;
					case '4':
						minAlg = maxAlg = ALG_SHA224;
						break;
					case '2':
						minAlg = maxAlg = ALG_SHA256;
						break;
					case '3':
						minAlg = maxAlg = ALG_SHA384;
						break;
					case '1':
						minAlg = maxAlg = ALG_SHA512;
						break;
					default:
						usage(argv);
				}
				break;
		    case 'l':
				loops = atoi(&argp[2]);
				break;
		    case 'n':
				minPtextSize = atoi(&argp[2]);
				break;
		    case 'm':
				maxPtextSize = atoi(&argp[2]);
				break;
		    case 'k':
		    	keySizeInBytes = atoi(&argp[2]);
		    	keySizeSpec = true;
				break;
		    case 'v':
		    	verbose = true;
				break;
		    case 'q':
		    	quiet = true;
				break;
		    case 'p':
		    	pauseInterval = atoi(&argp[2]);;
				break;
		    case 's':
		    	stagedOrig = stagedClone = false;
				stagedSpec = true;
				break;
		    case 'S':
		    	stagedOrig = stagedClone = true;
				stagedSpec = true;
				break;
		    case 'h':
		    default:
				usage(argv);
		}
	}
	ptext = (uint8 *)malloc(maxPtextSize);
	if(ptext == NULL) {
		printf("Insufficient heap space\n");
		exit(1);
	}
	/* ptext length set in test loop */
	
	printf("Starting ccHmacClone; args: ");
	for(i=1; i<argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n");
	
	if(pauseInterval) {
		fpurge(stdin);
		printf("Top of test; hit CR to proceed: ");
		getchar();
	}

	for(currAlg=minAlg; currAlg<=maxAlg; currAlg++) {
		/* when zero, set size randomly or per user setting */
		switch(currAlg) {
			case ALG_MD5:
				hmacAlg = kCCHmacAlgMD5;
				algStr = "HMACMD5";
				break;
			case ALG_SHA1:
				hmacAlg = kCCHmacAlgSHA1;
				algStr = "HMACSHA1";
				break;
			case ALG_SHA224:
				hmacAlg = kCCHmacAlgSHA224;
				algStr = "HMACSHA224";
				break;
			case ALG_SHA256:
				hmacAlg = kCCHmacAlgSHA256;
				algStr = "HMACSHA256";
				break;
			case ALG_SHA384:
				hmacAlg = kCCHmacAlgSHA384;
				algStr = "HMACSHA384";
				break;
			case ALG_SHA512:
				hmacAlg = kCCHmacAlgSHA512;
				algStr = "HMACSHA512";
				break;
			default:
				printf("***BRRZAP!\n");
				exit(1);
		}
		if(!quiet || verbose) {
			printf("Testing alg %s\n", algStr);
		}
		for(loop=1; ; loop++) {
			ptextLen = genRand(minPtextSize, maxPtextSize);
			appGetRandomBytes(ptext, ptextLen);
			if(!keySizeSpec) {
				keySizeInBytes = genRand(MIN_KEY_SIZE, MAX_KEY_SIZE);
			}
			
			/* per-loop settings */
			if(!stagedSpec) {
				stagedOrig = isBitSet(1, loop);
				stagedClone = isBitSet(2, loop);
			}
			
			if(!quiet) {
			   	if(verbose || ((loop % LOOP_NOTIFY) == 0)) {
					printf("..loop %d ptextLen %4lu keySize %3lu stagedOrig=%d "
						"stagedClone=%d\n", 
						loop, (unsigned long)ptextLen, (unsigned long)keySizeInBytes,
						(int)stagedOrig, (int)stagedClone);
				}
			}
			
			if(doTest(ptext, ptextLen,
					hmacAlg, keySizeInBytes,
					stagedOrig, stagedClone, quiet, verbose)) {
				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:
	if(pauseInterval) {
		fpurge(stdin);
		printf("ModuleDetach/Unload complete; hit CR to exit: ");
		getchar();
	}
	if((rtn == 0) && !quiet) {
		printf("%s test complete\n", argv[0]);
	}
	free(ptext);
	return rtn;
}