+/*
+ * Copyright (c) 2008-2009,2012,2014 Apple 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@
+ *
+ * access_utils.c
+ */
+
+#include "access_utils.h"
+#include "security_tool.h"
+#include <stdio.h>
+#include <Security/SecKeychainItem.h>
+#include <Security/SecAccess.h>
+#include <Security/SecACL.h>
+
+// create_access
+//
+// This function creates a SecAccessRef with an array of trusted applications.
+//
+int
+create_access(const char *accessName, Boolean allowAny, CFArrayRef trustedApps, SecAccessRef *access)
+{
+ int result = 0;
+ CFArrayRef appList = NULL;
+ CFArrayRef aclList = NULL;
+ CFStringRef description = NULL;
+ const char *descriptionLabel = (accessName) ? accessName : "<unlabeled key>";
+ CFStringRef promptDescription = NULL;
+ CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector;
+ SecACLRef aclRef;
+ OSStatus status;
+
+ if (accessName) {
+ description = CFStringCreateWithCString(NULL, descriptionLabel, kCFStringEncodingUTF8);
+ }
+
+ status = SecAccessCreate(description, trustedApps, access);
+ if (status)
+ {
+ sec_perror("SecAccessCreate", status);
+ result = 1;
+ goto cleanup;
+ }
+
+ // get the access control list for decryption operations (this controls access to an item's data)
+ status = SecAccessCopySelectedACLList(*access, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList);
+ if (status)
+ {
+ sec_perror("SecAccessCopySelectedACLList", status);
+ result = 1;
+ goto cleanup;
+ }
+
+ // get the first entry in the access control list
+ aclRef = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
+ status = SecACLCopySimpleContents(aclRef, &appList, &promptDescription, &promptSelector);
+ if (status)
+ {
+ sec_perror("SecACLCopySimpleContents", status);
+ result = 1;
+ goto cleanup;
+ }
+
+ if (allowAny) // "allow all applications to access this item"
+ {
+ // change the decryption ACL to not require the passphrase, and have a nil application list.
+ promptSelector.flags &= ~CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
+ status = SecACLSetSimpleContents(aclRef, NULL, promptDescription, &promptSelector);
+ }
+ else // "allow access by these applications"
+ {
+ // modify the application list
+ status = SecACLSetSimpleContents(aclRef, trustedApps, promptDescription, &promptSelector);
+ }
+ if (status)
+ {
+ sec_perror("SecACLSetSimpleContents", status);
+ result = 1;
+ goto cleanup;
+ }
+
+cleanup:
+ if (description)
+ CFRelease(description);
+ if (promptDescription)
+ CFRelease(promptDescription);
+ if (appList)
+ CFRelease(appList);
+ if (aclList)
+ CFRelease(aclList);
+
+ return result;
+}
+
+// merge_access
+//
+// This function merges the contents of otherAccess into access.
+// Simple ACL contents are assumed, and only the standard ACL
+// for decryption operations is currently processed.
+//
+int
+merge_access(SecAccessRef access, SecAccessRef otherAccess)
+{
+ OSStatus status;
+ CFArrayRef aclList, newAclList;
+
+ // get existing access control list for decryption operations (this controls access to an item's data)
+ status = SecAccessCopySelectedACLList(access, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList);
+ if (status) {
+ return status;
+ }
+ // get desired access control list for decryption operations
+ status = SecAccessCopySelectedACLList(otherAccess, CSSM_ACL_AUTHORIZATION_DECRYPT, &newAclList);
+ if (status) {
+ newAclList = nil;
+ } else {
+ SecACLRef aclRef=(SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
+ SecACLRef newAclRef=(SecACLRef)CFArrayGetValueAtIndex(newAclList, 0);
+ CFArrayRef appList=nil;
+ CFArrayRef newAppList=nil;
+ CFMutableArrayRef mergedAppList = nil;
+ CFStringRef promptDescription=nil;
+ CFStringRef newPromptDescription=nil;
+ CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector;
+ CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR newPromptSelector;
+
+ status = SecACLCopySimpleContents(aclRef, &appList, &promptDescription, &promptSelector);
+ if (!status) {
+ status = SecACLCopySimpleContents(newAclRef, &newAppList, &newPromptDescription, &newPromptSelector);
+ }
+ if (!status) {
+ if (appList) {
+ mergedAppList = CFArrayCreateMutableCopy(NULL, 0, appList);
+ }
+ if (newAppList) {
+ if (mergedAppList) {
+ CFArrayAppendArray(mergedAppList, newAppList, CFRangeMake(0, CFArrayGetCount(newAppList)));
+ } else {
+ mergedAppList = CFArrayCreateMutableCopy(NULL, 0, newAppList);
+ }
+ }
+ promptSelector.flags = newPromptSelector.flags;
+ status = SecACLSetSimpleContents(aclRef, mergedAppList, newPromptDescription, &newPromptSelector);
+ }
+
+ if (appList) CFRelease(appList);
+ if (newAppList) CFRelease(newAppList);
+ if (mergedAppList) CFRelease(mergedAppList);
+ if (promptDescription) CFRelease(promptDescription);
+ if (newPromptDescription) CFRelease(newPromptDescription);
+ }
+ if (aclList) CFRelease(aclList);
+ if (newAclList) CFRelease(newAclList);
+
+ return status;
+}
+
+// modify_access
+//
+// This function updates the access for an existing item.
+// The provided access is merged with the item's existing access.
+//
+int
+modify_access(SecKeychainItemRef itemRef, SecAccessRef access)
+{
+ OSStatus status;
+ SecAccessRef curAccess = NULL;
+ // for historical reasons, we have to modify the item's existing access reference
+ // (setting the item's access to a freshly created SecAccessRef instance doesn't behave as expected)
+ status = SecKeychainItemCopyAccess(itemRef, &curAccess);
+ if (status) {
+ sec_error("SecKeychainItemCopyAccess: %s", sec_errstr(status));
+ } else {
+ status = merge_access(curAccess, access); // make changes to the existing access reference
+ if (!status) {
+ status = SecKeychainItemSetAccess(itemRef, curAccess); // will prompt!
+ if (status) {
+ sec_error("SecKeychainItemSetAccess: %s", sec_errstr(status));
+ }
+ }
+ }
+ if (curAccess) {
+ CFRelease(curAccess);
+ }
+ return status;
+}