caVerify.cpp   [plain text]


/* Copyright (c) 1998,2003-2006,2008 Apple Inc.
 *
 * caVerify.cpp
 *
 * Verify proper detection of basicConstraints.cA
 */

#include <utilLib/common.h>
#include <utilLib/cspwrap.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <clAppUtils/CertBuilderApp.h>
#include <clAppUtils/clutils.h>
#include <clAppUtils/timeStr.h>
#include <clAppUtils/tpUtils.h>
#include <security_cdsa_utils/cuPrintCert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <Security/cssm.h>
#include <Security/x509defs.h>
#include <Security/oidsattr.h>
#include <Security/oidscert.h>
#include <Security/oidsalg.h>
#include <Security/certextensions.h>
#include <Security/cssmapple.h>
#include <string.h>

/* define nonzero to build on Puma */
#define PUMA_BUILD			0

/* default key and signature algorithm */
#define SIG_ALG_DEFAULT		CSSM_ALGID_SHA1WithRSA
#define KEY_ALG_DEFAULT		CSSM_ALGID_RSA

#define NUM_CERTS_DEF		5		/* default is random from 2 to this */
#define NUM_LOOPS_DEF		100

#if		PUMA_BUILD
extern "C" {
	void cssmPerror(const char *how, CSSM_RETURN error);
}
#endif	/* PUMA_BUILD */

static void usage(char **argv)
{
	printf("Usage: %s [options]\n", argv[0]);
	printf("Options:\n");
	printf("   n=numCerts (default=random from 2 to %d)\n", NUM_CERTS_DEF);
	printf("   a=alg  where alg is s(RSA/SHA1), 5(RSA/MD5), f(FEE/MD5), "
			"F(FEE/SHA1), e(ECDSA), E(ANSI/ECDSA), 6(ECDSA/SHA256)\n");
	printf("   k=keySizeInBits\n");
	printf("   c (dump certs on error)\n");
	printf("   l=numLoops (default = %d)\n", NUM_LOOPS_DEF);
	exit(1);
}

void showCerts(
	const CSSM_DATA *certs,
	unsigned numCerts)
{
	unsigned i;
	
	for(i=0; i<numCerts; i++) {
		printf("======== cert %d ========\n", i);
		printCert(certs[i].Data, certs[i].Length, CSSM_FALSE);
		printf("\n\n");
	}
}

void writeCerts(
	const CSSM_DATA *certs,
	unsigned numCerts)
{
	unsigned i;
	char fileName[80];
	
	for(i=0; i<numCerts; i++) {
		sprintf(fileName, "caVerifyCert%u.cer", i);
		writeFile(fileName, certs[i].Data, certs[i].Length);
		printf("....wrote %lu bytes to %s\n", certs[i].Length, fileName);
	}
}

/* 
 * Generate a cert chain using specified key pairs and extensions.
 * The last cert in the chain (certs[numCerts-1]) is a root cert, 
 * self-signed. 
 */
static CSSM_RETURN tpGenCertsExten(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_CL_HANDLE		clHand,
	CSSM_ALGORITHMS		sigAlg,			/* CSSM_ALGID_SHA1WithRSA, etc. */
	unsigned			numCerts,
	/* per-cert arrays */
	const char 			*nameBase,		/* C string */
	CSSM_KEY_PTR		pubKeys,		/* array of public keys */
	CSSM_KEY_PTR		privKeys,		/* array of private keys */
	CSSM_X509_EXTENSION	**extensions,
	unsigned			*numExtensions,
	CSSM_DATA_PTR		certs,			/* array of certs RETURNED here */
	const char			*notBeforeStr,	/* from genTimeAtNowPlus() */
	const char			*notAfterStr)	/* from genTimeAtNowPlus() */

{
	int 				dex;
	CSSM_RETURN			crtn;
	CSSM_X509_NAME		*issuerName = NULL;
	CSSM_X509_NAME		*subjectName = NULL;
	CSSM_X509_TIME		*notBefore;			// UTC-style "not before" time
	CSSM_X509_TIME		*notAfter;			// UTC-style "not after" time
	CSSM_DATA_PTR		rawCert = NULL;		// from CSSM_CL_CertCreateTemplate
	CSSM_DATA			signedCert;			// from CSSM_CL_CertSign	
	uint32				rtn;
	CSSM_KEY_PTR		signerKey;			// signs the cert
	CSSM_CC_HANDLE		signContext;
	char				nameStr[100];
	CSSM_DATA_PTR		thisCert;			// ptr into certs[]
	CB_NameOid			nameOid;
	
	nameOid.oid = &CSSMOID_OrganizationName;	// const
	nameOid.string = nameStr;
	
	/* main loop - once per keypair/cert - starting at end/root */
	for(dex=numCerts-1; dex>=0; dex--) {
		thisCert = &certs[dex];
		
		thisCert->Data = NULL;
		thisCert->Length = 0;
		
		sprintf(nameStr, "%s%04d", nameBase, dex);
		if(issuerName == NULL) {
			/* last (root) cert - subject same as issuer */
			issuerName = CB_BuildX509Name(&nameOid, 1); 
			/* self-signed */
			signerKey = &privKeys[dex];
		}
		else {
			/* previous subject becomes current issuer */
			CB_FreeX509Name(issuerName);
			issuerName = subjectName;
			signerKey = &privKeys[dex+1];
		}
		subjectName = CB_BuildX509Name(&nameOid, 1);
		if((subjectName == NULL) || (issuerName == NULL)) {
			printf("Error creating X509Names\n");
			crtn = CSSMERR_CSSM_MEMORY_ERROR;
			break;
		}
		
		/* 
		 * not before/after in Y2k-compliant generalized time format.
		 * These come preformatted from our caller. 
		 */
		notBefore = CB_BuildX509Time(0, notBeforeStr);
		notAfter  = CB_BuildX509Time(0, notAfterStr);

		/* 
		 * Cook up cert template 
		 * Note serial number would be app-specified in real world
		 */
		rawCert = CB_MakeCertTemplate(clHand,
			0x12345 + dex,	// serial number
			issuerName,
			subjectName,
			notBefore,
			notAfter,
			&pubKeys[dex],
			sigAlg,
			NULL,						// subj unique ID
			NULL,						// issuer unique ID
			extensions[dex],			// extensions
			numExtensions[dex]);		// numExtensions
	
		if(rawCert == NULL) {
			crtn = CSSM_ERRCODE_INTERNAL_ERROR;
			break;
		}

		/* Free the stuff we allocd to get here */
		CB_FreeX509Time(notBefore);
		CB_FreeX509Time(notAfter);

		/**** sign the cert ****/
		/* 1. get a signing context */
		crtn = CSSM_CSP_CreateSignatureContext(cspHand,
				sigAlg,
				NULL,			// no passphrase for now
				signerKey,
				&signContext);
		if(crtn) {
			printError("CreateSignatureContext", crtn);
			break;
		}
		
		/* 2. use CL to sign the cert */ 
		signedCert.Data = NULL;
		signedCert.Length = 0;
		crtn = CSSM_CL_CertSign(clHand,
			signContext,
			rawCert,			// CertToBeSigned
			NULL,				// SignScope per spec
			0,					// ScopeSize per spec
			&signedCert);
		if(crtn) {
			printError("CSSM_CL_CertSign", crtn);
			break;
		}
		
		/* 3. delete signing context */
		crtn = CSSM_DeleteContext(signContext);
		if(crtn) {
			printError("CSSM_DeleteContext", crtn);
			break;
		}

		/* 
		 * CSSM_CL_CertSign() returned us a mallocd CSSM_DATA. Copy
		 * its fields to caller's cert. 
		 */
		certs[dex] = signedCert;
		
		/* and the raw unsigned cert as well */
		appFreeCssmData(rawCert, CSSM_TRUE);
		rtn = 0;
	}
	
	/* free resources */
	if(issuerName != NULL) {
		CB_FreeX509Name(issuerName);
	}
	if(subjectName != NULL) {
		CB_FreeX509Name(subjectName);
	}
	return crtn;
}

static int doTest(
	CSSM_CSP_HANDLE	cspHand,		// CSP handle
	CSSM_CL_HANDLE	clHand,			// CL handle
	CSSM_TP_HANDLE	tpHand,			// TP handle
	unsigned		numCerts,		// >= 2
	CSSM_KEY_PTR	pubKeys,
	CSSM_KEY_PTR	privKeys,
	CSSM_ALGORITHMS	sigAlg,
	CSSM_BOOL		expectFail,
	CSSM_BOOL		dumpCerts,
	CSSM_BOOL		quiet)
{
	CSSM_DATA_PTR		certs;
	CSSM_X509_EXTENSION	**extens;
	unsigned			*numExtens;
	char 				*notBeforeStr = genTimeAtNowPlus(0);
	char 				*notAfterStr = genTimeAtNowPlus(10000);
	unsigned			certDex;
	CE_BasicConstraints	*bc;
	CSSM_X509_EXTENSION *thisExten;
	CE_BasicConstraints *thisBc;
	const char			*failMode = "not set - internal error";
	CSSM_RETURN			crtn;
	unsigned 			badCertDex = 0;
	
	certs = (CSSM_DATA_PTR)malloc(sizeof(CSSM_DATA) * numCerts);
	memset(certs, 0, sizeof(CSSM_DATA) * numCerts);
	
	/*
	 * For now just zero or one extension per cert - basicConstraints. 
	 * Eventually we'll want to test keyUsage as well.
	 */
	extens = (CSSM_X509_EXTENSION **)malloc(sizeof(CSSM_X509_EXTENSION *) * numCerts);
	memset(extens, 0, sizeof(CSSM_X509_EXTENSION *) * numCerts);
	numExtens = (unsigned *)malloc(sizeof(unsigned) * numCerts);
	bc = (CE_BasicConstraints *)malloc(sizeof(CE_BasicConstraints) * numCerts);
	
	/*
	 * Set up all extensions for success
	 */
	for(certDex=0; certDex<numCerts; certDex++) {
		extens[certDex] = (CSSM_X509_EXTENSION *)malloc(sizeof(CSSM_X509_EXTENSION));
		numExtens[certDex] = 1;
		thisExten = extens[certDex];
		thisBc = &bc[certDex];
		
		thisExten->extnId = CSSMOID_BasicConstraints;
		thisExten->critical = CSSM_TRUE;
		thisExten->format = CSSM_X509_DATAFORMAT_PARSED;
		thisExten->value.parsedValue = thisBc;
		thisExten->BERvalue.Data = NULL;
		thisExten->BERvalue.Length = 0;

		if(certDex == 0) {
			/* leaf - flip coin to determine presence of basicConstraints */
			int coin = genRand(1,2);
			if(coin == 1) {
				/* basicConstraints, !cA */
				thisBc->cA = CSSM_FALSE;
				thisBc->pathLenConstraintPresent = CSSM_FALSE;
				thisBc->pathLenConstraint = 0;
			}
			else {
				/* !basicConstraints, !cA by default */
				numExtens[certDex] = 0;
			}
		}
		else if(certDex == (numCerts-1)) {
			/* root - flip coin to determine presence of basicConstraints */
			int coin = genRand(1,2);
			if(coin == 1) {
				/* basicConstraints, cA */
				thisBc->cA = CSSM_TRUE;
				/* flip coin to determine present of pathLenConstraint */
				coin = genRand(1,2);
				if(coin == 1) {
					thisBc->pathLenConstraintPresent = CSSM_FALSE;
					thisBc->pathLenConstraint = 0;
				}
				else {
					thisBc->pathLenConstraintPresent = CSSM_TRUE;
					thisBc->pathLenConstraint = genRand(certDex-1, numCerts+1);
				}
			}
			else {
				/* !basicConstraints, cA by default */
				numExtens[certDex] = 0;
			}
		}
		else {
			/* intermediate = cA required */
			thisBc->cA = CSSM_TRUE;
			/* flip coin to determine presence of pathLenConstraint */
			int coin = genRand(1,2);
			if(coin == 1) {
				thisBc->pathLenConstraintPresent = CSSM_FALSE;
				thisBc->pathLenConstraint = 0;
			}
			else {
				thisBc->pathLenConstraintPresent = CSSM_TRUE;
				thisBc->pathLenConstraint = genRand(certDex-1, numCerts+1);
			}
		}
	}
	
	if(expectFail) {
		/* introduce a failure */
		if(numCerts == 2) {
			/* only possible failure is explicit !cA in root */
			/* don't assume presence of BC exten */
			badCertDex = 1;
			thisExten = extens[badCertDex];
			thisBc = &bc[badCertDex];
			thisBc->cA = CSSM_FALSE;
			thisBc->pathLenConstraintPresent = CSSM_FALSE;
			bc->pathLenConstraint = 0;
			numExtens[badCertDex] = 1;
			failMode = "Explicit !cA in root";
		}
		else {
			/* roll the dice to select an intermediate cert */
			badCertDex = genRand(1, numCerts-2);
			thisExten = extens[badCertDex];
			if((thisExten == NULL) || (numExtens[badCertDex] == 0)) {
				printf("***INTERNAL SCREWUP\n");
				exit(1);
			}
			thisBc = &bc[badCertDex];
			
			/* 
			 * roll die: fail by 
			 *   -- no BasicConstraints
			 *   -- !cA
			 *   -- bad pathLenConstraint 
			 */
			int die = genRand(1,3);
			if((die == 1) &&
			   (badCertDex != 1)) {		// last cA doesn't need pathLenConstraint 
				thisBc->pathLenConstraintPresent = CSSM_TRUE;
				thisBc->pathLenConstraint = badCertDex - 2;	// one short
				failMode = "Short pathLenConstraint";
			}
			else if(die == 2) {
				thisBc->cA = CSSM_FALSE;
				failMode = "Explicit !cA in intermediate";
			}
			else {
				/* no extension */
				numExtens[badCertDex] = 0;
				failMode = "No BasicConstraints in intermediate";
			}
		}
	}
	if(!quiet && expectFail) {
		printf("   ...bad cert at index %d: %s\n", badCertDex, failMode);
	}
	
	/* here we go - create cert chain */
	crtn = tpGenCertsExten(cspHand,
		clHand,
		sigAlg,
		numCerts,
		"caVerify",		// nameBase
		pubKeys,
		privKeys,
		extens,
		numExtens,
		certs,
		notBeforeStr,
		notAfterStr);
	if(crtn) {
		printError("tpGenCertsExten", crtn);
		return crtn;	// and leak like crazy 
	}
	
	CSSM_CERTGROUP  cgrp;
	memset(&cgrp, 0, sizeof(CSSM_CERTGROUP));
	cgrp.NumCerts = numCerts;
	#if		PUMA_BUILD
	cgrp.CertGroupType = CSSM_CERTGROUP_ENCODED_CERT;
	#else
	/* Jaguar */
	cgrp.CertGroupType = CSSM_CERTGROUP_DATA;
	#endif	/* PUMA_BUILD */
	cgrp.CertType = CSSM_CERT_X_509v3;
	cgrp.CertEncoding = CSSM_CERT_ENCODING_DER; 
	cgrp.GroupList.CertList = certs;
	
	#if		PUMA_BUILD
	crtn = tpCertGroupVerify(tpHand,
		clHand,
		cspHand,
		NULL,						// DlDbList
		&CSSMOID_APPLE_X509_BASIC,	// SSL requires built-in root match 
		&cgrp,
		/* pass in OUR ROOT as anchors */
		(CSSM_DATA_PTR)&certs[numCerts-1],		// anchorCerts
		1,
		CSSM_TP_STOP_ON_POLICY, 
		CSSM_FALSE,					// allowExpired
		NULL);						// vfyResult
	#else
	/* Jaguar */
	crtn = tpCertGroupVerify(tpHand,
		clHand,
		cspHand,
		NULL,						// DlDbList
		&CSSMOID_APPLE_TP_SSL,		// may want to parameterize this
		NULL,						// fieldOpts for server name
		NULL,						// actionDataPtr for allow expired
		NULL,						// policyOpts
		&cgrp,
		/* pass in OUR ROOT as anchors */
		(CSSM_DATA_PTR)&certs[numCerts-1],		// anchorCerts
		1,
		CSSM_TP_STOP_ON_POLICY, 
		NULL,						// cssmTimeStr
		NULL);						// vfyResult
	#endif	/* PUMA_BUILD */
	if(expectFail) {
		if(crtn != CSSMERR_TP_VERIFY_ACTION_FAILED) {
			cssmPerror("***Expected error TP_VERIFY_ACTION_FAILED; got ", crtn);
			printf("   Expected failure due to %s\n", failMode);
			if(dumpCerts) {
				showCerts(certs, numCerts);
				writeCerts(certs, numCerts);
			}
			return testError(quiet);
		}
	}
	else if(crtn) {
		cssmPerror("Unexpected failure on tpCertGroupVerify", crtn);
		if(dumpCerts) {
			showCerts(certs, numCerts);
		}
		return testError(quiet);
	}
		
	/* clean up */
	return 0;
}

int main(int argc, char **argv)
{
	CSSM_CL_HANDLE	clHand;			// CL handle
	CSSM_CSP_HANDLE	cspHand;		// CSP handle
	CSSM_TP_HANDLE	tpHand;
	CSSM_KEY_PTR	pubKeys;
	CSSM_KEY_PTR	privKeys;
	CSSM_RETURN		crtn;
	int				arg;
	unsigned		certDex;
	unsigned		loopNum;
	unsigned		maxCerts;
	
	/* user-spec'd variables */
	CSSM_ALGORITHMS	keyAlg = KEY_ALG_DEFAULT;
	CSSM_ALGORITHMS sigAlg = SIG_ALG_DEFAULT;
	uint32			keySizeInBits = CSP_KEY_SIZE_DEFAULT;
	unsigned 		numCerts = 0;			// means random per loop
	unsigned 		numLoops = NUM_LOOPS_DEF;
	CSSM_BOOL		quiet = CSSM_FALSE;
	CSSM_BOOL		dumpCerts = CSSM_FALSE;
	
	for(arg=1; arg<argc; arg++) {
		switch(argv[arg][0]) {
			case 'a':
				if((argv[arg][1] == '\0') || (argv[arg][2] == '\0')) {
					usage(argv);
				}
				switch(argv[arg][2]) {
					case 's':
						keyAlg = CSSM_ALGID_RSA;
						sigAlg = CSSM_ALGID_SHA1WithRSA;
						break;
					case '5':
						keyAlg = CSSM_ALGID_RSA;
						sigAlg = CSSM_ALGID_MD5WithRSA;
						break;
					case 'f':
						keyAlg = CSSM_ALGID_FEE;
						sigAlg = CSSM_ALGID_FEE_MD5;
						break;
					case 'F':
						keyAlg = CSSM_ALGID_FEE;
						sigAlg = CSSM_ALGID_FEE_SHA1;
						break;
					case 'e':
						keyAlg = CSSM_ALGID_FEE;
						sigAlg = CSSM_ALGID_SHA1WithECDSA;
						break;
					case 'E':
						keyAlg = CSSM_ALGID_ECDSA;
						sigAlg = CSSM_ALGID_SHA1WithECDSA;
						break;
					case '6':
						keyAlg = CSSM_ALGID_ECDSA;
						sigAlg = CSSM_ALGID_SHA256WithECDSA;
						break;
					default:
						usage(argv);
				}
				break;
		    case 'k':
				keySizeInBits = atoi(&argv[arg][2]);
				break;
		    case 'l':
				numLoops = atoi(&argv[arg][2]);
				break;
		    case 'n':
				numCerts = atoi(&argv[arg][2]);
				break;
		    case 'c':
				dumpCerts = CSSM_TRUE;
				break;
		    case 'q':
				quiet = CSSM_TRUE;
				break;
			default:
				usage(argv);
		}
	}
	
	/* connect to CL, TP, and CSP */
	clHand = clStartup();
	if(clHand == 0) {
		return 0;
	}
	tpHand = tpStartup();
	if(tpHand == 0) {
		return 0;
	}
	cspHand = cspStartup();
	if(cspHand == 0) {
		return 0;
	}

	if(numCerts == 0) {
		maxCerts = NUM_CERTS_DEF;	// random, this is the max $
	}
	else {
		maxCerts = numCerts;		// user-specd
	}
	
	printf("Starting caVerify; args: ");
	for(unsigned i=1; i<(unsigned)argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n");

	/* cook up maxCerts key pairs */
	if(!quiet) {
		printf("generating key pairs...\n");
	}
	pubKeys  = (CSSM_KEY_PTR)malloc(sizeof(CSSM_KEY) * maxCerts);
	privKeys = (CSSM_KEY_PTR)malloc(sizeof(CSSM_KEY) * maxCerts);
	for(certDex=0; certDex<maxCerts; certDex++) {
		crtn = cspGenKeyPair(cspHand,
			keyAlg,
			"a key pair",
			9,
			keySizeInBits,
			&pubKeys[certDex],
			CSSM_FALSE,			// pubIsRef - should work both ways, but not yet
			CSSM_KEYUSE_VERIFY,
			CSSM_KEYBLOB_RAW_FORMAT_NONE,
			&privKeys[certDex],
			CSSM_TRUE,			// privIsRef - doesn't matter
			CSSM_KEYUSE_SIGN,
			CSSM_KEYBLOB_RAW_FORMAT_NONE,
			CSSM_FALSE);
		if(crtn) {
			printError("cspGenKeyPair", crtn);
			printf("***error generating key pair. Aborting.\n");
			exit(1);
		}
	}

	for(loopNum=0; loopNum<numLoops; loopNum++) {
		unsigned thisNumCerts;
		
		/* random: num certs and whether this loop is to test a failure */
		if(numCerts) {
			thisNumCerts = numCerts;
		}
		else {
			thisNumCerts = genRand(2, NUM_CERTS_DEF);
		}
		int coin = genRand(1,2);
		CSSM_BOOL expectFail = (coin == 1) ? CSSM_TRUE : CSSM_FALSE;
		if(!quiet) {
			printf("...loop %d numCerts %u expectFail %s\n", loopNum,
				thisNumCerts, expectFail ? "TRUE" : "FALSE");
		}
		if(doTest(cspHand,
			clHand,
			tpHand,
			thisNumCerts,
			pubKeys,
			privKeys,
			sigAlg,
			expectFail,
			dumpCerts,
			quiet)) {
				break;
		}
	}
	/* clean up */
	return 0;
}