trusted_cert_add.c   [plain text]


/*
 * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 * trust_cert_add.c
 */

/*
 * This command is fairly versatile and hence the usage might be a bit confusing. 
 * The standard usage of this command is to add one or more certs to a Trust
 * Settings domain, along with optional usage constraints. Often, but not
 * necessarily, you'd also add the cert to a keychain while you're adding
 * it to Trust Settings.
 *
 * -- To add someRoot.cer to your login keychain and to your Trust Settings as 
 *    an unrestricted root cert:
 *
 *    % security add-trusted-cert -k login.keychain someRoot.cer  
 *
 * -- To add anotherRoot.cer to the local admin trust settings, only for policy 
 *    ssl, without adding it to a keychain (presumably because it's already in 
 *    a keychain somewhere else):
 *
 *    % security add-trusted-cert -p ssl -d anotherRoot.cer
 *
 * The more obscure uses involve default settings and trust settings files. 
 *
 * Specifying a default trust setting precludes specifying a cert. Other 
 * options apply as usual; note that if the domain for which you are 
 * specifying a default setting already has a default setting, the old default 
 * will be replaced by the new one you specify.
 *
 * -- To specify a default of "deny" for policy SMIME for the admin domain:
 *
 *    % security add-trusted-cert -p smime -r deny -D
 *
 * This command can also operate on trust settings as files instead of 
 * modifying an actual on-disk Trust Settings record. One standard use for
 * this function is in the creation of the system Trust Settings, which 
 * are immutable at runtime via the SecTrustSettings API. You provide a 
 * file name for this option via -f settingsFile. If the file does not 
 * exist, a new empty Trust Settings will be created, and certs and/or
 * a default will be added to that record, and the record will be written 
 * out to the filename you provide (infile = outfile, always). 
 *
 * -- To create Trust Settings record with one cert in it, restricted to 
 *    policy SSL:
 *
 *    % security add-trusted-cert -p ssl -f someTrustSettingsFile.plist -r someRoot.cer
 *
 * You can also use the -f option and specify no certs, in which case an empty
 * Trust Settings record will be created. This can be useful if you want to 
 * quickly reset the Trust Settings in a given domain to "empty"; the 
 * empty Trust Settings record can be imported via the trust-settings-import
 * command. 
 *
 * -- To reset the admin trust settings to "empty":
 * 
 *    % security add-trusted-cert -f emptySettingsFile.plist 
 *    % security trust-settings-import -d emptySettingsFile.plist
 */

#include "trusted_cert_add.h"
#include "trusted_cert_utils.h"
#include "security.h"
#include "keychain_utilities.h"
#include <Security/Security.h>
#include <Security/SecTrust.h>
#include <Security/SecTrustSettings.h>
#include <Security/SecTrustSettingsPriv.h>
#include <Security/oidsalg.h>
#include <errno.h>
#include <unistd.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <CoreFoundation/CoreFoundation.h>

/* r/w files as CFData */
static CFDataRef readFileData(
	const char *fileName)
{
	unsigned char *d;
	unsigned dLen;

	if(readFile(fileName, &d, &dLen)) {
		return NULL;
	}
	CFDataRef cfd = CFDataCreate(NULL, (const UInt8 *)d, dLen);
	free(d);
	return cfd;
}

static int writeFileData(
	const char *fileName, 
	CFDataRef cfd)
{
	unsigned long l = (unsigned long)CFDataGetLength(cfd);
	int rtn = writeFile(fileName, CFDataGetBytePtr(cfd), l);
	if(rtn) {
		fprintf(stderr, "Error %d writing to %s\n", rtn, fileName);
	}
	else if(!do_quiet) {
		fprintf(stdout, "...wrote %ld bytes to %s\n", l, fileName);
	}
	return rtn;
}

static int appendConstraintsToDict(
	const char *appPath,			/* optional */
	const char *policy,				/* optional - smime, ssl, etc. */
	const char *policyStr,			/* optional policy string */
	SecTrustSettingsResult resultType,	
	CSSM_RETURN allowErr,			/* optional allowed error */
	SecTrustSettingsKeyUsage keyUse,/* optional key use */
	CFMutableDictionaryRef *dict)	/* result RETURNED here, created if necessary */
{
	if(*dict == NULL) {
		*dict = CFDictionaryCreateMutable(NULL, 
			0,		// capacity 
			&kCFTypeDictionaryKeyCallBacks, 
			&kCFTypeDictionaryValueCallBacks);
	}

	/* OID string to an OID pointer */
	const CSSM_OID *oid = NULL;
	if(policy != NULL) {
		oid = policyStringToOid(policy);
		if(oid == NULL) {
			return 2;
		}
		
		/* OID to SecPolicyRef */
		SecPolicyRef policyRef = oidToPolicy(oid);
		if(policyRef == NULL) {
			return 2;
		}
		CFDictionaryAddValue(*dict, kSecTrustSettingsPolicy, policyRef);
		CFRelease(policyRef);
	}
	
	/* app string to SecTrustedApplicationRef */
	if(appPath != NULL) {
		SecTrustedApplicationRef appRef;
		OSStatus ortn = SecTrustedApplicationCreateFromPath(appPath, &appRef);
		if(ortn) {
			cssmPerror("SecTrustedApplicationCreateFromPath", ortn);
			return -1;
		}
		CFDictionaryAddValue(*dict, kSecTrustSettingsApplication, appRef);
		CFRelease(appRef);
	}
	
	if(policyStr != NULL) {
		CFStringRef pstr = CFStringCreateWithCString(NULL, policyStr, kCFStringEncodingASCII);
		CFDictionaryAddValue(*dict, kSecTrustSettingsPolicyString, pstr);
		CFRelease(pstr);
	}
	
	if(allowErr) {
		SInt32 ae = (SInt32)allowErr;
		CFNumberRef cfNum = CFNumberCreate(NULL, kCFNumberSInt32Type, &ae);
		CFDictionaryAddValue(*dict, kSecTrustSettingsAllowedError, cfNum);
		CFRelease(cfNum);
	}

	if(keyUse != 0) {
		SInt32 ku = (SInt32)ku;
		CFNumberRef cfNum = CFNumberCreate(NULL, kCFNumberSInt32Type, &ku);
		CFDictionaryAddValue(*dict, kSecTrustSettingsKeyUsage, cfNum);
		CFRelease(cfNum);
	}
	
	if(resultType != kSecTrustSettingsResultTrustRoot) {
		SInt32 rt = (SInt32)resultType;
		CFNumberRef cfNum = CFNumberCreate(NULL, kCFNumberSInt32Type, &rt);
		CFDictionaryAddValue(*dict, kSecTrustSettingsResult, cfNum);
		CFRelease(cfNum);
	}
	
	return 0;
}


int
trusted_cert_add(int argc, char * const *argv)
{
	extern char *optarg;
	extern int optind;
	OSStatus ortn;
	int arg;
	SecTrustSettingsDomain domain = kSecTrustSettingsDomainUser;
	int ourRtn = 0;
	SecKeychainRef kcRef = NULL;
	int defaultSetting = 0;
	char *certFile = NULL;
	SecCertificateRef certRef = NULL;

	/* for operating in file-based settings */
	char *settingsFileIn = NULL;
	char *settingsFileOut = NULL;
	CFDataRef settingsIn = NULL;
	CFDataRef settingsOut = NULL;

	/* optional usage constraints */
	char *policy = NULL;
	char *appPath = NULL;
	char *policyString = NULL;
	SecTrustSettingsResult resultType = kSecTrustSettingsResultTrustRoot;
	CSSM_RETURN allowErr = CSSM_OK;
	SecTrustSettingsKeyUsage keyUse = 0;
	CFMutableDictionaryRef trustSettings = NULL;
	int haveConstraints = 0;

	if(argc < 2) {
		return 2; /* @@@ Return 2 triggers usage message. */
	}
	
	optind = 1;
	while ((arg = getopt(argc, argv, "dr:a:p:s:e:u:k:i:o:Dh")) != -1) {
		switch (arg) {
			case 'd':
				domain = kSecTrustSettingsDomainAdmin;
				break;
			case 'r':
				if(!strcmp(optarg, "trustRoot")) {
					resultType = kSecTrustSettingsResultTrustRoot;
				}
				else if(!strcmp(optarg, "trustAsRoot")) {
					resultType = kSecTrustSettingsResultTrustAsRoot;
				}
				else if(!strcmp(optarg, "deny")) {
					resultType = kSecTrustSettingsResultDeny;
				}
				else if(!strcmp(optarg, "unspecified")) {
					resultType = kSecTrustSettingsResultUnspecified;
				}
				else {
					return 2;
				}
				haveConstraints = 1;
				break;
			case 'p':
				policy = optarg;
				haveConstraints = 1;
				break;
			case 'a':
				appPath = optarg;
				haveConstraints = 1;
				break;
			case 's':
				policyString = optarg;
				haveConstraints = 1;
			case 'e':
				allowErr = (CSSM_RETURN)atoi(optarg);
				haveConstraints = 1;
				break;
			case 'u':
				keyUse = (SecTrustSettingsKeyUsage)atoi(optarg);
				haveConstraints = 1;
				break;
			case 'k':
				kcRef = keychain_open(optarg);
				if(kcRef == NULL) {
					return 1;
				}
				break;
			case 'i':
				settingsFileIn = optarg;
				break;
			case 'o':
				settingsFileOut = optarg;
				break;
			case 'D':
				defaultSetting = 1;
				break;
			default:
			case 'h':
				return 2; /* @@@ Return 2 triggers usage message. */
		}
	}
	if(ourRtn) {
		goto errOut;
	}

	switch(argc - optind) {
		case 0:
			/* no certs */
			break;
		case 1:
			certFile = argv[optind];
			break;
		default:
			ourRtn = 2;
			goto errOut;
	}

	/* validate inputs */
	if(defaultSetting && (certFile != NULL)) {
		fprintf(stderr, "Can't specify cert when manipulating default setting.\n");
		ourRtn = 2; /* @@@ Return 2 triggers usage message. */
		goto errOut;
	}
	if((certFile == NULL) && (settingsFileOut == NULL) && !defaultSetting) {
		/* no cert file - only legal for r/w file or for default settings */
		fprintf(stderr, "No cert file specified.\n");
		ourRtn = 2; 
		goto errOut;
	}
	if((settingsFileOut != NULL) && (domain != kSecTrustSettingsDomainUser)) {
		fprintf(stderr, "Can't specify both domain and a settingsFile\n");
		ourRtn = 2;
		goto errOut;
	}
	if((settingsFileIn != NULL) && (settingsFileOut == NULL)) {
		/* on the other hand, fileOut with no fileIn is OK */
		fprintf(stderr, "Can't specify settingsFileIn and no settingsFileOut\n");
		ourRtn = 2;
		goto errOut;
	}

	/*
	 * Note we're just providing the capability of one UsageConstraints
	 * dictionary - doing multiple dictionaries from the cmd line is just
	 * too hard. 
	 */
	if(haveConstraints) {
		ourRtn = appendConstraintsToDict(appPath, policy, policyString,
			resultType, allowErr, keyUse, &trustSettings);
		if(ourRtn) {
			goto errOut;
		}
	}

	/* optional settings file */
	if(settingsFileIn) {
		settingsIn = readFileData(settingsFileIn);
		if(settingsIn == NULL) {
			fprintf(stderr, "Error reading file %s\n", settingsFileIn);
			ourRtn = 1;
			goto errOut;
		}
	}
	else if(settingsFileOut) {
		/* output file, no input file - start with empty settings */
		ortn = SecTrustSettingsSetTrustSettingsExternal(NULL,
			NULL, NULL, &settingsIn);
		if(ortn) {
			cssmPerror("SecTrustSettingsSetTrustSettings", ortn);
			ourRtn = 1;
			goto errOut;
		}
	}
		
	/* optional cert file */
	if(defaultSetting) {
		/* we don't have a cert; use this instead... */
		certRef = kSecTrustSettingsDefaultRootCertSetting;
	}
	else if(certFile != NULL) {
		if(readCertFile(certFile, &certRef)) {
			fprintf(stderr, "Error reading file %s\n", certFile);
			ourRtn = 1;
			goto errOut;
		}
		if(kcRef) {
			/* note we do NOT add by default */
			ortn = SecCertificateAddToKeychain(certRef, kcRef);
			switch(ortn) {
				case noErr:
				case errSecDuplicateItem:		/* that's fine */
					break;
				default:
					cssmPerror("SecCertificateAddToKeychain", ortn);
					ourRtn = 1;
					goto errOut;
			}
		}
	}

	/* now manipulate the Trust Settings */
	if(settingsFileOut) {
		/*
		 * Operating on file data, not actual domain.
		 * At this point settingsIn is the current settings data; it
		 * may be empty but it's valid nonetheless. 
		 */
		if(certRef != NULL) {
			ortn = SecTrustSettingsSetTrustSettingsExternal(settingsIn,
				certRef, trustSettings, &settingsOut);
			if(ortn) {
				cssmPerror("SecTrustSettingsSetTrustSettings", ortn);
				ourRtn = 1;
				goto errOut;
			}
		}
		else {
			/* no cert data: output := input, e.g. create empty settings file */
			settingsOut = settingsIn;
			settingsIn = NULL;
		}
		ourRtn = writeFileData(settingsFileOut, settingsOut);
		if(ourRtn) {
			fprintf(stderr, "Error writing to %s\n", settingsFileOut);
			goto errOut;
		}
	}
	else {
		/* normal "Add this cert to Trust Settings" */
		if(certRef == NULL) {
			fprintf(stderr, "Internal error in trusted_cert_add\n");
			ourRtn = 1;
			goto errOut;
		}
		ortn = SecTrustSettingsSetTrustSettings(certRef, domain, trustSettings);
		if(ortn) {
			cssmPerror("SecTrustSettingsSetTrustSettings", ortn);
			ourRtn = 1;
		}
	}
errOut:
	if((certRef != NULL) & (certRef != kSecTrustSettingsDefaultRootCertSetting)) {
		CFRelease(certRef);
	}
	CFRELEASE(trustSettings);
	CFRELEASE(kcRef);
	CFRELEASE(settingsIn);
	CFRELEASE(settingsOut);
	return ourRtn;
}

int
trusted_cert_remove(int argc, char * const *argv)
{
	OSStatus ortn = noErr;
	int ourRtn = 0;
	SecTrustSettingsDomain domain = kSecTrustSettingsDomainUser;
	int defaultSetting = 0;
	SecCertificateRef certRef = NULL;
	char *certFile = NULL;

	extern char *optarg;
	extern int optind;
	int arg;
	
	optind = 1;
	while ((arg = getopt(argc, argv, "dDh")) != -1) {
		switch (arg) {
			case 'd':
				domain = kSecTrustSettingsDomainAdmin;
				break;
			case 'D':
				defaultSetting = 1;
				break;
			default:
			case 'h':
				return 2; /* @@@ Return 2 triggers usage message. */
		}
	}
	
	switch(argc - optind) {
		case 0:
			/* no certs */
			break;
		case 1:
			certFile = argv[optind];
			break;
		default:
			return 2;
	}

	if((certFile == NULL) && !defaultSetting) {
		fprintf(stderr, "No cert file specified.\n");
		return 2;
	}
	if((certFile != NULL) && defaultSetting) {
		fprintf(stderr, "Can't specify cert when manipulating default setting.\n");
		return 2;
	}
	
	if(defaultSetting) {
		/* we don't have a cert; use this instead... */
		certRef = kSecTrustSettingsDefaultRootCertSetting;
	}
	else {
		if(readCertFile(certFile, &certRef)) {
			fprintf(stderr, "Error reading file %s\n", certFile);
			return 1;
		}
	}

	ortn = SecTrustSettingsRemoveTrustSettings(certRef, domain);
	if(ortn) {
		cssmPerror("SecTrustSettingsRemoveTrustSettings", ortn);
		ourRtn = 1;
	}

	if((certRef != NULL) & (certRef != kSecTrustSettingsDefaultRootCertSetting)) {
		CFRelease(certRef);
	}
	
	return ourRtn;
}