1/*
2 * Copyright (c) 2004-2006 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * aclUtils.cpp - ACL utility functions, copied from the SecurityTool project.
26 */
27
28#include "aclUtils.h"
29#include <Security/SecTrustedApplicationPriv.h>
30#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
31
32/* Read a line from stdin into buffer as a null terminated string.  If buffer is
33   non NULL use at most buffer_size bytes and return a pointer to buffer.  Otherwise
34   return a newly malloced buffer.
35   if EOF is read this function returns NULL.  */
36char *
37readline(char *buffer, int buffer_size)
38{
39	int ix = 0, bytes_malloced = 0;
40
41	if (!buffer)
42	{
43		bytes_malloced = 64;
44		buffer = (char *)malloc(bytes_malloced);
45		buffer_size = bytes_malloced;
46	}
47
48	for (;;++ix)
49	{
50		int ch;
51
52		if (ix == buffer_size - 1)
53		{
54			if (!bytes_malloced)
55				break;
56			bytes_malloced += bytes_malloced;
57			buffer = (char *)realloc(buffer, bytes_malloced);
58			buffer_size = bytes_malloced;
59		}
60
61		ch = getchar();
62		if (ch == EOF)
63		{
64			if (bytes_malloced)
65				free(buffer);
66			return NULL;
67		}
68		if (ch == '\n')
69			break;
70		buffer[ix] = ch;
71	}
72
73	/* 0 terminate buffer. */
74	buffer[ix] = '\0';
75
76	return buffer;
77}
78
79void
80print_buffer_hex(FILE *stream, UInt32 length, const void *data)
81{
82	unsigned i;
83	const unsigned char *cp = (const unsigned char *)data;
84
85	printf("\n   ");
86	for(i=0; i<length; i++) {
87		fprintf(stream, "%02X ", cp[i]);
88		if((i % 24) == 23) {
89			printf("\n   ");
90		}
91	}
92}
93
94void
95print_buffer_ascii(FILE *stream, UInt32 length, const void *data)
96{
97	uint8 *p = (uint8 *) data;
98	while (length--)
99	{
100		int ch = *p++;
101		if (ch >= ' ' && ch <= '~' && ch != '\\')
102		{
103			fputc(ch, stream);
104		}
105		else
106		{
107			fputc('\\', stream);
108			fputc('0' + ((ch >> 6) & 7), stream);
109			fputc('0' + ((ch >> 3) & 7), stream);
110			fputc('0' + ((ch >> 0) & 7), stream);
111		}
112	}
113}
114
115void
116print_buffer(FILE *stream, UInt32 length, const void *data)
117{
118	uint8 *p = (uint8 *) data;
119	Boolean ascii = TRUE;		// unless we determine otherwise
120	UInt32 ix;
121	for (ix = 0; ix < length; ++ix) {
122		int ch = *p++;
123		if ((ch < ' ') || (ch > '~')) {
124			if((ch == 0) && (ix == (length - 1))) {
125				/* ignore trailing null */
126				length--;
127				break;
128			}
129			ascii = FALSE;
130			break;
131		}
132	}
133
134	if (ascii) {
135		fputc('"', stream);
136		print_buffer_ascii(stream, length, data);
137		fputc('"', stream);
138	}
139	else {
140		print_buffer_hex(stream, length, data);
141	}
142}
143
144void
145print_cfdata(FILE *stream, CFDataRef data)
146{
147	if (data)
148		return print_buffer(stream, CFDataGetLength(data), CFDataGetBytePtr(data));
149	else
150		fprintf(stream, "<NULL>");
151}
152
153void
154print_cfstring(FILE *stream, CFStringRef string)
155{
156	if (!string)
157		fprintf(stream, "<NULL>");
158	else
159	{
160		const char *utf8 = CFStringGetCStringPtr(string, kCFStringEncodingUTF8);
161		if (utf8)
162			fprintf(stream, "%s", utf8);
163		else
164		{
165			CFRange rangeToProcess = CFRangeMake(0, CFStringGetLength(string));
166			while (rangeToProcess.length > 0)
167			{
168				UInt8 localBuffer[256];
169				CFIndex usedBufferLength;
170				CFIndex numChars = CFStringGetBytes(string, rangeToProcess,
171					kCFStringEncodingUTF8, '?', FALSE, localBuffer,
172					sizeof(localBuffer), &usedBufferLength);
173				if (numChars == 0)
174					break;   // Failed to convert anything...
175
176				fprintf(stream, "%.*s", (int)usedBufferLength, localBuffer);
177				rangeToProcess.location += numChars;
178				rangeToProcess.length -= numChars;
179			}
180		}
181	}
182}
183
184
185int
186print_access(FILE *stream, SecAccessRef access, Boolean interactive)
187{
188	CFArrayRef aclList = NULL;
189	CFIndex aclix, aclCount;
190	int result = 0;
191	OSStatus status;
192
193	status = SecAccessCopyACLList(access, &aclList);
194	if (status)
195	{
196		cssmPerror("SecAccessCopyACLList", status);
197		result = 1;
198		goto loser;
199	}
200
201	aclCount = CFArrayGetCount(aclList);
202	fprintf(stream, "access: %lu entries\n", aclCount);
203	for (aclix = 0; aclix < aclCount; ++aclix)
204	{
205		CFArrayRef applicationList = NULL;
206		CFStringRef description = NULL;
207		CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector = {};
208		CFIndex appix, appCount;
209
210		SecACLRef acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, aclix);
211		CSSM_ACL_AUTHORIZATION_TAG tags[64]; // Pick some upper limit
212		uint32 tagix, tagCount = sizeof(tags) / sizeof(*tags);
213		status = SecACLGetAuthorizations(acl, tags, &tagCount);
214		if (status)
215		{
216			cssmPerror("SecACLGetAuthorizations", status);
217			result = 1;
218			goto loser;
219		}
220
221		fprintf(stream, "    entry %lu:\n        authorizations (%lu):", aclix, (unsigned long)tagCount);
222		for (tagix = 0; tagix < tagCount; ++tagix)
223		{
224			CSSM_ACL_AUTHORIZATION_TAG tag = tags[tagix];
225			switch (tag)
226			{
227			case CSSM_ACL_AUTHORIZATION_ANY:
228				fputs(" any", stream);
229				break;
230			case CSSM_ACL_AUTHORIZATION_LOGIN:
231				fputs(" login", stream);
232				break;
233			case CSSM_ACL_AUTHORIZATION_GENKEY:
234				fputs(" genkey", stream);
235				break;
236			case CSSM_ACL_AUTHORIZATION_DELETE:
237				fputs(" delete", stream);
238				break;
239			case CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED:
240				fputs(" export_wrapped", stream);
241				break;
242			case CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR:
243				fputs(" export_clear", stream);
244				break;
245			case CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED:
246				fputs(" import_wrapped", stream);
247				break;
248			case CSSM_ACL_AUTHORIZATION_IMPORT_CLEAR:
249				fputs(" import_clear", stream);
250				break;
251			case CSSM_ACL_AUTHORIZATION_SIGN:
252				fputs(" sign", stream);
253				break;
254			case CSSM_ACL_AUTHORIZATION_ENCRYPT:
255				fputs(" encrypt", stream);
256				break;
257			case CSSM_ACL_AUTHORIZATION_DECRYPT:
258				fputs(" decrypt", stream);
259				break;
260			case CSSM_ACL_AUTHORIZATION_MAC:
261				fputs(" mac", stream);
262				break;
263			case CSSM_ACL_AUTHORIZATION_DERIVE:
264				fputs(" derive", stream);
265				break;
266			case CSSM_ACL_AUTHORIZATION_DBS_CREATE:
267				fputs(" dbs_create", stream);
268				break;
269			case CSSM_ACL_AUTHORIZATION_DBS_DELETE:
270				fputs(" dbs_delete", stream);
271				break;
272			case CSSM_ACL_AUTHORIZATION_DB_READ:
273				fputs(" db_read", stream);
274				break;
275			case CSSM_ACL_AUTHORIZATION_DB_INSERT:
276				fputs(" db_insert", stream);
277				break;
278			case CSSM_ACL_AUTHORIZATION_DB_MODIFY:
279				fputs(" db_modify", stream);
280				break;
281			case CSSM_ACL_AUTHORIZATION_DB_DELETE:
282				fputs(" db_delete", stream);
283				break;
284			case CSSM_ACL_AUTHORIZATION_CHANGE_ACL:
285				fputs(" change_acl", stream);
286				break;
287			case CSSM_ACL_AUTHORIZATION_CHANGE_OWNER:
288				fputs(" change_owner", stream);
289				break;
290			default:
291				fprintf(stream, " tag=%lu", (unsigned long)tag);
292				break;
293			}
294		}
295		fputc('\n', stream);
296
297		status = SecACLCopySimpleContents(acl, &applicationList, &description, &promptSelector);
298		if (status)
299		{
300			cssmPerror("SecACLCopySimpleContents", status);
301			continue;
302		}
303
304		if (promptSelector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE)
305			fputs("        require-password\n", stream);
306		else
307			fputs("        don't-require-password\n", stream);
308
309		fputs("        description: ", stream);
310		print_cfstring(stream, description);
311		fputc('\n', stream);
312
313		if (applicationList)
314		{
315			appCount = CFArrayGetCount(applicationList);
316			fprintf(stream, "        applications (%lu):\n", appCount);
317		}
318		else
319		{
320			appCount = 0;
321			fprintf(stream, "        applications: <null>\n");
322		}
323
324		for (appix = 0; appix < appCount; ++appix)
325		{
326			const UInt8* bytes;
327			SecTrustedApplicationRef app = (SecTrustedApplicationRef)CFArrayGetValueAtIndex(applicationList, appix);
328			CFDataRef data = NULL;
329			fprintf(stream, "            %lu: ", appix);
330			status = SecTrustedApplicationCopyData(app, &data);
331			if (status)
332			{
333				cssmPerror("SecTrustedApplicationCopyData", status);
334				continue;
335			}
336
337			bytes = CFDataGetBytePtr(data);
338			if (bytes && bytes[0] == 0x2f) {
339				fprintf(stream, "%s", (const char *)bytes);
340				if ((status = SecTrustedApplicationValidateWithPath(app, (const char *)bytes)) == noErr) {
341					fprintf(stream, " (OK)");
342				} else {
343					fprintf(stream, " (status %ld)", status);
344				}
345				fprintf(stream, "\n");
346			} else {
347				print_cfdata(stream, data);
348				fputc('\n', stream);
349			}
350			if (data)
351				CFRelease(data);
352		}
353
354
355		if (interactive)
356		{
357			char buffer[10] = {};
358			if(applicationList != NULL) {
359				fprintf(stderr, "NULL out this application list? ");
360				if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
361				{
362					/*
363					 * This makes the ops in this entry wide-open, no dialog or confirmation
364					 * other than requiring the keychain be open.
365					 */
366					fprintf(stderr, "setting app list to NULL\n");
367					status = SecACLSetSimpleContents(acl, NULL, description, &promptSelector);
368					if (status)
369					{
370						cssmPerror("SecACLSetSimpleContents", status);
371						continue;
372					}
373				}
374				else {
375					fprintf(stderr, "Set this application list to empty array? ");
376					if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
377					{
378						/*
379						 * This means "always get confirmation, from all apps".
380						 */
381						fprintf(stderr, "setting app list to empty array\n");
382						status = SecACLSetSimpleContents(acl,
383							CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks),
384							description, &promptSelector);
385						if (status)
386						{
387							cssmPerror("SecACLSetSimpleContents", status);
388							continue;
389						}
390					}
391				}
392			}
393			else {
394				fprintf(stderr, "Remove this acl? ");
395				if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
396				{
397					/*
398					 * This make ths ops in this entry completely inaccessible.
399					 */
400					fprintf(stderr, "removing acl\n");
401					status = SecACLRemove(acl);
402					if (status)
403					{
404						cssmPerror("SecACLRemove", status);
405						continue;
406					}
407				}
408			}
409		}
410		if (description)
411			CFRelease(description);
412		if (applicationList)
413			CFRelease(applicationList);
414
415	}
416
417loser:
418	if (aclList)
419		CFRelease(aclList);
420
421	return result;
422}
423
424/* Simluate what StickyRecord is trying to do.... */
425
426/*
427 * Given an Access object:
428 *  -- extract the ACL for the specified CSSM_ACL_AUTHORIZATION_TAG. We expect there
429 *     to exactly one of these - if the form of a default ACL changes we'll have to
430 *	   revisit this.
431 *  -- set the ACL's app list to the provided CFArray, which may be NULL (meaning
432 *     "any app can access this, no problem"), an empty array (meaning "always
433 *     prompt"), or an actual app list.
434 *  -- set or clear the PROMPT_REQUIRE_PASSPHRASE bit per the requirePassphrase
435 *     argument
436 */
437static OSStatus srUpdateAcl(
438	SecAccessRef accessRef,
439	CSSM_ACL_AUTHORIZATION_TAG whichAcl,	// e.g. CSSM_ACL_AUTHORIZATION_DECRYPT
440	CFArrayRef appArray,
441	bool requirePassphrase)
442{
443	OSStatus ortn;
444	CFArrayRef aclList = NULL;
445
446	ortn = SecAccessCopySelectedACLList(accessRef, whichAcl, &aclList);
447	if(ortn) {
448		cssmPerror("SecAccessCopySelectedACLList", ortn);
449		return ortn;
450	}
451
452	if(CFArrayGetCount(aclList) != 1) {
453		printf("StickyRecord::updateAcl - unexpected ACL list count (%d)",
454			(int)CFArrayGetCount(aclList));
455		return internalComponentErr;
456	}
457	SecACLRef acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
458
459	CFArrayRef applicationList = NULL;
460	CFStringRef description = NULL;
461	CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector = {};
462	ortn = SecACLCopySimpleContents(acl, &applicationList, &description, &promptSelector);
463	if(ortn) {
464		cssmPerror("SecACLCopySimpleContents", ortn);
465		return ortn;
466	}
467	if(applicationList != NULL) {
468		CFRelease(applicationList);
469	}
470	if(requirePassphrase) {
471		promptSelector.flags |= CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
472	}
473	else {
474		promptSelector.flags &= ~CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
475	}
476	/* update */
477	ortn = SecACLSetSimpleContents(acl, appArray, description, &promptSelector);
478
479	/* we got this from SecACLCopySimpleContents - release it regardless */
480	if(description != NULL) {
481		CFRelease(description);
482	}
483	if(ortn) {
484		cssmPerror("SecACLSetSimpleContents", ortn);
485	}
486	if(aclList != NULL) {
487		CFRelease(aclList);
488	}
489	return ortn;
490}
491
492OSStatus stickyRecordUpdateAcl(
493	SecAccessRef accessRef)
494{
495	OSStatus ortn;
496
497	printf("...updating ACL to simulate a StickyRecord\n");
498
499	/* First: decrypt. Wide open (NULL app list), !REQUIRE_PASSPHRASE. */
500	ortn = srUpdateAcl(accessRef, CSSM_ACL_AUTHORIZATION_DECRYPT, NULL, false);
501	if(ortn) {
502		return ortn;
503	}
504
505	/* encrypt: always ask (empty app list, require passphrase */
506	CFArrayRef nullArray = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
507	return srUpdateAcl(accessRef, CSSM_ACL_AUTHORIZATION_ENCRYPT, nullArray, true);
508
509}
510