aclUtils.cpp   [plain text]


/*
 * Copyright (c) 2004-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@
 */

/*
 * aclUtils.cpp - ACL utility functions, copied from the SecurityTool project. 
 */
 
#include "aclUtils.h"
#include <Security/SecTrustedApplicationPriv.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>

/* Read a line from stdin into buffer as a null terminated string.  If buffer is
   non NULL use at most buffer_size bytes and return a pointer to buffer.  Otherwise
   return a newly malloced buffer.
   if EOF is read this function returns NULL.  */
char *
readline(char *buffer, int buffer_size)
{
	int ix = 0, bytes_malloced = 0;

	if (!buffer)
	{
		bytes_malloced = 64;
		buffer = (char *)malloc(bytes_malloced);
		buffer_size = bytes_malloced;
	}

	for (;;++ix)
	{
		int ch;

		if (ix == buffer_size - 1)
		{
			if (!bytes_malloced)
				break;
			bytes_malloced += bytes_malloced;
			buffer = (char *)realloc(buffer, bytes_malloced);
			buffer_size = bytes_malloced;
		}

		ch = getchar();
		if (ch == EOF)
		{
			if (bytes_malloced)
				free(buffer);
			return NULL;
		}
		if (ch == '\n')
			break;
		buffer[ix] = ch;
	}

	/* 0 terminate buffer. */
	buffer[ix] = '\0';

	return buffer;
}

void
print_buffer_hex(FILE *stream, UInt32 length, const void *data)
{
	unsigned i;
	const unsigned char *cp = (const unsigned char *)data;
	
	printf("\n   ");
	for(i=0; i<length; i++) {
		fprintf(stream, "%02X ", cp[i]);
		if((i % 24) == 23) {
			printf("\n   ");
		}
	}
}

void
print_buffer_ascii(FILE *stream, UInt32 length, const void *data)
{
	uint8 *p = (uint8 *) data;
	while (length--)
	{
		int ch = *p++;
		if (ch >= ' ' && ch <= '~' && ch != '\\')
		{
			fputc(ch, stream);
		}
		else
		{
			fputc('\\', stream);
			fputc('0' + ((ch >> 6) & 7), stream);
			fputc('0' + ((ch >> 3) & 7), stream);
			fputc('0' + ((ch >> 0) & 7), stream);
		}
	}
}

void
print_buffer(FILE *stream, UInt32 length, const void *data)
{
	uint8 *p = (uint8 *) data;
	Boolean ascii = TRUE;		// unless we determine otherwise
	UInt32 ix;
	for (ix = 0; ix < length; ++ix) {
		int ch = *p++;
		if ((ch < ' ') || (ch > '~')) {
			if((ch == 0) && (ix == (length - 1))) {
				/* ignore trailing null */
				length--;
				break;
			}
			ascii = FALSE;
			break;
		}
	}

	if (ascii) {
		fputc('"', stream);
		print_buffer_ascii(stream, length, data);
		fputc('"', stream);
	}
	else {
		print_buffer_hex(stream, length, data);
	}
}

void
print_cfdata(FILE *stream, CFDataRef data)
{
	if (data)
		return print_buffer(stream, CFDataGetLength(data), CFDataGetBytePtr(data));
	else
		fprintf(stream, "<NULL>");
}

void
print_cfstring(FILE *stream, CFStringRef string)
{
	if (!string)
		fprintf(stream, "<NULL>");
	else
	{
		const char *utf8 = CFStringGetCStringPtr(string, kCFStringEncodingUTF8);
		if (utf8)
			fprintf(stream, "%s", utf8);
		else
		{
			CFRange rangeToProcess = CFRangeMake(0, CFStringGetLength(string));
			while (rangeToProcess.length > 0)
			{
				UInt8 localBuffer[256];
				CFIndex usedBufferLength;
				CFIndex numChars = CFStringGetBytes(string, rangeToProcess,
					kCFStringEncodingUTF8, '?', FALSE, localBuffer,
					sizeof(localBuffer), &usedBufferLength);
				if (numChars == 0)
					break;   // Failed to convert anything...

				fprintf(stream, "%.*s", (int)usedBufferLength, localBuffer);
				rangeToProcess.location += numChars;
				rangeToProcess.length -= numChars;
			}
		}
	}
}


int
print_access(FILE *stream, SecAccessRef access, Boolean interactive)
{
	CFArrayRef aclList = NULL;
	CFIndex aclix, aclCount;
	int result = 0;
	OSStatus status;

	status = SecAccessCopyACLList(access, &aclList);
	if (status)
	{
		cssmPerror("SecAccessCopyACLList", status);
		result = 1;
		goto loser;
	}

	aclCount = CFArrayGetCount(aclList);
	fprintf(stream, "access: %lu entries\n", aclCount);
	for (aclix = 0; aclix < aclCount; ++aclix)
	{
		CFArrayRef applicationList = NULL;
		CFStringRef description = NULL;
		CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector = {};
		CFIndex appix, appCount;

		SecACLRef acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, aclix);
		CSSM_ACL_AUTHORIZATION_TAG tags[64]; // Pick some upper limit
		uint32 tagix, tagCount = sizeof(tags) / sizeof(*tags);
		status = SecACLGetAuthorizations(acl, tags, &tagCount);
		if (status)
		{
			cssmPerror("SecACLGetAuthorizations", status);
			result = 1;
			goto loser;
		}

		fprintf(stream, "    entry %lu:\n        authorizations (%lu):", aclix, (unsigned long)tagCount);
		for (tagix = 0; tagix < tagCount; ++tagix)
		{
			CSSM_ACL_AUTHORIZATION_TAG tag = tags[tagix];
			switch (tag)
			{
			case CSSM_ACL_AUTHORIZATION_ANY:
				fputs(" any", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_LOGIN:
				fputs(" login", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_GENKEY:
				fputs(" genkey", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DELETE:
				fputs(" delete", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED:
				fputs(" export_wrapped", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR:
				fputs(" export_clear", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED:
				fputs(" import_wrapped", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_IMPORT_CLEAR:
				fputs(" import_clear", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_SIGN:
				fputs(" sign", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_ENCRYPT:
				fputs(" encrypt", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DECRYPT:
				fputs(" decrypt", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_MAC:
				fputs(" mac", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DERIVE:
				fputs(" derive", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DBS_CREATE:
				fputs(" dbs_create", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DBS_DELETE:
				fputs(" dbs_delete", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DB_READ:
				fputs(" db_read", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DB_INSERT:
				fputs(" db_insert", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DB_MODIFY:
				fputs(" db_modify", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_DB_DELETE:
				fputs(" db_delete", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_CHANGE_ACL:
				fputs(" change_acl", stream);
				break;
			case CSSM_ACL_AUTHORIZATION_CHANGE_OWNER:
				fputs(" change_owner", stream);
				break;
			default:
				fprintf(stream, " tag=%lu", (unsigned long)tag);
				break;
			}
		}
		fputc('\n', stream);

		status = SecACLCopySimpleContents(acl, &applicationList, &description, &promptSelector);
		if (status)
		{
			cssmPerror("SecACLCopySimpleContents", status);
			continue;
		}

		if (promptSelector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE)
			fputs("        require-password\n", stream);
		else
			fputs("        don't-require-password\n", stream);

		fputs("        description: ", stream);
		print_cfstring(stream, description);
		fputc('\n', stream);

		if (applicationList)
		{
			appCount = CFArrayGetCount(applicationList);
			fprintf(stream, "        applications (%lu):\n", appCount);
		}
		else
		{
			appCount = 0;
			fprintf(stream, "        applications: <null>\n");
		}

		for (appix = 0; appix < appCount; ++appix)
		{
			const UInt8* bytes;
			SecTrustedApplicationRef app = (SecTrustedApplicationRef)CFArrayGetValueAtIndex(applicationList, appix);
			CFDataRef data = NULL;
			fprintf(stream, "            %lu: ", appix);
			status = SecTrustedApplicationCopyData(app, &data);
			if (status)
			{
				cssmPerror("SecTrustedApplicationCopyData", status);
				continue;
			}

			bytes = CFDataGetBytePtr(data);
			if (bytes && bytes[0] == 0x2f) {
				fprintf(stream, "%s", (const char *)bytes);
				if ((status = SecTrustedApplicationValidateWithPath(app, (const char *)bytes)) == noErr) {
					fprintf(stream, " (OK)");
				} else {
					fprintf(stream, " (status %ld)", status);
				}
				fprintf(stream, "\n");
			} else {
				print_cfdata(stream, data);
				fputc('\n', stream);
			}
			if (data)
				CFRelease(data);
		}


		if (interactive)
		{
			char buffer[10] = {};
			if(applicationList != NULL) {
				fprintf(stderr, "NULL out this application list? ");
				if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
				{
					/* 
					 * This makes the ops in this entry wide-open, no dialog or confirmation 
					 * other than requiring the keychain be open.
					 */
					fprintf(stderr, "setting app list to NULL\n");
					status = SecACLSetSimpleContents(acl, NULL, description, &promptSelector);
					if (status)
					{
						cssmPerror("SecACLSetSimpleContents", status);
						continue;
					}
				}
				else {
					fprintf(stderr, "Set this application list to empty array? ");
					if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
					{
						/* 
						 * This means "always get confirmation, from all apps".
						 */
						fprintf(stderr, "setting app list to empty array\n");
						status = SecACLSetSimpleContents(acl, 
							CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks), 
							description, &promptSelector);
						if (status)
						{
							cssmPerror("SecACLSetSimpleContents", status);
							continue;
						}
					}
				}
			}
			else {
				fprintf(stderr, "Remove this acl? ");
				if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
				{
					/* 
					 * This make ths ops in this entry completely inaccessible. 
					 */
					fprintf(stderr, "removing acl\n");
					status = SecACLRemove(acl);
					if (status)
					{
						cssmPerror("SecACLRemove", status);
						continue;
					}
				}
			}
		}
		if (description)
			CFRelease(description);
		if (applicationList)
			CFRelease(applicationList);

	}

loser:
	if (aclList)
		CFRelease(aclList);

	return result;
}

/* Simluate what StickyRecord is trying to do.... */

/* 
 * Given an Access object:
 *  -- extract the ACL for the specified CSSM_ACL_AUTHORIZATION_TAG. We expect there
 *     to exactly one of these - if the form of a default ACL changes we'll have to 
 *	   revisit this.
 *  -- set the ACL's app list to the provided CFArray, which may be NULL (meaning 
 *     "any app can access this, no problem"), an empty array (meaning "always
 *     prompt"), or an actual app list. 
 *  -- set or clear the PROMPT_REQUIRE_PASSPHRASE bit per the requirePassphrase
 *     argument
 */
static OSStatus srUpdateAcl(
	SecAccessRef accessRef,
	CSSM_ACL_AUTHORIZATION_TAG whichAcl,	// e.g. CSSM_ACL_AUTHORIZATION_DECRYPT
	CFArrayRef appArray,
	bool requirePassphrase)
{
	OSStatus ortn;
	CFArrayRef aclList = NULL;
	
	ortn = SecAccessCopySelectedACLList(accessRef, whichAcl, &aclList);
	if(ortn) {
		cssmPerror("SecAccessCopySelectedACLList", ortn);
		return ortn;
	}
	
	if(CFArrayGetCount(aclList) != 1) {
		printf("StickyRecord::updateAcl - unexpected ACL list count (%d)",
			(int)CFArrayGetCount(aclList));
		return internalComponentErr;
	}
	SecACLRef acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
	
	CFArrayRef applicationList = NULL;
	CFStringRef description = NULL;
	CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector = {};
	ortn = SecACLCopySimpleContents(acl, &applicationList, &description, &promptSelector);
	if(ortn) {
		cssmPerror("SecACLCopySimpleContents", ortn);
		return ortn;
	}
	if(applicationList != NULL) {
		CFRelease(applicationList);
	}
	if(requirePassphrase) {
		promptSelector.flags |= CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
	}
	else {
		promptSelector.flags &= ~CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
	}
	/* update */
	ortn = SecACLSetSimpleContents(acl, appArray, description, &promptSelector);
	
	/* we got this from SecACLCopySimpleContents - release it regardless */
	if(description != NULL) {
		CFRelease(description);
	}
	if(ortn) {
		cssmPerror("SecACLSetSimpleContents", ortn);
	}
	if(aclList != NULL) {
		CFRelease(aclList);
	}
	return ortn;
}

OSStatus stickyRecordUpdateAcl(
	SecAccessRef accessRef)
{
	OSStatus ortn;
	
	printf("...updating ACL to simulate a StickyRecord\n");
	
	/* First: decrypt. Wide open (NULL app list), !REQUIRE_PASSPHRASE. */
	ortn = srUpdateAcl(accessRef, CSSM_ACL_AUTHORIZATION_DECRYPT, NULL, false);
	if(ortn) {
		return ortn;
	}
	
	/* encrypt: always ask (empty app list, require passphrase */
	CFArrayRef nullArray = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
	return srUpdateAcl(accessRef, CSSM_ACL_AUTHORIZATION_ENCRYPT, nullArray, true);
	
}