rootStoreTool.cpp   [plain text]


/*
 * rootStoreTool.cpp - exercise SecTrustSettings API
 */
 
#include <stdlib.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <Security/Security.h>
#include <Security/SecTrustSettings.h>
#include <Security/SecTrustPriv.h>
#include <Security/TrustSettingsSchema.h>
#include <Security/SecTrustSettingsPriv.h>
#include <Security/cssmapplePriv.h>
#include <Security/SecPolicyPriv.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <security_utilities/cfutilities.h>
#include <security_cdsa_utils/cuPrintCert.h>
#include <security_cdsa_utils/cuOidParser.h>
#include "parseTrustedRootList.h"
#include <Security/TrustSettingsSchema.h>		/* private header */
#include "rootUtils.h"
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <pthread.h>
#include <sys/param.h>

static void usage(char **argv)
{
	printf("usage: %s op [options]\n", argv[0]);
	printf("Op values:\n");
	printf("   a   -- add cert\n");
	printf("   p   -- parse TrustSettings record\n");
	printf("   r   -- get certs from TS & display\n");
	printf("   d   -- delete entries from TS interactively\n");
	printf("   D   -- delete ALL certs from TS (requires -R argument)\n");
	printf("   R   -- remove legacy User Trust setting\n");
	
	printf("Options:\n");
	printf("  -c certFile        -- specify cert\n");
	printf("  -s                 -- system TrustSettings; default is user\n");
	printf("  -d                 -- Admin TrustSettings; default is user\n");
	printf("  -t settingsFile    -- settings from file; default is user\n");
	printf("  -T settingsFileOut -- settings to file\n");
	printf("  -a appPath         -- specify app constraints\n");
	printf("  -p policy          -- specify policy constraint\n");
	printf("                        policy = ssl, smime, swuSign, codeSign, IPSec, iChat\n");
	printf("  -P appPath policy  -- specify app AND policy constraint\n");
	printf("  -e emailAddress    -- specify SMIME policy plus email address\n");
	printf("  -L hostname        -- specify SSL policy plus hostname\n");
	printf("  -r resultType      -- resultType = trust, trustAsRoot, deny, unspecified\n");
	printf("  -w allowErr        -- allowed error, an integer; implies result unspecified\n");
	printf("  -W allowErr policy -- allowed error AND policy AND implies result unspecified\n");
	printf("  -u keyUsage        -- key usage, an integer\n");
	printf("  -k keychain        -- Default is default keychain.\n");
	printf("  -R                 -- Really. For Delete All op.\n");
	printf("  -v                 -- verbose cert display\n");
	printf("  -A                 -- add cert to keychain\n");
	printf("  -U                 -- use SecTrustSetUserTrust\n");
	printf("  -2                 -- use SecTrustSetUserTrustLegacy\n");
	printf("  -l                 -- loop and pause for malloc debug\n");
	printf("  -h                 -- help\n");
	exit(1);
}

/* 
 * Start up a CFRunLoop. This is needed to field keychain event callbacks, used
 * to maintain root cert cache coherency. This operation is only needed in command 
 * line tools; regular GUI apps already have a CFRunLoop. 
 */
 
/* first we need something to register so we *have* a run loop */
static OSStatus kcCacheCallback (
   SecKeychainEvent keychainEvent,
   SecKeychainCallbackInfo *info,
   void *context)
{
	return noErr;
}

/* main thread has to wait for this to be set to know a run loop has been set up */
static int runLoopInitialized = 0;

/* this is the thread which actually runs the CFRunLoop */
void *cfRunLoopThread(void *arg)
{
	OSStatus ortn = SecKeychainAddCallback(kcCacheCallback, 
		kSecTrustSettingsChangedEventMask, NULL);
	if(ortn) {
		printf("registerCacheCallbacks: SecKeychainAddCallback returned %ld", ortn);
		/* Not sure how this could ever happen - maybe if there is no run loop active? */
		return NULL;
	}
	runLoopInitialized = 1;
	CFRunLoopRun();
	/* should not be reached */
	printf("\n*** Hey! CFRunLoopRun() exited!***\n");
	return NULL;
}

static int startCFRunLoop()
{
	pthread_t runLoopThread;
	
	int result = pthread_create(&runLoopThread, NULL, cfRunLoopThread, NULL);
	if(result) {
		printf("***pthread_create returned %d, aborting\n", result);
		return -1;
	}
	return 0;
}

static SecCertificateRef certFromFile(
	const char *fileName)
{
	unsigned char *cp = NULL;
	unsigned len = 0;
	if(readFile(fileName, &cp, &len)) {
		printf("***Error reading file %s\n", fileName);
		return NULL;
	}
	SecCertificateRef certRef;
	CSSM_DATA certData = {len, cp};
	OSStatus ortn = SecCertificateCreateFromData(&certData, 
			CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &certRef);
	if(ortn) {
		cssmPerror("SecCertificateCreateFromData", ortn);
		return NULL;
	}
	free(cp);
	return certRef;
}

/*
 * Display usage constraints array as obtained from 
 * SecTrustSettingsCopyTrustSettings().
 */
static int displayTrustSettings(
	CFArrayRef	trustSettings,
	OidParser	&parser)
{
	/* must always be there though it may be empty */
	if(trustSettings == NULL) {
		printf("***displayTrustSettings: missing trust settings array");
		return -1;
	}
	if(CFGetTypeID(trustSettings) != CFArrayGetTypeID()) {
		printf("***displayTrustSettings: malformed trust settings array");
		return -1;
	}
	
	int ourRtn = 0;
	CFIndex numUseConstraints = CFArrayGetCount(trustSettings);
	indentIncr();
	indent(); printf("Number of trust settings : %ld\n", numUseConstraints);
	OSStatus ortn;
	SecPolicyRef certPolicy;
	SecTrustedApplicationRef certApp;
	CFDictionaryRef ucDict;
	CFStringRef policyStr;
	CFNumberRef cfNum;
	
	/* grind thru the trust settings dictionaries */
	for(CFIndex ucDex=0; ucDex<numUseConstraints; ucDex++) {
		indent(); printf("Trust Setting %ld:\n", ucDex);
		indentIncr();
		
		ucDict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, ucDex);
		if(CFGetTypeID(ucDict) != CFDictionaryGetTypeID()) {
			printf("***displayTrustSettings: malformed usage constraints dictionary");
			ourRtn = -1;
			goto nextAp;
		}
		
		/* policy - optional */
		certPolicy = (SecPolicyRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsPolicy);
		if(certPolicy != NULL) {
			if(CFGetTypeID(certPolicy) != SecPolicyGetTypeID()) {
				printf("***displayTrustSettings: malformed certPolicy");
				ourRtn = -1;
				goto nextAp;
			}
			CSSM_OID policyOid;
			ortn = SecPolicyGetOID(certPolicy, &policyOid);
			if(ortn) {
				cssmPerror("SecPolicyGetOID", ortn);
				ourRtn = -1;
				goto nextAp;
			}
			indent(); printf("Policy OID            : "); 
			printOid(policyOid.Data, policyOid.Length, parser);
			printf("\n");
		}
		
		/* app - optional  */
		certApp = (SecTrustedApplicationRef)CFDictionaryGetValue(ucDict, 
			kSecTrustSettingsApplication);
		if(certApp != NULL) {
			if(CFGetTypeID(certApp) != SecTrustedApplicationGetTypeID()) {
				printf("***displayTrustSettings: malformed certApp");
				ourRtn = -1;
				goto nextAp;
			}
			CFRef<CFDataRef> appPath;
			ortn = SecTrustedApplicationCopyData(certApp, appPath.take());
			if(ortn) {
				cssmPerror("SecTrustedApplicationCopyData", ortn);
				ourRtn = -1;
				goto nextAp;
			}
			indent(); printf("Application           : %s", CFDataGetBytePtr(appPath));
			printf("\n");
		}
		
		/* policy string */
		policyStr = (CFStringRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsPolicyString);
		if(policyStr != NULL) {
			if(CFGetTypeID(policyStr) != CFStringGetTypeID()) {
				printf("***displayTrustSettings: malformed policyStr");
				ourRtn = -1;
				goto nextAp;
			}
			indent(); printf("Policy String         : ");
			printCfStr(policyStr); printf("\n");
		}

		/* Allowed error */
		cfNum = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsAllowedError);
		if(cfNum != NULL) {
			if(CFGetTypeID(cfNum) != CFNumberGetTypeID()) {
				printf("***displayTrustSettings: malformed allowedError");
				ourRtn = -1;
				goto nextAp;
			}
			indent(); printf("Allowed Error         : ");
			printCssmErr(cfNum); printf("\n");
		}

		/* Result */
		cfNum = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsResult);
		if(cfNum != NULL) {
			if(CFGetTypeID(cfNum) != CFNumberGetTypeID()) {
				printf("***displayTrustSettings: malformed Result");
				ourRtn = -1;
				goto nextAp;
			}
			indent(); printf("Result Type           : ");
			printResult(cfNum); printf("\n");
		}

		/* key usage */
		cfNum = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsKeyUsage);
		if(cfNum != NULL) {
			if(CFGetTypeID(cfNum) != CFNumberGetTypeID()) {
				printf("***displayTrustSettings: malformed keyUsage");
				ourRtn = -1;
				goto nextAp;
			}
			indent(); printf("Key Usage             : ");
			printKeyUsage(cfNum); printf("\n");
		}

	nextAp:
		indentDecr();
	}
	indentDecr();
	return ourRtn;
}

/* convert an OID to a SecPolicyRef */
static SecPolicyRef oidToPolicy(
	const CSSM_OID &oid)
{
	SecPolicyRef policyRef = NULL;

	OSStatus ortn = SecPolicyCopy(CSSM_CERT_X_509v3, &oid, &policyRef);
	if(ortn) {
		cssmPerror("SecPolicyCopy", ortn);
		return NULL;
	}
	return policyRef;
}

/* Convert cmdline policy string to SecPolicyRef */
static SecPolicyRef policyStringToPolicy(
	const char *policy)
{
	if(policy == NULL) {
		return NULL;
	}
	const CSSM_OID *oid = NULL;
	if(!strcmp(policy, "ssl")) {
		oid = &CSSMOID_APPLE_TP_SSL;
	}
	else if(!strcmp(policy, "smime")) {
		oid = &CSSMOID_APPLE_TP_SMIME;
	}
	else if(!strcmp(policy, "codeSign")) {
		oid = &CSSMOID_APPLE_TP_CODE_SIGNING;
	}
	else if(!strcmp(policy, "swuSign")) {
		oid = &CSSMOID_APPLE_TP_SW_UPDATE_SIGNING;
	}
	else if(!strcmp(policy, "IPSec")) {
		oid = &CSSMOID_APPLE_TP_IP_SEC;
	}
	else if(!strcmp(policy, "iChat")) {
		oid = &CSSMOID_APPLE_TP_ICHAT;
	}
	else {
		printf("***Unknown policy string (%s)\n", policy);
		return NULL;
	}

	/* OID to SecPolicyRef */
	return oidToPolicy(*oid);
}

static int appendConstraintToArray(
	const char *appPath,			/* optional, "-" means ensure apArray is nonempty */
	const char *policy,				/* optional (ssl/smime), "-" as above */
	const char *policyStr,			/* optional policy string */
	const SInt32 *allowErr,			/* optional allowed error */
	const char *resultType,			/* optional allow/confirm/deny */
	SecTrustSettingsKeyUsage keyUse,	/* optional key use */
	CFMutableArrayRef &array)		/* result RETURNED here, created if necessary */
{
	if(array == NULL) {
		array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	}
	
	CFMutableDictionaryRef outDict = CFDictionaryCreateMutable(NULL, 
		0,		// capacity 
		&kCFTypeDictionaryKeyCallBacks, 
		&kCFTypeDictionaryValueCallBacks);

	if((policy != NULL) && (strcmp(policy, "-"))) {
		
		/* policy string to SecPolicyRef */
		SecPolicyRef policyRef = policyStringToPolicy(policy);
		if(policyRef == NULL) {
			return -1;
		}
		CFDictionaryAddValue(outDict, kSecTrustSettingsPolicy, policyRef);
		CFRelease(policyRef);
	}
	
	/* app string to SecTrustedApplicationRef */
	if((appPath != NULL) && (strcmp(appPath, "-"))) {
		SecTrustedApplicationRef appRef;
		OSStatus ortn = SecTrustedApplicationCreateFromPath(appPath, &appRef);
		if(ortn) {
			cssmPerror("SecTrustedApplicationCreateFromPath", ortn);
			return -1;
		}
		CFDictionaryAddValue(outDict, kSecTrustSettingsApplication, appRef);
		CFRelease(appRef);
	}
	
	if(policyStr != NULL) {
		CFStringRef pstr = CFStringCreateWithCString(NULL, policyStr, kCFStringEncodingASCII);
		CFDictionaryAddValue(outDict, kSecTrustSettingsPolicyString, pstr);
		CFRelease(pstr);
	}
	
	if(allowErr != NULL) {
		CFNumberRef cfNum = CFNumberCreate(NULL, kCFNumberSInt32Type, allowErr);
		CFDictionaryAddValue(outDict, kSecTrustSettingsAllowedError, cfNum);
		CFRelease(cfNum);
	}

	if(keyUse != 0) {
		SInt32 ku = (SInt32)ku;
		CFNumberRef cfNum = CFNumberCreate(NULL, kCFNumberSInt32Type, &ku);
		CFDictionaryAddValue(outDict, kSecTrustSettingsKeyUsage, cfNum);
		CFRelease(cfNum);
	}
	
	if(resultType != NULL) {
		SInt32 n;
		
		if(!strcmp(resultType, "trust")) {
			n = kSecTrustSettingsResultTrustRoot;
		}
		else if(!strcmp(resultType, "trustAsRoot")) {
			n = kSecTrustSettingsResultTrustAsRoot;
		}
		else if(!strcmp(resultType, "deny")) {
			n = kSecTrustSettingsResultDeny;
		}
		else if(!strcmp(resultType, "unspecified")) {
			n = kSecTrustSettingsResultUnspecified;
		}
		else {
			printf("***unknown resultType spec (%s)\n", resultType);
			return -1;
		}
		CFNumberRef cfNum = CFNumberCreate(NULL, kCFNumberSInt32Type, &n);
		CFDictionaryAddValue(outDict, kSecTrustSettingsResult, cfNum);
		CFRelease(cfNum);
	}
	
	/* append dictionary to output */
	CFArrayAppendValue(array, outDict);
	/* array owns the dictionary now */
	CFRelease(outDict);
	return 0;
}

/* read a file --> CFDataRef */
CFDataRef readFileCFData(
	const char *fileName)
{
	int rtn;
	unsigned char *fileData = NULL;
	unsigned fileDataLen = 0;

	rtn = readFile(fileName, &fileData, &fileDataLen);
	if(rtn) {
		printf("Error (%d) reading %s.\n", rtn, fileName);
		return NULL;
	}
	CFDataRef cfd = CFDataCreate(NULL, (const UInt8 *)fileData, fileDataLen);
	free(fileData);
	return cfd;
}

static int fetchParseTrustRecord(
	SecTrustSettingsDomain domain,
	char *settingsFile)				/* optional, ignore domain if present */
{
	CFDataRef trustSettings = NULL;
	
	if(settingsFile) {
		trustSettings = readFileCFData(settingsFile);
		if(trustSettings == NULL) {
			return -1;
		}
	}
	else {
		OSStatus ortn = SecTrustSettingsCreateExternalRepresentation(domain, &trustSettings);
		if(ortn) {
			cssmPerror("SecTrustSettingsCreateExternalRepresentation", ortn);
			return -1;
		}
	}
	int rtn = parseTrustedRootList(trustSettings);
	CFRelease(trustSettings);
	return rtn;
}

static int copyCertsAndDisplay(
	bool verbose,
	SecTrustSettingsDomain domain)
{
	OSStatus ortn;
	
	auto_ptr<OidParser> parser(NULL);
	
	if(verbose) {
		parser.reset(new OidParser);
	}
	
	CFArrayRef certArray = NULL;
	ortn = SecTrustSettingsCopyCertificates(domain, &certArray);
	if(ortn) {
		cssmPerror("SecTrustSettingsCopyCertificates", ortn);
		return ortn;
	}
	
	CFIndex numCerts = CFArrayGetCount(certArray);
	indent();
	printf("Num certs = %ld\n", numCerts);
	int ourRtn = 0;
	for(CFIndex dex=0; dex<numCerts; dex++) {
		SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certArray, dex);
		if(CFGetTypeID(certRef) != SecCertificateGetTypeID()) {
			printf("***Bad CFGetTypeID for cert\n");
			return -1;
		}
		indent();
		printf("Cert %ld: ", dex); 
		printCertLabel(certRef);
		printf("\n");
		if(verbose) {
			CFRef<CFArrayRef> appPolicies;
			ortn = SecTrustSettingsCopyTrustSettings(certRef, domain, appPolicies.take());
			if(ortn) {
				cssmPerror("SecRootCertificateCopyAppPolicyConstraints", ortn);
				ourRtn = -1;
				continue;
			}
			if(displayTrustSettings(appPolicies, *parser.get())) {
				ourRtn = -1;
			}
		}
	}
	CFRelease(certArray);
	return ourRtn;
}

static int deleteCerts(
	SecTrustSettingsDomain domain,
	bool deleteAll)
{
	OSStatus ortn;
	
	CFArrayRef certArray = NULL;
	ortn = SecTrustSettingsCopyCertificates(domain, &certArray);
	if(ortn) {
		cssmPerror("SecTrustSettingsCopyCertificates", ortn);
		return ortn;
	}
	
	CFIndex numCerts = CFArrayGetCount(certArray);
	unsigned numDeleted = 0;
	
	for(CFIndex dex=0; dex<numCerts; dex++) {
		SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certArray, dex);
		if(CFGetTypeID(certRef) != SecCertificateGetTypeID()) {
			printf("***Bad CFGetTypeID for cert\n");
			return -1;
		}
		bool doDelete = false;
		
		if(deleteAll) {
			printf("DELETING: ");
			printCertLabel(certRef);
			printf("\n");
			doDelete = true;
		}
		else {
			indent();
			printf("Cert %ld: ", dex);
			printCertLabel(certRef);
			printf("\n");
			fpurge(stdin);
			printf("Delete (y/anything)? ");
			char resp = getchar();
			if(resp == 'y') {
				doDelete = true;
			}
		}
		if(doDelete) {
			ortn = SecTrustSettingsRemoveTrustSettings(certRef, domain);
			if(ortn) {
				cssmPerror("SecTrustSettingsRemoveTrustSettings", ortn);
				fpurge(stdin);
				printf("Continue deleting (y/anything)? ");
				char resp = getchar();
				fflush(stdout);
				if(resp != 'y') {
					return ortn;
				}
			}
			else {
				numDeleted++;
			}
		}
	}
	CFRelease(certArray);
	printf("...%u certs deleted\n", numDeleted);
	return noErr;
}

/* add a cert to trust list */
static int addCert(
	SecCertificateRef certRef,
	SecTrustSettingsDomain domain,
	bool addToKc,					// import cert to keychain 
	const char *kcName,				// only for addToKC option
	CFArrayRef trustSettings,
	CFDataRef settingsIn,			// optional, requires settingsFileOut
	CFDataRef *settingsOut)
{
	OSStatus ortn;
	char *domainName;

	if(settingsIn && !settingsOut) {
		printf("Modifying trust settings as file requires output file\n");
		return -1;
	}
	switch(domain) {
		case kSecTrustSettingsDomainSystem:
			printf("***Can't modify system trust settings.\n");
			return -1;
		case kSecTrustSettingsDomainAdmin:
			kcName = "/Library/Keychains/System.keychain";
			domainName = "Admin";
			break;
		default:
			domainName = "User";
			break;
	}
	if(addToKc) {
		SecKeychainRef kcRef = NULL;
		if(kcName) {
			ortn = SecKeychainOpen(kcName, &kcRef);
			if(ortn) {
				cssmPerror("SecKeychainOpen", ortn);
				return -1;
			}
		}
		ortn = SecCertificateAddToKeychain(certRef, kcRef);
		if(ortn) {
			cssmPerror("SecCertificateAddToKeychain", ortn);
			return -1;
		}
		printf("...cert added to keychain %s\n", (kcName ? kcName : "<default>"));
	}
	if(settingsIn) {
		ortn = SecTrustSettingsSetTrustSettingsExternal(settingsIn,
			certRef, trustSettings, settingsOut);
		if(ortn) {
			cssmPerror("SecTrustSettingsSetTrustSettingsExternal", ortn);
			return -1;
		}
	}
	else {
		ortn = SecTrustSettingsSetTrustSettings(certRef, domain, trustSettings);
		if(ortn) {
			cssmPerror("SecTrustSettingsSetTrustSettings", ortn);
			return -1;
		}
		printf("...cert added to %s TrustList.\n", domainName);
	}
	return 0;
}

static int addCertLegacy(
	SecCertificateRef certRef, 
	const char *policy, 
	const char *resultStr,
	bool useLegacy)
{
	/* OID string to an OID pointer */
	if(policy == NULL) {
		printf("***You must specify a policy to set legacy User Trust\n");
		return 1;
	}
	SecPolicyRef policyRef = policyStringToPolicy(policy);
	if(policyRef == NULL) {
		return -1;
	}

	/* result string to legacy SecTrustUserSetting */
	SecTrustUserSetting setting = kSecTrustResultInvalid;
	if(resultStr == NULL) {
		setting = kSecTrustResultProceed;
	}
	else if(!strcmp(resultStr, "trust")) {
		setting = kSecTrustResultProceed;
	}
	else if(!strcmp(resultStr, "trustAsRoot")) {
		setting = kSecTrustResultProceed;
	}
	else if(!strcmp(resultStr, "deny")) {
		setting = kSecTrustResultDeny;
	}
	else if (!strcmp(resultStr, "unspecified")) {
		setting = kSecTrustResultUnspecified;
	}
	else {
		printf("***Can't map %s to a SecTrustUserSetting\n", resultStr);
		return -1;
	}
	OSStatus ortn;
	if(useLegacy) {
		ortn = SecTrustSetUserTrustLegacy(certRef, policyRef, setting);
		if(ortn) {
			cssmPerror("SecTrustSetUserTrustLegacy", ortn);
		}
		else {
			if(setting == kSecTrustResultUnspecified) {
				printf("...User Trust removed via SecTrustSetUserTrustLegacy().\n");
			}
			else {
				printf("...User Trust set via SecTrustSetUserTrustLegacy().\n");
			}
		}
	}
	else {
		#if 1
		printf("...Legacy implementation needs Makefile work to avoid deprecation error\n");
		exit(1);
		#else
		ortn = SecTrustSetUserTrust(certRef, policyRef, setting);
		if(ortn) {
			cssmPerror("SecTrustSetUserTrust", ortn);
		}
		else {
			printf("...trust setting set via SecTrustSetUserTrust().\n");
		}
		#endif
	}
	if(policyRef != NULL) {
		CFRelease(policyRef);
	}
	return ortn;
}

int main(int argc, char **argv)
{
	int arg;
	CFMutableArrayRef appPolicies = NULL;
	CFDataRef settingsIn = NULL;
	CFDataRef settingsOut = NULL;
	
	/* user-spec'd variables */
	bool loopPause = false;
	bool really = false;
	bool verbose = false;
	char *kcName = NULL;
	SecTrustSettingsDomain domain = kSecTrustSettingsDomainUser;
	SecCertificateRef certRef = NULL;
	bool addToKeychain = false;
	char *settingsFileIn = NULL;
	char *settingsFileOut = NULL;
	bool userTrustLegacy = false;
	char *policyStr = NULL;
	char *resultStr = NULL;
	bool userTrust = false;

	extern char *optarg;
	extern int optind;
	optind = 2;
	while ((arg = getopt(argc, argv, "c:sdt:T:a:p:P:e:L:r:w:W:k:u:RvAU2lh")) != -1) {
		switch (arg) {
			case 'c':
				if(certRef) {
					printf("***Only one cert at a time, please.\n");
					usage(argv);
				}
				certRef = certFromFile(optarg);
				if(certRef == NULL) {
					exit(1);
				}
				break;
			case 's':
				domain = kSecTrustSettingsDomainSystem;
				break;
			case 'd':
				domain = kSecTrustSettingsDomainAdmin;
				break;
			case 't':
				settingsFileIn = optarg;
				break;
			case 'T':
				settingsFileOut = optarg;
				break;
			case 'a':
				if(appendConstraintToArray(optarg, NULL, NULL, NULL, NULL, 0, appPolicies)) {
					exit(1);
				}
				break;
			case 'p':
				if(appendConstraintToArray(NULL, optarg, NULL, NULL, NULL, 0, appPolicies)) {
					exit(1);
				}
				policyStr = optarg;
				break;
			case 'P':
				/* this takes an additional argument */
				if(optind > (argc - 1)) {
					usage(argv);
				}
				if(appendConstraintToArray(optarg, argv[optind], NULL, NULL, NULL, 
						0, appPolicies)) {
					exit(1);
				}
				optind++;
				break;
			case 'e':
				if(appendConstraintToArray(NULL, "smime", optarg, NULL, NULL, 
						0, appPolicies)) {
					exit(1);
				}
				policyStr = "smime";
				break;
			case 'L':
				if(appendConstraintToArray(NULL, "ssl", optarg, NULL, NULL, 0, appPolicies)) {
					exit(1);
				}
				policyStr = "ssl";
				break;
			case 'r':
				if(appendConstraintToArray(NULL, NULL, NULL, NULL, optarg, 0, appPolicies)) {
					exit(1);
				}
				resultStr = optarg;
				break;
			case 'w':
			{
				SInt32 l = atol(optarg);
				if(appendConstraintToArray(NULL, NULL, NULL, &l, "unspecified", 0, appPolicies)) {
					exit(1);
				}
				break;
			}
			case 'W':
			{
				/* this takes an additional argument */
				if(optind > (argc - 1)) {
					usage(argv);
				}
				SInt32 l = atol(optarg);
				if(appendConstraintToArray(NULL, argv[optind], NULL, &l, "unspecified", 0,
						appPolicies)) {
					exit(1);
				}
				optind++;
				break;
			}
			case 'u':
			{
				SInt32 l = atol(optarg);
				SecTrustSettingsKeyUsage ku = (SecTrustSettingsKeyUsage)l;
				if(appendConstraintToArray(NULL, NULL, NULL, NULL, NULL, ku, appPolicies)) {
					exit(1);
				}
				break;
			}
			case 'k':
				kcName = optarg;
				break;
			case 'R':
				really = true;
				break;
			case 'v':
				verbose = true;
				break;
			case 'A':
				addToKeychain = true;
				break;
			case 'l':
				loopPause = true;
				break;
			case '2':
				userTrustLegacy = true;
				break;
			case 'U':
				userTrust = true;
				break;
			default:
			case 'h':
				usage(argv);
		}
	}
	if(optind != argc) {
		usage(argv);
	}
	if(startCFRunLoop()) {
		/* enable reception of KC event messages */
		exit(1);
	}

	/* give that thread a chance right now */
	while(!runLoopInitialized) {
		usleep(1000);
	};

	int ortn = 0;
	do {
		switch(argv[1][0]) {
			case 'a':
				if(certRef == NULL) {
					printf("You must supply a cert.\n");
					usage(argv);
				}
				if(settingsFileIn) {
					if(!settingsFileOut) {
						printf("Modifying trust settings as file requires output file\n");
						return -1;
					}
					settingsIn = readFileCFData(settingsFileIn);
					if(!settingsIn) {
						return -1;
					}
				}
				if(userTrustLegacy || userTrust) {
					ortn = addCertLegacy(certRef, policyStr, resultStr, userTrustLegacy);
				}
				else {
					ortn = addCert(certRef, domain, addToKeychain, kcName, appPolicies,
						settingsIn, &settingsOut);
					if((ortn == noErr) && (settingsOut != NULL)) {
						unsigned len = CFDataGetLength(settingsOut);
						if(writeFile(settingsFileOut, CFDataGetBytePtr(settingsOut), len)) {
							printf("***Error writing settings to %s\n", settingsFileOut);
						}
						else {
							printf("...wrote %u bytes to %s\n", len, settingsFileOut);
						}
					}
				}
				if(settingsIn) {
					CFRelease(settingsIn);
				}
				if(settingsOut) {
					CFRelease(settingsOut);
				}
				break;
			case 'p':
				ortn = fetchParseTrustRecord(domain, settingsFileIn);
				break;
			case 'r':
				ortn = copyCertsAndDisplay(verbose, domain);
				break;
			case 'd':
				ortn = deleteCerts(domain, false);
				break;
			case 'D':
				if(!really) {
					printf("I do not believe you. Specify -D option to delete all roots.\n");
					exit(1);
				}
				ortn = deleteCerts(domain, true);
				break;
			case 'R':
				ortn = addCertLegacy(certRef, policyStr, "unspecified", true);
				break;
			default:
				usage(argv);
		}
		if(loopPause) {
			fpurge(stdin);
			printf("Pausing for MallocDebug. Hit CR to continue: ");
			fflush(stdout);
			getchar();
		}
	} while(loopPause);
	return (int)ortn;
}