1/*
2 * Copyright (c) 2008-2009 Apple 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 *  access_utils.c
24 */
25
26#include "access_utils.h"
27#include "security.h"
28#include <stdio.h>
29#include <Security/SecKeychainItem.h>
30#include <Security/SecAccess.h>
31#include <Security/SecACL.h>
32
33// create_access
34//
35// This function creates a SecAccessRef with an array of trusted applications.
36//
37int
38create_access(const char *accessName, Boolean allowAny, CFArrayRef trustedApps, SecAccessRef *access)
39{
40	int result = 0;
41	CFArrayRef appList = NULL;
42	CFArrayRef aclList = NULL;
43	CFStringRef description = NULL;
44	const char *descriptionLabel = (accessName) ? accessName : "<unlabeled key>";
45	CFStringRef promptDescription = NULL;
46	CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector;
47	SecACLRef aclRef;
48	OSStatus status;
49
50	if (accessName) {
51		description = CFStringCreateWithCString(NULL, descriptionLabel, kCFStringEncodingUTF8);
52	}
53
54	status = SecAccessCreate(description, trustedApps, access);
55	if (status)
56	{
57		sec_perror("SecAccessCreate", status);
58		result = 1;
59		goto cleanup;
60	}
61
62	// get the access control list for decryption operations (this controls access to an item's data)
63	status = SecAccessCopySelectedACLList(*access, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList);
64	if (status)
65	{
66		sec_perror("SecAccessCopySelectedACLList", status);
67		result = 1;
68		goto cleanup;
69	}
70
71	// get the first entry in the access control list
72	aclRef = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
73	status = SecACLCopySimpleContents(aclRef, &appList, &promptDescription, &promptSelector);
74	if (status)
75	{
76		sec_perror("SecACLCopySimpleContents", status);
77		result = 1;
78		goto cleanup;
79	}
80
81	if (allowAny) // "allow all applications to access this item"
82	{
83		// change the decryption ACL to not require the passphrase, and have a nil application list.
84		promptSelector.flags &= ~CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
85		status = SecACLSetSimpleContents(aclRef, NULL, promptDescription, &promptSelector);
86	}
87	else // "allow access by these applications"
88	{
89		// modify the application list
90		status = SecACLSetSimpleContents(aclRef, trustedApps, promptDescription, &promptSelector);
91	}
92	if (status)
93	{
94		sec_perror("SecACLSetSimpleContents", status);
95		result = 1;
96		goto cleanup;
97	}
98
99cleanup:
100	if (description)
101		CFRelease(description);
102	if (promptDescription)
103		CFRelease(promptDescription);
104	if (appList)
105		CFRelease(appList);
106	if (aclList)
107		CFRelease(aclList);
108
109	return result;
110}
111
112// merge_access
113//
114// This function merges the contents of otherAccess into access.
115// Simple ACL contents are assumed, and only the standard ACL
116// for decryption operations is currently processed.
117//
118int
119merge_access(SecAccessRef access, SecAccessRef otherAccess)
120{
121	OSStatus status;
122	CFArrayRef aclList, newAclList;
123
124	// get existing access control list for decryption operations (this controls access to an item's data)
125	status = SecAccessCopySelectedACLList(access, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList);
126	if (status) {
127		return status;
128	}
129	// get desired access control list for decryption operations
130	status = SecAccessCopySelectedACLList(otherAccess, CSSM_ACL_AUTHORIZATION_DECRYPT, &newAclList);
131	if (status) {
132		newAclList = nil;
133	} else {
134		SecACLRef aclRef=(SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
135		SecACLRef newAclRef=(SecACLRef)CFArrayGetValueAtIndex(newAclList, 0);
136		CFArrayRef appList=nil;
137		CFArrayRef newAppList=nil;
138		CFMutableArrayRef mergedAppList = nil;
139		CFStringRef promptDescription=nil;
140		CFStringRef newPromptDescription=nil;
141		CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector;
142		CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR newPromptSelector;
143
144		status = SecACLCopySimpleContents(aclRef, &appList, &promptDescription, &promptSelector);
145		if (!status) {
146			status = SecACLCopySimpleContents(newAclRef, &newAppList, &newPromptDescription, &newPromptSelector);
147		}
148		if (!status) {
149			if (appList) {
150				mergedAppList = CFArrayCreateMutableCopy(NULL, 0, appList);
151			}
152			if (newAppList) {
153				if (mergedAppList) {
154					CFArrayAppendArray(mergedAppList, newAppList, CFRangeMake(0, CFArrayGetCount(newAppList)));
155				} else {
156					mergedAppList = CFArrayCreateMutableCopy(NULL, 0, newAppList);
157				}
158			}
159			promptSelector.flags = newPromptSelector.flags;
160			status = SecACLSetSimpleContents(aclRef, mergedAppList, newPromptDescription, &newPromptSelector);
161		}
162
163		if (appList) CFRelease(appList);
164		if (newAppList) CFRelease(newAppList);
165		if (mergedAppList) CFRelease(mergedAppList);
166		if (promptDescription) CFRelease(promptDescription);
167		if (newPromptDescription) CFRelease(newPromptDescription);
168	}
169	if (aclList) CFRelease(aclList);
170	if (newAclList) CFRelease(newAclList);
171
172	return status;
173}
174
175// modify_access
176//
177// This function updates the access for an existing item.
178// The provided access is merged with the item's existing access.
179//
180int
181modify_access(SecKeychainItemRef itemRef, SecAccessRef access)
182{
183	OSStatus status;
184	SecAccessRef curAccess = NULL;
185	// for historical reasons, we have to modify the item's existing access reference
186	// (setting the item's access to a freshly created SecAccessRef instance doesn't behave as expected)
187	status = SecKeychainItemCopyAccess(itemRef, &curAccess);
188	if (status) {
189		sec_error("SecKeychainItemCopyAccess: %s", sec_errstr(status));
190	} else {
191		status = merge_access(curAccess, access); // make changes to the existing access reference
192		if (!status) {
193			status = SecKeychainItemSetAccess(itemRef, curAccess); // will prompt!
194			if (status) {
195				sec_error("SecKeychainItemSetAccess: %s", sec_errstr(status));
196			}
197		}
198	}
199	if (curAccess) {
200		CFRelease(curAccess);
201	}
202	return status;
203}
204