certLabelTest.cpp   [plain text]


/*
 * certLabelTest.cpp - test SecCertificateInferLabel(), in particular, Radar 
 *					   3529689 (teletex strings) and 4746055 (add Description 
 *					   in parentheses)
 */
 
#include <stdlib.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <Security/Security.h>
#include <Security/SecCertificatePriv.h>
#include <clAppUtils/clutils.h>
#include <clAppUtils/CertBuilderApp.h>
#include <utilLib/common.h>
#include <utilLib/cspwrap.h>
#include <security_cdsa_utils/cuFileIo.h>

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("Options:\n");
	printf("  -p              -- pause for leaks check\n");
	printf("  -q              -- quiet\n");
	/* etc. */
	exit(1);
}

#define KEY_SIZE		1024
#define KEY_ALG			CSSM_ALGID_RSA
#define SIG_ALG			CSSM_ALGID_SHA1WithRSA
#define CERT_FILE_OUT	"/tmp/certLabelTest.cer"

/* 
 * Here's the definitive string for Radar 3529689.
 * BER tag = Teletex/T61, encoding = kCFStringEncodingISOLatin1.
 * I hope Herr Petersen does not mind. 
 */
static const unsigned char JurgenPetersen[] = 
{
	0x4a, 0xf8, 0x72, 0x67, 0x65, 0x6e, 0x20, 0x4e,
	0xf8, 0x72, 0x67, 0x61, 0x61, 0x72, 0x64, 0x20,
	0x50, 0x65, 0x74, 0x65, 0x72, 0x73, 0x65, 0x6e
};

/*
 * Name/OID pair used in buildX509Name().
 * This logic is like the CB_BuildX509Name() code in clAppUtils/CertBuilderApp.cpp,
 * with the addition of the berTag specification, and data is specified as a void *
 * and size_t. 
 */
typedef struct {
	const void			*nameVal;
	CSSM_SIZE			nameLen;
	const CSSM_OID 		*oid;
	CSSM_BER_TAG		berTag;
} NameOid;

/*
 * Build up a CSSM_X509_NAME from an arbitrary list of name/OID/tag triplets.
 * We do one a/v pair per RDN.
 */
static CSSM_X509_NAME *buildX509Name(
	const NameOid		*nameArray,
	unsigned 			numNames)
{
	CSSM_X509_NAME *top = (CSSM_X509_NAME *)appMalloc(sizeof(CSSM_X509_NAME), 0);
	if(top == NULL) {
		return NULL;
	}
	top->numberOfRDNs = numNames;
	top->RelativeDistinguishedName = 
		(CSSM_X509_RDN_PTR)appMalloc(sizeof(CSSM_X509_RDN) * numNames, 0);
	if(top->RelativeDistinguishedName == NULL) {
		return NULL;
	}
	CSSM_X509_RDN_PTR rdn;
	const NameOid *nameOid;
	unsigned nameDex;
	for(nameDex=0; nameDex<numNames; nameDex++) {
		rdn = &top->RelativeDistinguishedName[nameDex];
		nameOid = &nameArray[nameDex];
		rdn->numberOfPairs = 1;
		rdn->AttributeTypeAndValue = (CSSM_X509_TYPE_VALUE_PAIR_PTR)
			appMalloc(sizeof(CSSM_X509_TYPE_VALUE_PAIR), 0);
		CSSM_X509_TYPE_VALUE_PAIR_PTR atvp = rdn->AttributeTypeAndValue;
		if(atvp == NULL) {
			return NULL;
		}
		appCopyCssmData(nameOid->oid, &atvp->type);
		atvp->valueType = nameOid->berTag;
		atvp->value.Length = nameOid->nameLen;
		atvp->value.Data = (uint8 *)CSSM_MALLOC(nameOid->nameLen);
		memmove(atvp->value.Data, nameOid->nameVal, nameOid->nameLen);
	}
	return top;
}

/* just make these static and reuse them */
static CSSM_X509_TIME *notBefore;
static CSSM_X509_TIME *notAfter;

/* 
 * Core test routine.
 * -- build a cert with issuer and subject as per specified name components
 * -- extract inferred label 
 * -- compare inferred label to expected value 
 * -- if labelIsCommonName true, verify that SecCertificateCopyCommonName() yields
 *    the same string as inferred label
 */
static int doTest(
	const char			*testName,
	bool				quiet,
	CSSM_CSP_HANDLE		cspHand,
	CSSM_CL_HANDLE		clHand,
	CSSM_KEY_PTR		privKey,
	CSSM_KEY_PTR		pubKey,
	
	/* input names - one or two */
	const void			*name1Val,
	CSSM_SIZE			name1Len,
	CSSM_BER_TAG		berTag1,
	const CSSM_OID		*name1Oid,
	const void			*name2Val,		// optional 
	CSSM_SIZE			name2Len,
	CSSM_BER_TAG		berTag2,
	const CSSM_OID		*name2Oid,
	
	/* expected label */
	CFStringRef			expectedLabel,
	bool				labelIsCommonName)
{
	if(!quiet) {
		printf("...%s\n", testName);
	}
	
	/* build the subject/issuer name */
	NameOid nameArray[2] = { {name1Val, name1Len, name1Oid, berTag1 },
							 {name2Val, name2Len, name2Oid, berTag2 } };
	unsigned numNames = name2Val ? 2 : 1;
	
	CSSM_X509_NAME *name = buildX509Name(nameArray, numNames);
	if(name == NULL) {
		printf("***buildX509Name screwup\n");
		return -1;
	}
	
	/* build the cert template */
	CSSM_DATA_PTR certTemp = CB_MakeCertTemplate(
		clHand, 0x123456,
		name, name,
		notBefore, notAfter,
		pubKey, SIG_ALG,
		NULL, NULL,		// subject/issuer UniqueID
		NULL, 0);		// extensions 
	if(certTemp == NULL) {
		printf("***CB_MakeCertTemplate screwup\n");
		return -1;
	}
	
	/* sign the cert */
	CSSM_DATA signedCert = {0, NULL};
	CSSM_CC_HANDLE sigHand;
	CSSM_RETURN crtn = CSSM_CSP_CreateSignatureContext(cspHand,
			SIG_ALG,
			NULL,			// no passphrase for now
			privKey,
			&sigHand);
	if(crtn) {
		/* should never happen */
		cssmPerror("CSSM_CSP_CreateSignatureContext", crtn);
		return 1;
	}
	crtn = CSSM_CL_CertSign(clHand,
		sigHand,
		certTemp,			// CertToBeSigned
		NULL,				// SignScope per spec
		0,					// ScopeSize per spec
		&signedCert);
	if(crtn) {
		cssmPerror("CSSM_CL_CertSign", crtn);
		return 1;
	}
	CSSM_DeleteContext(sigHand);
	CSSM_FREE(certTemp->Data);
	CSSM_FREE(certTemp);

	/* 
	 * OK, we have a signed cert. 
	 * Turn it into a SecCertificateRef and get the inferred label.
	 */
	OSStatus ortn;
	SecCertificateRef certRef;
	ortn = SecCertificateCreateFromData(&signedCert, 
		CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER,
		&certRef);
	if(ortn) {
		cssmPerror("SecCertificateCreateFromData", ortn);
		return -1;
	}
	CFStringRef inferredLabel;
	ortn = SecCertificateInferLabel(certRef, &inferredLabel);
	if(ortn) {
		cssmPerror("SecCertificateCreateFromData", ortn);
		return -1;
	}
	CFComparisonResult res = CFStringCompare(inferredLabel, expectedLabel, 0);
	if(res != kCFCompareEqualTo) {
		fprintf(stderr, "*** label miscompare in test '%s' ***\n", testName);
		fprintf(stderr, "expected label : ");
		CFShow(expectedLabel);
		fprintf(stderr, "inferred label : ");
		CFShow(inferredLabel);
		if(writeFile(CERT_FILE_OUT, signedCert.Data, signedCert.Length)) {
			fprintf(stderr, "***Error writing cert to %s\n", CERT_FILE_OUT);
		}
		else {
			fprintf(stderr, "...write %lu bytes to %s\n", (unsigned long)signedCert.Length, 
			CERT_FILE_OUT);
		}
		return -1;
	}
	
	if(labelIsCommonName) {
		CFStringRef commonName = NULL;
		ortn = SecCertificateCopyCommonName(certRef, &commonName);
		if(ortn) {
			cssmPerror("SecCertificateCopyCommonName", ortn);
			return -1;
		}
		res = CFStringCompare(inferredLabel, commonName, 0);
		if(res != kCFCompareEqualTo) {
			printf("*** CommonName miscompare in test '%s' ***\n", testName);
			printf("Common Name    : '");
			CFShow(commonName);
			printf("'\n");
			printf("inferred label : '");
			CFShow(inferredLabel);
			printf("'\n");
			if(writeFile(CERT_FILE_OUT, signedCert.Data, signedCert.Length)) {
				printf("***Error writing cert to %s\n", CERT_FILE_OUT);
			}
			else {
				printf("...write %lu bytes to %s\n", (unsigned long)signedCert.Length, 
				CERT_FILE_OUT);
			}
			return -1;
		}
		CFRelease(commonName);
	}
	CFRelease(certRef);
	CSSM_FREE(signedCert.Data);
	CB_FreeX509Name(name);
	CFRelease(inferredLabel);
	return 0;
}

int main(int argc, char **argv)
{
	bool quiet = false;
	bool doPause = false;
	
	int arg;
	while ((arg = getopt(argc, argv, "pqh")) != -1) {
		switch (arg) {
			case 'q':
				quiet = true;
				break;
			case 'p':
				doPause = true;
				break;
			case 'h':
				usage(argv);
		}
	}
	if(optind != argc) {
		usage(argv);
	}
	
	testStartBanner("certLabelTest", argc, argv);
	
	CSSM_CL_HANDLE	clHand = clStartup();
	CSSM_CSP_HANDLE cspHand = cspStartup();
	
	/* create a key pair */
	CSSM_RETURN crtn;
	CSSM_KEY pubKey;
	CSSM_KEY privKey;
	
	crtn = cspGenKeyPair(cspHand, KEY_ALG, 
		"someLabel", 8,
		KEY_SIZE,
		&pubKey, CSSM_FALSE, CSSM_KEYUSE_ANY, CSSM_KEYBLOB_RAW_FORMAT_NONE,
		&privKey, CSSM_FALSE, CSSM_KEYUSE_ANY, CSSM_KEYBLOB_RAW_FORMAT_NONE, 
		CSSM_FALSE);
	if(crtn) {
		printf("***Error generating RSA key pair. Aborting.\n");
		exit(1);
	}
	
	/* common params, reused for each test */
	notBefore = CB_BuildX509Time(0);
	notAfter = CB_BuildX509Time(100000);
	
	/* 
	 * Grind thru test cases.
	 */
	int ourRtn;
	
	/* very basic */
	ourRtn = doTest("simple ASCII common name", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		"Simple Name", strlen("Simple Name"), BER_TAG_PRINTABLE_STRING, &CSSMOID_CommonName,
		NULL, 0, BER_TAG_UNKNOWN, NULL, 
		CFSTR("Simple Name"), true);
	if(ourRtn) {
		exit(1);
	}
	
	/* test concatentation of description */
	ourRtn = doTest("ASCII common name plus ASCII description", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		"Simple Name", strlen("Simple Name"), BER_TAG_PRINTABLE_STRING, &CSSMOID_CommonName,
		"Description", strlen("Description"), BER_TAG_PRINTABLE_STRING, &CSSMOID_Description, 
		CFSTR("Simple Name (Description)"), false);
	if(ourRtn) {
		exit(1);
	}

	/* basic, specifying UTF8 (should be same as PRINTABLE) */
	ourRtn = doTest("simple UTF8 common name", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		"Simple Name", strlen("Simple Name"), BER_TAG_PKIX_UTF8_STRING, &CSSMOID_CommonName,
		NULL, 0, BER_TAG_UNKNOWN, NULL, 
		CFSTR("Simple Name"), true);
	if(ourRtn) {
		exit(1);
	}

	/* label from org name instead of common name */
	ourRtn = doTest("label from OrgName", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		"Simple Name", strlen("Simple Name"), BER_TAG_PRINTABLE_STRING, &CSSMOID_OrganizationName,
		NULL, 0, BER_TAG_UNKNOWN, NULL, 
		CFSTR("Simple Name"), false);
	if(ourRtn) {
		exit(1);
	}

	/* label from orgUnit name instead of common name */
	ourRtn = doTest("label from OrgUnit", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		"Simple Name", strlen("Simple Name"), BER_TAG_PRINTABLE_STRING, &CSSMOID_OrganizationalUnitName,
		NULL, 0, BER_TAG_UNKNOWN, NULL, 
		CFSTR("Simple Name"), false);
	if(ourRtn) {
		exit(1);
	}

	/* label from orgUnit name, description is ignored (it's only used if the 
	 * label comes from CommonName) */
	ourRtn = doTest("label from OrgUnit, description is ignored", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		"Simple Name", strlen("Simple Name"), BER_TAG_PRINTABLE_STRING, &CSSMOID_OrganizationalUnitName,
		"Description", strlen("Description"), BER_TAG_PRINTABLE_STRING, &CSSMOID_Description, 
		CFSTR("Simple Name"), false);
	if(ourRtn) {
		exit(1);
	}

	/* Radar 3529689: T61/Teletex, ISOLatin encoding, commonName only */
	CFStringRef t61Str = CFStringCreateWithBytes(NULL, JurgenPetersen, sizeof(JurgenPetersen),
				kCFStringEncodingISOLatin1, true);
	ourRtn = doTest("T61/Teletex name from Radar 3529689", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		JurgenPetersen, sizeof(JurgenPetersen), BER_TAG_TELETEX_STRING, &CSSMOID_CommonName,
		NULL, 0, BER_TAG_UNKNOWN, NULL, 
		t61Str, true);
	if(ourRtn) {
		exit(1);
	}
	
	/* Now convert that ISOLatin into Unicode and try with that */
	CFDataRef unicodeStr = CFStringCreateExternalRepresentation(NULL, t61Str,
			kCFStringEncodingUnicode, 0);
	if(unicodeStr == NULL) {
		printf("***Error converting to Unicode\n");
		exit(1);
	}
	ourRtn = doTest("Unicode CommonName", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		CFDataGetBytePtr(unicodeStr), CFDataGetLength(unicodeStr), 
				BER_TAG_PKIX_BMP_STRING, &CSSMOID_CommonName,
		NULL, 0, BER_TAG_UNKNOWN, NULL, 
		t61Str, true);
	if(ourRtn) {
		exit(1);
	}
	CFRelease(unicodeStr);
	
	/* Mix up ISOLatin Common Name and ASCII Description to ensure that the encodings
	 * of the two components are handled separately */
	CFMutableStringRef combo = CFStringCreateMutable(NULL, 0);
	CFStringAppend(combo, t61Str);
	CFStringAppendCString(combo, " (Description)", kCFStringEncodingASCII);
	ourRtn = doTest("ISOLatin Common Name and ASCII Description", quiet, 
		cspHand, clHand, &privKey, &pubKey,
		JurgenPetersen, sizeof(JurgenPetersen), BER_TAG_TELETEX_STRING, &CSSMOID_CommonName,
		"Description", strlen("Description"), BER_TAG_PRINTABLE_STRING, &CSSMOID_Description, 
		combo, false);
	if(ourRtn) {
		exit(1);
	}
	CFRelease(combo);
	CFRelease(t61Str);
				
	if(doPause) {
		fpurge(stdin);
		printf("Pausing for leaks testing; CR to continue: ");
		getchar();
	}
	if(!quiet) {
		printf("...success\n");
	}
	return 0;
}