dhFulltest.cpp   [plain text]


/*
 * dhFullTest - Diffie-Hellman exerciser. 
 */
#include <stdlib.h>
#include <stdio.h>
#include <Security/cssm.h>
#include "cspwrap.h"
#include "common.h"
#include <security_cdsa_utils/cuFileIo.h>
#include <strings.h>
#include "cspdlTesting.h"

#define USAGE_DEF		"noUsage"
#define LOOPS_DEF		10
#define KEY_SIZE_DEF	512
#define DERIVE_KEY_SIZE	128
#define DERIVE_KEY_ALG	CSSM_ALGID_AES
#define ENCR_ALG		CSSM_ALGID_AES
#define ENCR_MODE		CSSM_ALGMODE_CBCPadIV8
#define ENCR_PADDING	CSSM_PADDING_PKCS7
#define MAX_PTEXT_SIZE	1024

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("Options:\n");
	printf("  k=keySize (default = %d)\n", KEY_SIZE_DEF);
	printf("  l=loops (0=forever)\n");
	printf("  p=pauseInterval (default=0, no pause)\n");
	printf("  D (CSP/DL; default = bare CSP)\n");
	printf("  o=fileName (dump key and param blobs to filename)\n");
	printf("  i=filename (obtain param blobs from filename\n");
	printf("  q(uiet)\n");
	printf("  v(erbose))\n");
	exit(1);
}

/* 
 * Translate blob format to strings.
 */
typedef struct {
	CSSM_KEYBLOB_FORMAT form;
	const char *str;
} BlobDesc;

static const BlobDesc BlobDescs[] = {
	{ CSSM_KEYBLOB_RAW_FORMAT_NONE,  "NONE" },
	{ CSSM_KEYBLOB_RAW_FORMAT_PKCS3, "PKCS3" },
	{ CSSM_KEYBLOB_RAW_FORMAT_PKCS8, "PKCS8" },
	{ CSSM_KEYBLOB_RAW_FORMAT_X509,  "X509" },
};
#define NUM_BLOB_DESCS	(sizeof(BlobDescs) / sizeof(BlobDesc))

static const char *blobFormStr(
	CSSM_KEYBLOB_FORMAT form)
{
	for(unsigned i=0; i<NUM_BLOB_DESCS; i++) {
		const BlobDesc *bdp = &BlobDescs[i];
		if(bdp->form == form) {
			return bdp->str;
		}
	}
	return "***UNKNOWN BLOB FORM""";
}

/*
 * Generate a Diffie-Hellman key pair. Optionally allows specification of 
 * algorithm parameters, and optionally returns algorithm parameters if
 * we generate them. 
 */
static int dhKeyGen(
	CSSM_CSP_HANDLE	cspHand,
	CSSM_KEY_PTR	pubKey,
	CSSM_BOOL		pubIsRef,
	CSSM_KEYBLOB_FORMAT	pubForm,		// NONE, PKCS3, X509
	CSSM_KEYBLOB_FORMAT	expectPubForm,	// PKCS3, X509
	CSSM_KEY_PTR	privKey,
	CSSM_BOOL		privIsRef,
	CSSM_KEYBLOB_FORMAT	privForm,		// NONE, PKCS3, PKCS8
	CSSM_KEYBLOB_FORMAT	expectPrivForm,	// PKCS3, PKCS8
	const CSSM_DATA	*inParams,		// optional 
	CSSM_DATA_PTR	outParams,		// optional, we malloc
	uint32			keySizeInBits,
	CSSM_BOOL		quiet)
{
	CSSM_RETURN		crtn;
	CSSM_CC_HANDLE 	ccHand;
	CSSM_DATA		labelData = { strlen(USAGE_DEF), (uint8 *)USAGE_DEF };
	
	if(inParams && outParams) {
		printf("***dhKeyGen: inParams and outParams are mutually "
			"exclusive.\n");
		return -1;
	}
	memset(pubKey, 0, sizeof(CSSM_KEY));
	memset(privKey, 0, sizeof(CSSM_KEY));
	
	crtn = CSSM_CSP_CreateKeyGenContext(cspHand,
		CSSM_ALGID_DH,
		keySizeInBits,
		NULL,					// Seed
		NULL,					// Salt
		NULL,					// StartDate
		NULL,					// EndDate
		inParams,				// Params, may be NULL
		&ccHand);
	if(crtn) {
		printError("CSSM_CSP_CreateKeyGenContext", crtn);
		return testError(quiet);
	}
	
	if((inParams == NULL) && (outParams != NULL)) {
		/* explicitly generate params and return them to caller */
		outParams->Data = NULL;
		outParams->Length = 0;
		crtn = CSSM_GenerateAlgorithmParams(ccHand, 
			keySizeInBits, outParams);
		if(crtn) {
			printError("CSSM_GenerateAlgorithmParams", crtn);
			return testError(quiet);
		}
	}
	
	if((privForm != CSSM_KEYBLOB_RAW_FORMAT_NONE) && !privIsRef) {
		crtn = AddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_PRIVATE_KEY_FORMAT,
			sizeof(uint32),	
			CAT_Uint32,
			NULL,
			privForm);
		if(crtn) {
			printError("AddContextAttribute(CSSM_ATTRIBUTE_PRIVATE_KEY_"
				"FORMAT)", crtn);
			return crtn;
		}
	}
	if((pubForm != CSSM_KEYBLOB_RAW_FORMAT_NONE) && !pubIsRef) {
		crtn = AddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT,
			sizeof(uint32),	
			CAT_Uint32,
			NULL,
			pubForm);
		if(crtn) {
			printError("AddContextAttribute(CSSM_ATTRIBUTE_PUBLIC_KEY_"
				"FORMAT)", crtn);
			return crtn;
		}
	}

	uint32 privAttr;
	uint32 pubAttr = CSSM_KEYATTR_EXTRACTABLE;
	if(pubIsRef) {
		pubAttr |= CSSM_KEYATTR_RETURN_REF;
	}
	else {
		pubAttr |= CSSM_KEYATTR_RETURN_DATA;
	}
	if(privIsRef) {
		privAttr = CSSM_KEYATTR_RETURN_REF;
	}
	else {
		privAttr = CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE;
	}
	crtn = CSSM_GenerateKeyPair(ccHand,
		/* 
		 * Public key specification. We specify raw key format 
		 * here since we have to have access to the raw public key 
		 * bits in order to perform D-H key exchange.
		 */
		CSSM_KEYUSE_DERIVE,		// only legal use of a Diffie-Hellman key 
		pubAttr,
		&labelData,
		pubKey,
		/* private key specification */
		CSSM_KEYUSE_DERIVE,
		privAttr,
		&labelData,				// same labels
		NULL,					// CredAndAclEntry
		privKey);
	if(crtn) {
		printError("CSSM_GenerateKeyPair", crtn);
			return testError(quiet);
	}
	if(!privIsRef && (privKey->KeyHeader.Format != expectPrivForm)) {
		printf("***Expected priv format %s got %s\n",
			blobFormStr(expectPrivForm),
			blobFormStr(privKey->KeyHeader.Format));
		return testError(quiet);
	}
	if(!pubIsRef && (pubKey->KeyHeader.Format != expectPubForm)) {
		printf("***Expected pub format %s got %s\n",
			blobFormStr(expectPubForm),
			blobFormStr(pubKey->KeyHeader.Format));
		return testError(quiet);
	}
	CSSM_DeleteContext(ccHand);
	return crtn;
}

/*
 * Perform Diffie-Hellman key exchange. 
 * Given "our" private key (in the form of a CSSM_KEY) and "their" public
 * key (in the form of a raw blob of bytes), cook up a symmetric key.
 */
static int dhKeyExchange(
	CSSM_CSP_HANDLE	cspHand,
	CSSM_KEY_PTR	myPrivKey,
	CSSM_KEY_PTR	theirPubKey,
	CSSM_KEY_PTR	derivedKey,				// RETURNED
	uint32			deriveKeySizeInBits,
	CSSM_ALGORITHMS	derivedKeyAlg,
	uint32			derivedKeyUsage,
	uint32			derivedKeyAttr,
	CSSM_BOOL		quiet)
{
	CSSM_RETURN 			crtn;
	CSSM_ACCESS_CREDENTIALS	creds;
	CSSM_CC_HANDLE			ccHand;
	CSSM_DATA				labelData = 
								{ strlen(USAGE_DEF), (uint8 *)USAGE_DEF };
	
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
	memset(derivedKey, 0, sizeof(CSSM_KEY));
	
	crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
		CSSM_ALGID_DH,
		derivedKeyAlg,
		deriveKeySizeInBits,
		&creds,
		myPrivKey,		// BaseKey
		0,				// IterationCount
		0,				// Salt
		0,				// Seed
		&ccHand);
	if(crtn) {
		printError("CSSM_CSP_CreateDeriveKeyContext", crtn);
		return testError(quiet);
	}
	
	/* 
	 * Public key passed in as CSSM_DATA *Param - only if
	 * the pub key is in raw PKCS3 form
	 */
	CSSM_DATA nullParam = {0, NULL};
	CSSM_DATA_PTR paramPtr;
	CSSM_KEYHEADER &hdr = theirPubKey->KeyHeader;
	if((hdr.BlobType == CSSM_KEYBLOB_RAW) &&
	   (hdr.Format == CSSM_KEYBLOB_RAW_FORMAT_PKCS3)) {
		/* simple case */
		paramPtr = &theirPubKey->KeyData;
	}
	else {
		/* add this pub key as a context attr */
		crtn = AddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_PUBLIC_KEY,
			sizeof(CSSM_KEY),	
			CAT_Ptr,
			(void *)theirPubKey,
			0);
		if(crtn) {
			printError("AddContextAttribute(CSSM_ATTRIBUTE_PUBLIC_KEY)",
				crtn);
			return crtn;
		}
		paramPtr = &nullParam;
	}
	
	crtn = CSSM_DeriveKey(ccHand,
		paramPtr,
		derivedKeyUsage,
		derivedKeyAttr,
		&labelData,
		NULL,				// cread/acl
		derivedKey);
	if(crtn) {
		printError("CSSM_DeriveKey", crtn);
	}
	CSSM_DeleteContext(ccHand);
	return crtn;
}

static CSSM_DATA someIv = {16, (uint8 *)"Some enchanted init vector" };

/*
 * Encrypt ptext with myDeriveKey ==> ctext
 * decrypt ctext with theirDeriveKey ==> rptext
 * ensure ptext == rptext
 */  
static int doEncryptTest(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		*ptext,
	CSSM_KEY_PTR		myDeriveKey,
	CSSM_KEY_PTR		theirDeriveKey,
	CSSM_ALGORITHMS		encrAlg,
	uint32				encrMode,
	CSSM_PADDING		encrPadding,
	CSSM_BOOL			quiet)
{
	CSSM_DATA ctext = {0, NULL};
	CSSM_DATA rptext = {0, NULL};
	CSSM_RETURN crtn;
	
	crtn = cspEncrypt(cspHand,
		encrAlg,
		encrMode,
		encrPadding,
		myDeriveKey,
		NULL,			// 2nd key
		0,				// effective key size
		0,				// rounds
		&someIv,
		ptext,
		&ctext,
		CSSM_FALSE);	// mallocCtext
	if(crtn) {
		return testError(quiet);
	}
	crtn = cspDecrypt(cspHand,
		encrAlg,
		encrMode,
		encrPadding,
		theirDeriveKey,
		NULL,			// 2nd key
		0,				// effective key size
		0,				// rounds
		&someIv,
		&ctext,
		&rptext,
		CSSM_FALSE);	// mallocCtext
	if(crtn) {
		return testError(quiet);
	}
	if(!appCompareCssmData(ptext, &rptext)) {
		printf("***DATA MISCOMPARE***\n");
		return testError(quiet);
	}
	appFree(ctext.Data, NULL);
	appFree(rptext.Data, NULL);
	return 0;
}

int doTest(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		*ptext,
	CSSM_BOOL			pubIsRef,
	CSSM_KEYBLOB_FORMAT	pubForm,		// NONE, PKCS3, X509
	CSSM_KEYBLOB_FORMAT	expectPubForm,	// PKCS3, X509
	CSSM_BOOL			privIsRef,
	CSSM_KEYBLOB_FORMAT	privForm,		// NONE, PKCS3, PKCS8
	CSSM_KEYBLOB_FORMAT	expectPrivForm,	// PKCS3, PKCS8
	CSSM_BOOL			sym1IsRef,
	CSSM_BOOL			sym2IsRef,
	const CSSM_DATA		*inParams,		// optional
	CSSM_DATA_PTR		outParams,		// optional
	uint32				keySizeInBits,
	CSSM_BOOL			quiet)
{
	CSSM_KEY	myPriv;
	CSSM_KEY	myPub;
	CSSM_KEY	theirPriv;
	CSSM_KEY	theirPub;
	int			rtn = 0;
	
	/* generate two key pairs */
	if(dhKeyGen(cspHand,
		&myPub,
		pubIsRef,
		pubForm,
		expectPubForm,
		&myPriv,
		privIsRef,
		privForm,
		expectPrivForm,
		inParams,
		outParams,
		keySizeInBits,
		quiet)) {
			return 1;
	}
	
	/* note this MUST match the params either specified or generated 
	 * in previous call */
	if((inParams == NULL) && (outParams == NULL)) {
		printf("***BRRZAP! Must provide a way to match D-H parameters!\n");
		exit(1);
	}
	const CSSM_DATA *theParams = inParams;
	if(theParams == NULL) {
		theParams = outParams;
	}
	if(dhKeyGen(cspHand,
		&theirPub,
		pubIsRef,
		/* let's always use default for this pair */
		CSSM_KEYBLOB_RAW_FORMAT_NONE,
		CSSM_KEYBLOB_RAW_FORMAT_PKCS3,		// we know this is the default
		&theirPriv,
		privIsRef,
		/* let's always use default for this pair */
		CSSM_KEYBLOB_RAW_FORMAT_NONE,
		CSSM_KEYBLOB_RAW_FORMAT_PKCS3,		// we know this is the default
		theParams,
		NULL,				// outParams
		keySizeInBits,
		quiet)) {
			return 1;
	}
		
	/* derive two keys, ensure they match */
	CSSM_KEY myDerive;
	CSSM_KEY theirDerive;
	uint32 myDeriveAttr;
	uint32 theirDeriveAttr;
	if(sym1IsRef) {
		myDeriveAttr = CSSM_KEYATTR_RETURN_REF;
	}
	else {
		myDeriveAttr = 
			CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE;
	}
	if(sym2IsRef) {
		theirDeriveAttr = CSSM_KEYATTR_RETURN_REF;
	}
	else {
		theirDeriveAttr = 
			CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE;
	}
	if(dhKeyExchange(cspHand,
		&myPriv,
		&theirPub,
		&myDerive,
		DERIVE_KEY_SIZE,
		DERIVE_KEY_ALG,
		CSSM_KEYUSE_ENCRYPT,
		myDeriveAttr,
		quiet)) {
			return testError(quiet);
	}
	if(dhKeyExchange(cspHand,
		&theirPriv,
		&myPub,
		&theirDerive,
		DERIVE_KEY_SIZE,
		DERIVE_KEY_ALG,
		CSSM_KEYUSE_DECRYPT,
		theirDeriveAttr,
		quiet)) {
			return testError(quiet);
	}
	
	if(doEncryptTest(cspHand,
		ptext,
		&myDerive,
		&theirDerive,
		ENCR_ALG,
		ENCR_MODE,
		ENCR_PADDING,
		quiet)) {
			return 1;
	}
	
	cspFreeKey(cspHand, &myPub);
	cspFreeKey(cspHand, &myPriv);
	cspFreeKey(cspHand, &theirPub);
	cspFreeKey(cspHand, &theirPriv);
	cspFreeKey(cspHand, &myDerive);
	cspFreeKey(cspHand, &theirDerive);
	return rtn;
}

int main(int argc, char **argv)
{
	int		 				arg;
	char					*argp;
	CSSM_CSP_HANDLE 		cspHand;
	unsigned				loop;
	int 					i;
	CSSM_DATA				inParams = {0, NULL};
	CSSM_DATA				outParams = {0, NULL};
	CSSM_DATA_PTR			inParamPtr = NULL;
	CSSM_DATA_PTR			outParamPtr = NULL;
	CSSM_DATA				ptext;
	CSSM_BOOL				pubIsRef;
	CSSM_BOOL				privIsRef;
	CSSM_BOOL				sym1IsRef;
	CSSM_BOOL				sym2IsRef;
	CSSM_KEYBLOB_FORMAT		pubForm;		// NONE, PKCS3, X509
	CSSM_KEYBLOB_FORMAT		expectPubForm;	// PKCS3, X509
	CSSM_KEYBLOB_FORMAT		privForm;		// NONE, PKCS3, PKCS8
	CSSM_KEYBLOB_FORMAT		expectPrivForm;	// PKCS3, PKCS8
	
	/* user-spec'd parameters */
	unsigned				keySize = KEY_SIZE_DEF;
	unsigned				pauseInterval = 0;
	unsigned				loops = LOOPS_DEF;
	CSSM_BOOL				quiet = CSSM_FALSE;
	CSSM_BOOL				verbose = CSSM_FALSE;
	CSSM_BOOL				bareCsp = CSSM_TRUE;
	char					*inFileName = NULL;
	char					*outFileName = NULL;
	
	for(arg=1; arg<argc; arg++) { 
		argp = argv[arg];
	    switch(argp[0]) {
			case 'k':
				keySize = atoi(&argp[2]);
				break;
		    case 'l':
				loops = atoi(&argp[2]);
				break;
		    case 'p':
				pauseInterval = atoi(&argp[2]);
				break;
			case 'i':
				inFileName = &argp[2];
				break;
			case 'o':
				outFileName = &argp[2];
				break;
			case 'D':
				bareCsp = CSSM_FALSE;
				break;
			case 'q':
				quiet = CSSM_TRUE;
				break;
		    case 'v':
		    	verbose = CSSM_TRUE;
				break;
			default:
				usage(argv);
		}
	}
	
	cspHand = cspDlDbStartup(bareCsp, NULL);
	if(cspHand == 0) {
		exit(1);
	}
	
	if(!bareCsp && 
	   !CSPDL_DSA_GEN_PARAMS &&
	   (inFileName == NULL)) {
		/*
		 * For now, CSPDL can not do gen parameters. This only
		 * works if we're supplied params externally (which most
		 * likely were generated from the bare CSP).
		 */
		printf("*** %s can't run with CSPDL unless you supply DH "
			"parameters.\n", argv[0]);
		exit(1);
	}
	
	/* optionally fetch algorithm parameters from a file */
	if(inFileName) {
		unsigned len;
		int r = readFile(inFileName, &inParams.Data, &len);
		if(r) {
			printf("***Can't read parameters from %s; aborting.\n",
				inFileName);
			exit(1);
		}
		inParams.Length = len;
		/* constant from now on */
		inParamPtr = &inParams;
	}
	else {
		/* first time thru, no user-supplied parameters; generate them and
		 * save in outParams */
		outParamPtr = &outParams;
	}
	
	ptext.Data = (uint8 *)appMalloc(MAX_PTEXT_SIZE, NULL);
	/* ptext length set in test loop */

	printf("Starting %s; args: ", argv[0]);
	for(i=1; i<argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n");
	for(loop=1; ; loop++) {
		if(!quiet) {
			printf("...Loop %d\n", loop);
		}
		simpleGenData(&ptext, 10, MAX_PTEXT_SIZE);
		
		/* mix up raw and ref keys, except for CSPDL which 
		 * requires all ref keys */
		if(bareCsp) {
			pubIsRef  = (loop & 1) ? CSSM_TRUE : CSSM_FALSE;
			privIsRef = (loop & 2) ? CSSM_TRUE : CSSM_FALSE;
			sym1IsRef = (loop & 4) ? CSSM_TRUE : CSSM_FALSE;
			sym2IsRef = (loop & 8) ? CSSM_TRUE : CSSM_FALSE;
		}
		else {
			pubIsRef = privIsRef = sym1IsRef = sym2IsRef = CSSM_TRUE;
		}
		if(!privIsRef) {
			int die = genRand(1,3);
			switch(die) {
				case 1:
					privForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
					expectPrivForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
					break;
				case 2:
					privForm = expectPrivForm = 
						CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
					break;
				case 3:
					privForm = expectPrivForm = 
						CSSM_KEYBLOB_RAW_FORMAT_PKCS8;
					break;
			}
			if(verbose) {
				printf("...privIsRef false; form %s, expectForm %s\n",
					blobFormStr(privForm), blobFormStr(expectPrivForm));
			}
		}
		else {
			privForm = expectPrivForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
			if(verbose) {
				printf("...privIsRef true\n");
			}
		}
		if(!pubIsRef) {
			int die = genRand(1,3);
			switch(die) {
				case 1:
					pubForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
					expectPubForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
					break;
				case 2:
					pubForm = expectPubForm = 
						CSSM_KEYBLOB_RAW_FORMAT_PKCS3;
					break;
				case 3:
					pubForm = expectPubForm = 
						CSSM_KEYBLOB_RAW_FORMAT_X509;
					break;
			}
			if(verbose) {
				printf("...pubIsRef false; form %s, expectForm %s\n",
					blobFormStr(pubForm), blobFormStr(expectPubForm));
			}
		}
		else {
			pubForm = expectPubForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
			if(verbose) {
				printf("...pubIsRef true\n");
			}
		}
		i = doTest(cspHand, 
			&ptext,
			pubIsRef,
			pubForm,
			expectPubForm,
			privIsRef,
			privForm,
			expectPrivForm,
			sym1IsRef,
			sym2IsRef,
			inParamPtr, 
			outParamPtr, 
			keySize, 
			quiet);
		if(i) {
			break;
		}
		if(loop == 1) {
			/* first time thru */
			if(outFileName) {
				/* save parameters for another run */
				i = writeFile(outFileName, outParams.Data, outParams.Length);
				if(i) {
					printf("***Error writing params to %s; continuing.\n",
						outFileName);
				}
				else {
					if(!quiet) {
						printf("...wrote %lu bytes to %s\n", 
							outParams.Length, outFileName);
					}
				}
			}
			if(!inFileName) {
				/* from now on, use the parameters we just generated */
				inParamPtr = &outParams;
			}
			/* and in any case don't fetch any more params */
			outParamPtr = NULL;
		}
		if(loops && (loop == loops)) {
			break;
		}
		if(pauseInterval && ((loop % pauseInterval) == 0)) {
			char inch;
			
			fpurge(stdin);
			printf("Hit CR to proceed, q to quit: ");
			inch = getchar();
			if(inch == 'q') {
				break;
			}
		}
	}
	appFree(ptext.Data, NULL);
	CSSM_ModuleDetach(cspHand);
	if(!quiet) {
		printf("OK\n");
	}
	return 0;
}