sslSubjName.cpp   [plain text]


/* Copyright (c) 2002-2004,2006,2008 Apple Inc.
 *
 * sslSubjName.c
 *
 * Verify comparision of app-specified host name vs. various
 * forms of hostname in a cert.
 *
 */

#include <utilLib/common.h>
#include <utilLib/cspwrap.h>
#include <clAppUtils/clutils.h>
#include <clAppUtils/certVerify.h>
#include <clAppUtils/BlobList.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>
#include <security_cdsa_utils/cuFileIo.h>

/* key labels */
#define SUBJ_KEY_LABEL		"subjectKey"
#define ROOT_KEY_LABEL		"rootKey"

/* key and signature algorithm - shouldn't matter for this test */
#define SIG_ALG_DEFAULT		CSSM_ALGID_SHA1WithRSA
#define SIG_OID_DEFAULT		CSSMOID_SHA1WithRSA
#define KEY_ALG_DEFAULT		CSSM_ALGID_RSA

#define KEY_SIZE_DEFAULT	512

#define CERT_FILE		"sslCert.cer"

static void usage(char **argv)
{
	printf("Usage: %s [options]\n", argv[0]);
	printf("Options:\n");
	printf("    w(write certs)\n");
	printf("    q(uiet)\n");
	printf("    v(erbose)\n");
	exit(1);
}

/*
 * RDN components for root, subject
 */
CSSM_APPLE_TP_NAME_OID rootRdn[] = 
{
	{ "Apple Computer",					&CSSMOID_OrganizationName },
	{ "The Big Cheese",					&CSSMOID_Title }
};
#define NUM_ROOT_NAMES	(sizeof(rootRdn) / sizeof(CSSM_APPLE_TP_NAME_OID))

#define SUBJ_COMMON_NAME	"something.org"

CSSM_APPLE_TP_NAME_OID subjRdn[] = 
{
	{ "Apple Computer",					&CSSMOID_OrganizationName },
	/* overridden when creating the cert */
	{ NULL,								&CSSMOID_CommonName }
};
#define SUBJ_COMMON_NAME_DEX	1

#define NUM_SUBJ_NAMES	(sizeof(subjRdn) / sizeof(CSSM_APPLE_TP_NAME_OID))


/*
 * Test cases
 */
typedef struct {
	/* test description */
	const char		*testDesc;
	
	/* host names for leaf cert - zero or one of these */
	const char		*certDnsName;
	const char		*certIpAddr;
	
	/* subject common name */
	const char		*commonName;
	
	/* host name for CertGroupVerify */
	const char		*vfyHostName;
	
	/* expected error - NULL or e.g. "CSSMERR_APPLETP_CRL_NOT_TRUSTED" */
	const char		*expectErrStr;
	
	/* one optional per-cert error string */
	const char		*certErrorStr;
	
} SSN_TestCase;

SSN_TestCase testCases[] = 
{
	{
		"DNS Name foo.bar, vfyName foo.bar",
		"foo.bar", NULL, SUBJ_COMMON_NAME, "foo.bar",
		NULL, 
		NULL
	},
	{
		"DNS Name foo.bar, vfyName something.org, expect fail due to "
			"DNS present",
		"foo.bar", NULL, SUBJ_COMMON_NAME, "something.org",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
	{
		"DNS Name foo.bar, vfyName foo.foo.bar, expect fail",
		"foo.bar", NULL, SUBJ_COMMON_NAME, "foo.foo.bar",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
	{
		"IP Name 1.0.5.8, vfyName 1.0.5.8",
		NULL, "1.0.5.8", SUBJ_COMMON_NAME, "1.0.5.8",
		NULL, 
		NULL
	},
	{
		"IP Name 1.0.5.8, vfyName 1.00.5.008",
		NULL, "1.0.5.8", SUBJ_COMMON_NAME, "1.00.5.008",
		NULL, 
		NULL
	},
	{
		"IP Name 1.0.5.8, vfyName something.org",
		NULL, "1.0.5.8", SUBJ_COMMON_NAME, "something.org",
		NULL, 
		NULL
	},
	{
		"IP Name 1.0.5.8, vfyName 2.0.5.8, expect fail",
		NULL, "1.0.5.8", SUBJ_COMMON_NAME, "2.0.5.8",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
	{
		"DNS Name *.foo.bar, vfyName bar.foo.bar",
		"*.foo.bar", NULL, SUBJ_COMMON_NAME, "bar.foo.bar",
		NULL, 
		NULL
	},
	{
		"DNS Name *.foo.bar, vfyName foo.bar, expect fail",
		"*.foo.bar", NULL, SUBJ_COMMON_NAME, "foo.bar",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
	{
		"DNS Name *foo.bar, vfyName barfoo.bar",
		"*foo.bar", NULL, SUBJ_COMMON_NAME, "barfoo.bar",
		NULL, 
		NULL
	},
	{
		"DNS Name *foo*.bar, vfyName barfoo.bar",
		"*foo*.bar", NULL, SUBJ_COMMON_NAME, "barfoo.bar",
		NULL, 
		NULL
	},
	{
		"DNS Name *foo*.bar, vfyName foobar.bar",
		"*foo*.bar", NULL, SUBJ_COMMON_NAME, "foobar.bar",
		NULL, 
		NULL
	},
	{
		"DNS Name *foo*.bar, vfyName foo.bar",
		"*foo*.bar", NULL, SUBJ_COMMON_NAME, "foo.bar",
		NULL, 
		NULL
	},
	{
		"DNS Name *foo.bar, vfyName bar.foo.bar, should fail",
		"*foo.bar", NULL, SUBJ_COMMON_NAME, "bar.foo.bar",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
	{
		"DNS Name *foo.bar, vfyName foobar.bar, should fail",
		"*foo.bar", NULL, SUBJ_COMMON_NAME, "foobar.bar",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
	{
		"No DNS or IP name, commonName = vfyName = 1.0.5.8",
		NULL, NULL, "1.0.5.8", "1.0.5.8",
		"CSSMERR_APPLETP_HOSTNAME_MISMATCH", 
		"0:CSSMERR_APPLETP_HOSTNAME_MISMATCH"
	},
};

#define NUM_TEST_CASES	(sizeof(testCases) / sizeof(SSN_TestCase))

/*
 * Convert a string containing a dotted IP address to 4 bytes.
 * Returns nonzero on error.
 * FIXME - should handle 16-byte IP addresses. 
 */
static int convertIp(
	const char 	*str,
	uint8 		*buf)
{
	char cbuf[4];
	for(unsigned dex=0; dex<3; dex++) {
		char *nextDot = strchr(str, '.');
		if(nextDot == NULL) {
			return 1;
		}
		memset(cbuf, 0, sizeof(cbuf));
		memmove(cbuf, str, nextDot - str);
		*buf = atoi(cbuf);
		buf++;				// next out char
		str = nextDot + 1;	// next in char after dot
		
	}
	/* str points to last char */
	if(str == NULL) {
		return 1;
	}
	*buf = atoi(str);
	return 0;
}

/*
 * Generate a pair of certs.
 */
static CSSM_RETURN genCerts(
	CSSM_CL_HANDLE	clHand,
	CSSM_CSP_HANDLE	cspHand,	
	CSSM_TP_HANDLE	tpHand,	
	CSSM_KEY_PTR	rootPrivKey,
	CSSM_KEY_PTR	rootPubKey,
	CSSM_KEY_PTR	subjPubKey,
	/* one of these goes into leaf's subjectAltName */
	const char		*subjIpAddr,
	const char		*subjDnsName,
	const char		*commonName,
	CSSM_DATA		&rootCert,		// RETURNED
	CSSM_DATA		&subjCert)		// RETURNED
	
{
	CSSM_DATA					refId;	
								// mallocd by CSSM_TP_SubmitCredRequest
	CSSM_RETURN					crtn;
	CSSM_APPLE_TP_CERT_REQUEST	certReq;
	CSSM_TP_REQUEST_SET			reqSet;
	sint32						estTime;
	CSSM_BOOL					confirmRequired;
	CSSM_TP_RESULT_SET_PTR		resultSet;
	CSSM_ENCODED_CERT			*encCert;
	CSSM_TP_CALLERAUTH_CONTEXT 	CallerAuthContext;
	CSSM_FIELD					policyId;
	CE_GeneralNames				genNames;
	CE_GeneralName				genName;
	uint8						ipNameBuf[4];
	/* 
	 * Two extensions. Subject has two (KeyUsage and possibly 
	 * subjectAltName); root has KeyUsage and  BasicConstraints.
	 */
	CE_DataAndType 				rootExts[2];
	CE_DataAndType 				leafExts[2];
	unsigned					numLeafExts;
	
	if(subjIpAddr && subjDnsName) {
		printf("***Max of one of {subjIpAddr, subjDnsName} at a "
			"time, please.\n");
		exit(1);
	}
	if(subjIpAddr) {
		if(convertIp(subjIpAddr, ipNameBuf)) {
			printf("**Malformed IP address. Aborting.\n");
			exit(1);
		}
	}
	
	/* A KeyUsage extension for both certs */
	rootExts[0].type = DT_KeyUsage;
	rootExts[0].critical = CSSM_FALSE;
	rootExts[0].extension.keyUsage = 
		CE_KU_DigitalSignature | CE_KU_KeyCertSign;

	leafExts[0].type = DT_KeyUsage;
	leafExts[0].critical = CSSM_FALSE;
	leafExts[0].extension.keyUsage =  CE_KU_DigitalSignature;

	/* BasicConstraints for root only */
	rootExts[1].type = DT_BasicConstraints;
	rootExts[1].critical = CSSM_TRUE;
	rootExts[1].extension.basicConstraints.cA = CSSM_TRUE;
	rootExts[1].extension.basicConstraints.pathLenConstraintPresent = 
			CSSM_TRUE;
	rootExts[1].extension.basicConstraints.pathLenConstraint = 2;

	/* possible subjectAltName for leaf */
	numLeafExts = 1;
	if(subjIpAddr || subjDnsName) {
		numLeafExts++;
		leafExts[1].type = DT_SubjectAltName;
		leafExts[1].critical = CSSM_TRUE;
		
		genName.berEncoded = CSSM_FALSE;
		if(subjIpAddr) {
			genName.name.Data = (uint8 *)ipNameBuf;
			genName.name.Length = 4;
			genName.nameType = GNT_IPAddress;
		}
		else {
			genName.name.Data = (uint8 *)subjDnsName;
			genName.nameType = GNT_DNSName;
			genName.name.Length = strlen(subjDnsName);
		}
		genNames.numNames = 1;
		genNames.generalName = &genName;
		leafExts[1].extension.subjectAltName = genNames;
	}
	
	/* certReq for root */
	memset(&certReq, 0, sizeof(CSSM_APPLE_TP_CERT_REQUEST));
	certReq.cspHand = cspHand;
	certReq.clHand = clHand;
	certReq.serialNumber = 0x12345678;
	certReq.numSubjectNames = NUM_ROOT_NAMES;
	certReq.subjectNames = rootRdn;
	certReq.numIssuerNames = 0;
	certReq.issuerNames = NULL;
	certReq.certPublicKey = rootPubKey;
	certReq.issuerPrivateKey = rootPrivKey;
	certReq.signatureAlg = SIG_ALG_DEFAULT;
	certReq.signatureOid = SIG_OID_DEFAULT;
	certReq.notBefore = 0;			// now
	certReq.notAfter = 10000;		// seconds from now
	certReq.numExtensions = 2;
	certReq.extensions = rootExts;
	
	reqSet.NumberOfRequests = 1;
	reqSet.Requests = &certReq;
	
	/* a big CSSM_TP_CALLERAUTH_CONTEXT just to specify an OID */
	memset(&CallerAuthContext, 0, sizeof(CSSM_TP_CALLERAUTH_CONTEXT));
	memset(&policyId, 0, sizeof(CSSM_FIELD));
	policyId.FieldOid = CSSMOID_APPLE_TP_LOCAL_CERT_GEN;
	CallerAuthContext.Policy.NumberOfPolicyIds = 1;
	CallerAuthContext.Policy.PolicyIds = &policyId;
	
	/* generate root cert */
	crtn = CSSM_TP_SubmitCredRequest(tpHand,
		NULL,				// PreferredAuthority
		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
		&reqSet,
		&CallerAuthContext,	
		&estTime,
		&refId);
	if(crtn) {
		printError("CSSM_TP_SubmitCredRequest", crtn);
		return crtn;
	}
	crtn = CSSM_TP_RetrieveCredResult(tpHand,
		&refId,
		NULL,				// CallerAuthCredentials
		&estTime,
		&confirmRequired,
		&resultSet);
	if(crtn) {
		printError("CSSM_TP_RetrieveCredResult", crtn);
		return crtn;
	}
	if(resultSet == NULL) {
		printf("***CSSM_TP_RetrieveCredResult returned NULL result set.\n");
		return crtn;
	}
	encCert = (CSSM_ENCODED_CERT *)resultSet->Results;
	rootCert = encCert->CertBlob;

	/* now a subject cert signed by the root cert */
	certReq.serialNumber = 0x8765;
	certReq.numSubjectNames = NUM_SUBJ_NAMES;
	subjRdn[SUBJ_COMMON_NAME_DEX].string = commonName;
	certReq.subjectNames = subjRdn;
	certReq.numIssuerNames = NUM_ROOT_NAMES;
	certReq.issuerNames = rootRdn;
	certReq.certPublicKey = subjPubKey;
	certReq.issuerPrivateKey = rootPrivKey;
	certReq.numExtensions = numLeafExts;
	certReq.extensions = leafExts;

	crtn = CSSM_TP_SubmitCredRequest(tpHand,
		NULL,				// PreferredAuthority
		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
		&reqSet,
		&CallerAuthContext,
		&estTime,
		&refId);
	if(crtn) {
		printError("CSSM_TP_SubmitCredRequest (2)", crtn);
		return crtn;
	}
	crtn = CSSM_TP_RetrieveCredResult(tpHand,
		&refId,
		NULL,				// CallerAuthCredentials
		&estTime,
		&confirmRequired,
		&resultSet);		// leaks.....
	if(crtn) {
		printError("CSSM_TP_RetrieveCredResult (2)", crtn);
		return crtn;
	}
	if(resultSet == NULL) {
		printf("***CSSM_TP_RetrieveCredResult (2) returned NULL "
				"result set.\n");
		return crtn;
	}
	encCert = (CSSM_ENCODED_CERT *)resultSet->Results;
	subjCert = encCert->CertBlob;

	return CSSM_OK;
}

int main(int argc, char **argv)
{
	CSSM_CL_HANDLE	clHand;			// CL handle
	CSSM_CSP_HANDLE	cspHand;		// CSP handle
	CSSM_TP_HANDLE	tpHand;			// TP handle
	CSSM_DATA		rootCert;		
	CSSM_DATA		subjCert;	
	CSSM_KEY		subjPubKey;		// subject's RSA public key blob
	CSSM_KEY		subjPrivKey;	// subject's RSA private key - ref format
	CSSM_KEY		rootPubKey;		// root's RSA public key blob
	CSSM_KEY		rootPrivKey;	// root's RSA private key - ref format
	CSSM_RETURN		crtn = CSSM_OK;
	int				vfyRtn = 0;
	int				arg;
	SSN_TestCase	*testCase;
	unsigned		testNum;
	
	CSSM_BOOL		quiet = CSSM_FALSE;
	CSSM_BOOL		verbose = CSSM_FALSE;
	CSSM_BOOL		writeCerts = CSSM_FALSE;
	
	for(arg=1; arg<argc; arg++) {
		char *argp = argv[arg];
		switch(argp[0]) {
			case 'q':
				quiet = CSSM_TRUE;
				break;
			case 'v':
				verbose = CSSM_TRUE;
				break;
			case 'w':
				writeCerts = CSSM_TRUE;
				break;
			default:
				usage(argv);
		}
	}
	
	testStartBanner("sslSubjName", argc, 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;
	}

	/* subsequent errors to abort: to detach */
	
	/* cook up an RSA key pair for the subject */
	crtn = cspGenKeyPair(cspHand,
		KEY_ALG_DEFAULT,
		SUBJ_KEY_LABEL,
		strlen(SUBJ_KEY_LABEL),
		KEY_SIZE_DEFAULT,
		&subjPubKey,
		CSSM_FALSE,			// pubIsRef
		CSSM_KEYUSE_VERIFY,
		CSSM_KEYBLOB_RAW_FORMAT_NONE,
		&subjPrivKey,
		CSSM_TRUE,			// privIsRef - doesn't matter
		CSSM_KEYUSE_SIGN,
		CSSM_KEYBLOB_RAW_FORMAT_NONE,
		CSSM_FALSE);
	if(crtn) {
		return crtn;
	}

	/* and the root */
	crtn = cspGenKeyPair(cspHand,
		KEY_ALG_DEFAULT,
		ROOT_KEY_LABEL,
		strlen(ROOT_KEY_LABEL),
		KEY_SIZE_DEFAULT,
		&rootPubKey,
		CSSM_FALSE,			// pubIsRef
		CSSM_KEYUSE_VERIFY,
		CSSM_KEYBLOB_RAW_FORMAT_NONE,
		&rootPrivKey,
		CSSM_TRUE,			// privIsRef - doesn't matter
		CSSM_KEYUSE_SIGN,
		CSSM_KEYBLOB_RAW_FORMAT_NONE,
		CSSM_FALSE);
	if(crtn) {
		goto abort;
	}

	for(testNum=0; testNum<NUM_TEST_CASES; testNum++) {
		testCase = &testCases[testNum];
		if(!quiet) {
			printf("%s\n", testCase->testDesc);
		}
		crtn = genCerts(clHand, cspHand, tpHand,
			&rootPrivKey, &rootPubKey, &subjPubKey,
			testCase->certIpAddr, testCase->certDnsName, testCase->commonName,
			rootCert, subjCert);
		BlobList leaf;
		BlobList root;
		/* BlobList uses regular free() on the referent of the blobs */
		leaf.addBlob(subjCert, CSSM_TRUE);
		root.addBlob(rootCert, CSSM_TRUE);
		if(crtn) {
			if(testError(quiet)) {
				break;
			}
		}
		if(writeCerts) {
			if(writeFile(CERT_FILE, subjCert.Data, subjCert.Length)) {
				printf("***Error writing cert to %s\n", CERT_FILE);
			}
			else {
				printf("...wrote %lu bytes to %s\n", subjCert.Length, CERT_FILE);
			}
		}
		vfyRtn = certVerifySimple(tpHand, clHand, cspHand,
			leaf, root,
			CSSM_FALSE,		// useSystemAnchors
			CSSM_FALSE,		// leafCertIsCA
			CSSM_FALSE,		// allow expired root
			CVP_SSL,
			testCase->vfyHostName,
			CSSM_FALSE,		// sslClient
			NULL,
			NULL,
			testCase->expectErrStr,
			testCase->certErrorStr ? 1 : 0,
			testCase->certErrorStr ? (const char **)&testCase->certErrorStr :
				NULL,
			0, NULL,		// certStatus
			CSSM_FALSE,		// trustSettings
			quiet,
			verbose);
		if(vfyRtn) {
			if(testError(quiet)) {
				break;
			}
		}
		/* cert data freed by ~BlobList */
	}

	/* free keys */
	cspFreeKey(cspHand, &rootPubKey);
	cspFreeKey(cspHand, &rootPrivKey);
	cspFreeKey(cspHand, &subjPubKey);
	cspFreeKey(cspHand, &subjPrivKey);

abort:
	if(cspHand != 0) {
		CSSM_ModuleDetach(cspHand);
	}
	if(clHand != 0) {
		CSSM_ModuleDetach(clHand);
	}
	if(tpHand != 0) {
		CSSM_ModuleDetach(tpHand);
	}
	if(!vfyRtn && !crtn && !quiet) {
		printf("...test passed\n");
	}
	return 0;
}