--- /dev/null
+/*
+ * Copyright (c) 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@
+ */
+
+
+#include <stdio.h>
+#include <AssertMacros.h>
+#include <utilities/SecCFWrappers.h>
+#include <ACMDefs.h>
+#include <ACMAclDefs.h>
+#include <libaks_acl_cf_keys.h>
+
+#include "security.h"
+#include "keychain_util.h"
+#include <Security/SecAccessControlPriv.h>
+
+static CFStringRef createStringForValue(CFTypeRef value)
+{
+ CFStringRef result = NULL;
+ if (CFDataGetTypeID() == CFGetTypeID(value)) {
+ CFMutableStringRef stringData = CFStringCreateMutable(kCFAllocatorDefault, 0);
+ CFStringAppend(stringData, CFSTR("<"));
+ const UInt8 *dataPtr = CFDataGetBytePtr(value);
+ for (int i = 0; i < CFDataGetLength(value); ++i) {
+ CFStringAppendFormat(stringData, NULL, CFSTR("%02x"), dataPtr[i]);
+ }
+ CFStringAppend(stringData, CFSTR(">"));
+ result = stringData;
+ } else if (CFBooleanGetTypeID() == CFGetTypeID(value)) {
+ if (CFEqual(value, kCFBooleanTrue))
+ result = CFStringCreateWithCString(kCFAllocatorDefault, "true", kCFStringEncodingUTF8);
+ else
+ result = CFStringCreateWithCString(kCFAllocatorDefault, "false", kCFStringEncodingUTF8);
+ } else if (CFNumberGetTypeID() == CFGetTypeID(value))
+ result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), value);
+ else if (CFStringGetTypeID() == CFGetTypeID(value))
+ result = CFStringCreateCopy(kCFAllocatorDefault, value);
+ else
+ result = CFStringCreateWithCString(kCFAllocatorDefault, "unrecognized value", kCFStringEncodingUTF8);
+
+ return result;
+}
+
+static CFStringRef createStringForKofn(CFDictionaryRef kofn)
+{
+ CFMutableStringRef result = NULL;
+ if (kofn != NULL && CFDictionaryGetTypeID() == CFGetTypeID(kofn))
+ {
+ result = CFStringCreateMutable(kCFAllocatorDefault, 0);
+ CFDictionaryForEach(kofn, ^(const void *key, const void *value) {
+
+ CFStringAppend(result, key);
+ CFStringAppend(result, CFSTR("("));
+
+ CFStringRef valueString = createStringForKofn(value) ?: createStringForValue(value);
+ CFStringAppend(result, valueString);
+ CFStringAppend(result, CFSTR(")"));
+ CFReleaseSafe(valueString);
+ });
+ }
+
+ return result;
+}
+
+static CFStringRef createStringForOps(CFDictionaryRef constraints)
+{
+ CFMutableStringRef result = NULL;
+ if (constraints != NULL)
+ {
+ result = CFStringCreateMutable(kCFAllocatorDefault, 0);
+ CFDictionaryForEach(constraints, ^(const void *key, const void *value) {
+ if (CFStringGetLength(result) > 0)
+ CFStringAppend(result, CFSTR(";"));
+ CFStringAppend(result, key);
+ CFStringAppend(result, CFSTR("("));
+ CFStringRef valueString = createStringForKofn(value) ?: createStringForValue(value);
+ CFStringAppend(result, valueString);
+ CFStringAppend(result, CFSTR(")"));
+ CFReleaseSafe(valueString);
+ });
+ }
+
+ return result;
+}
+
+void
+display_sac_line(SecAccessControlRef sac, CFMutableStringRef line)
+{
+ CFTypeRef protection = SecAccessControlGetProtection(sac);
+ if (CFDictionaryGetTypeID() == CFGetTypeID(protection)) {
+ CFStringRef protectionStr = createStringForOps(protection);
+ CFStringAppend(line, protectionStr);
+ CFRelease(protectionStr);
+ } else if (CFStringGetTypeID() == CFGetTypeID(protection))
+ CFStringAppend(line, protection);
+ else
+ CFStringAppend(line, CFSTR("??"));
+
+ CFDictionaryRef constraints = SecAccessControlGetConstraints(sac);
+ CFStringRef constraintsString = createStringForOps(constraints);
+ if (constraintsString) {
+ CFStringAppend(line, CFSTR(";"));
+ CFStringAppend(line, constraintsString);
+ }
+ CFReleaseSafe(constraintsString);
+}
+
+bool
+keychain_query_parse_cstring(CFMutableDictionaryRef q, const char *query) {
+ CFStringRef s;
+ s = CFStringCreateWithCStringNoCopy(0, query, kCFStringEncodingUTF8, kCFAllocatorNull);
+ bool result = keychain_query_parse_string(q, s);
+ CFRelease(s);
+ return result;
+}
+
+/* Parse a string of the form attr=value,attr=value,attr=value */
+bool
+keychain_query_parse_string(CFMutableDictionaryRef q, CFStringRef s) {
+ bool inkey = true;
+ bool escaped = false;
+ bool error = false;
+ CFStringRef key = NULL;
+ CFMutableStringRef str = CFStringCreateMutable(0, 0);
+ CFRange rng = { .location = 0, .length = CFStringGetLength(s) };
+ CFCharacterSetRef cs_key = CFCharacterSetCreateWithCharactersInString(0, CFSTR("=\\"));
+ CFCharacterSetRef cs_value = CFCharacterSetCreateWithCharactersInString(0, CFSTR(",\\"));
+ while (rng.length) {
+ CFRange r;
+ CFStringRef sub;
+ bool complete = false;
+ if (escaped) {
+ r.location = rng.location;
+ r.length = 1;
+ sub = CFStringCreateWithSubstring(0, s, r);
+ escaped = false;
+ } else if (CFStringFindCharacterFromSet(s, inkey ? cs_key : cs_value, rng, 0, &r)) {
+ if (CFStringGetCharacterAtIndex(s, r.location) == '\\') {
+ escaped = true;
+ } else {
+ complete = true;
+ }
+ CFIndex next = r.location + 1;
+ r.length = r.location - rng.location;
+ r.location = rng.location;
+ sub = CFStringCreateWithSubstring(0, s, r);
+ rng.length -= next - rng.location;
+ rng.location = next;
+ } else {
+ sub = CFStringCreateWithSubstring(0, s, rng);
+ rng.location += rng.length;
+ rng.length = 0;
+ complete = true;
+ }
+ CFStringAppend(str, sub);
+ CFRelease(sub);
+
+ if (complete) {
+ CFStringRef value = CFStringCreateCopy(0, str);
+ CFStringReplaceAll(str, CFSTR(""));
+ if (inkey) {
+ key = value;
+ } else {
+ if(key && CFStringCompare(key, kSecAttrAccessControl, kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+ SecAccessControlRef sac = keychain_query_parse_sac(value);
+ if(sac) {
+ CFDictionarySetValue(q, key, sac);
+ } else {
+ fprintf(stderr, "failed to parse access control\n");
+ error = true;
+ }
+ } else {
+ CFDictionarySetValue(q, key, value);
+ }
+ CFReleaseNull(value);
+ CFReleaseNull(key);
+ }
+ inkey = !inkey;
+ }
+ if(error)
+ break;
+ }
+ if (key) {
+ /* Dangeling key value is true?. */
+ CFDictionarySetValue(q, key, kCFBooleanTrue);
+ CFReleaseNull(key);
+ }
+ CFRelease(str);
+ CFRelease(cs_key);
+ CFRelease(cs_value);
+ return error == false;
+}
+
+static uint32_t findLeft(CFStringRef s, uint32_t off)
+{
+ for (int i = off; i < CFStringGetLength(s); ++i) {
+ if (CFStringGetCharacterAtIndex(s, i) == '(')
+ return i;
+ }
+
+ return 0;
+}
+
+static uint32_t findRight(CFStringRef s, uint32_t off)
+{
+ int bracersCount = 0;
+ for (int i = off; i < CFStringGetLength(s); ++i) {
+ if (CFStringGetCharacterAtIndex(s, i) == '(')
+ ++bracersCount;
+
+ if (CFStringGetCharacterAtIndex(s, i) == ')') {
+ --bracersCount;
+ if (bracersCount == 0) {
+ return i;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static bool parseKeyAndValue(CFStringRef string, CFTypeRef *key, CFTypeRef *value);
+
+CF_RETURNS_RETAINED
+static CFTypeRef parseValue(CFStringRef string)
+{
+ CFTypeRef result = NULL, key = NULL, value = NULL;
+ CFMutableDictionaryRef resultDictionary = NULL;
+ CFStringRef subString = NULL;
+
+ uint32_t left = findLeft(string, 0);
+ if (left > 0) {
+ uint32_t offset = 0;
+ resultDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ for (;;) {
+ uint32_t right = findRight(string, left);
+ if (!right)
+ break;
+ CFAssignRetained(subString, CFStringCreateWithSubstring(kCFAllocatorDefault, string, CFRangeMake(offset, (right + 1) - offset)));
+ require_quiet(parseKeyAndValue(subString, &key, &value), out);
+ CFDictionarySetValue(resultDictionary, key, value);
+ offset = right + 1;
+ left = findLeft(string, offset);
+ if (!left)
+ break;
+ }
+ result = CFRetain(resultDictionary);
+ } else if (CFStringGetCharacterAtIndex(string, 0) == '<' && CFStringGetCharacterAtIndex(string, CFStringGetLength(string) - 1) == '>') {
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ if (CFStringGetLength(string) > 2) {
+ CFAssignRetained(subString, CFStringCreateWithSubstring(kCFAllocatorDefault, string, CFRangeMake(1, CFStringGetLength(string) - 2)));
+ const char *asciiString = CFStringGetCStringPtr(subString, kCFStringEncodingASCII);
+ uint8_t byte;
+ for(uint32_t i = 0; i < strlen(asciiString); i += 2) {
+ sscanf(&asciiString[i], "%02hhx", &byte);
+ CFDataAppendBytes(data, &byte, sizeof(byte));
+ }
+ }
+ result = data;
+ } else if (CFStringCompare(string, CFSTR("true"), 0) == kCFCompareEqualTo) {
+ CFRetainAssign(result, kCFBooleanTrue);
+ } else if (CFStringCompare(string, CFSTR("false"), 0) == kCFCompareEqualTo) {
+ CFRetainAssign(result, kCFBooleanFalse);
+ } else if (CFStringCompare(string, CFSTR(kACMPolicyDeviceOwnerAuthentication), 0) == kCFCompareEqualTo) {
+ CFRetainAssign(result, CFSTR(kACMPolicyDeviceOwnerAuthentication));
+ } else {
+ CFLocaleRef currentLocale = CFLocaleCopyCurrent();
+ CFNumberFormatterRef formaterRef = CFNumberFormatterCreate(kCFAllocatorDefault, currentLocale, kCFNumberFormatterDecimalStyle);
+ result = CFNumberFormatterCreateNumberFromString(kCFAllocatorDefault, formaterRef, string, NULL, kCFNumberFormatterParseIntegersOnly);
+ CFReleaseSafe(currentLocale);
+ CFReleaseSafe(formaterRef);
+ }
+
+ if (!result)
+ fprintf(stderr, "Failed to parse value: %s\n", CFStringGetCStringPtr(string, kCFStringEncodingUTF8));
+
+out:
+ CFReleaseSafe(key);
+ CFReleaseSafe(value);
+ CFReleaseSafe(subString);
+ CFReleaseSafe(resultDictionary);
+
+ return result;
+}
+
+static bool parseKeyAndValue(CFStringRef string, CFTypeRef *key, CFTypeRef *value)
+{
+ bool ok = false;
+ CFStringRef keyString = NULL;
+ CFStringRef valueString = NULL;
+ CFTypeRef parsedValue = NULL;
+
+ uint32_t left = findLeft(string, 0);
+ require_action_quiet(left != 0, out, fprintf(stderr, "Failed to find '(' in: %s\n", CFStringGetCStringPtr(string, kCFStringEncodingUTF8)));
+ uint32_t right = findRight(string, left);
+ require_action_quiet(right != 0, out, fprintf(stderr, "Failed to find ')' in: %s\n", CFStringGetCStringPtr(string, kCFStringEncodingUTF8)));
+ require_action_quiet(right == ((uint32_t)CFStringGetLength(string) - 1), out, fprintf(stderr, "Failed to find ')' in: %s\n", CFStringGetCStringPtr(string, kCFStringEncodingUTF8)));
+
+ keyString = CFStringCreateWithSubstring(kCFAllocatorDefault, string, CFRangeMake(0, left));
+ valueString = CFStringCreateWithSubstring(kCFAllocatorDefault, string, CFRangeMake(left + 1, right - left - 1));
+ require_quiet(parsedValue = parseValue(valueString), out);
+ CFRetainAssign(*key, keyString);
+ CFRetainAssign(*value, parsedValue);
+ ok = true;
+
+out:
+ CFReleaseSafe(parsedValue);
+ CFReleaseSafe(keyString);
+ CFReleaseSafe(valueString);
+
+ return ok;
+}
+
+SecAccessControlRef
+keychain_query_parse_sac(CFStringRef s) {
+ SecAccessControlRef sac = NULL, result = NULL;
+ CFTypeRef key = NULL, value = NULL;
+ CFArrayRef tokens = CFStringCreateArrayBySeparatingStrings(NULL, s, CFSTR(";"));
+
+ // process protection part
+ CFStringRef protection = CFArrayGetValueAtIndex(tokens, 0);
+
+ CFErrorRef error = NULL;
+ require_quiet(sac = SecAccessControlCreate(kCFAllocatorDefault, &error), out);
+ require_quiet(SecAccessControlSetProtection(sac, protection, &error), out);
+
+ CFIndex tokensCnt = CFArrayGetCount(tokens);
+ for(CFIndex i = 1; i < tokensCnt; ++i) {
+ require_action_quiet(parseKeyAndValue(CFArrayGetValueAtIndex(tokens, i), &key, &value), out, fprintf(stderr, "Error constructing SecAccessConstraint object\n") );
+
+ if (CFEqual(key, CFSTR(kACMKeyAclParamRequirePasscode)))
+ SecAccessControlSetRequirePassword(sac, CFEqual(value, kCFBooleanTrue)?true:false);
+ else
+ SecAccessControlAddConstraintForOperation(sac, key, value, NULL);
+ }
+
+ SecAccessConstraintRef constraintForDelete = SecAccessControlGetConstraint(sac, kAKSKeyOpDelete);
+ if (!constraintForDelete) {
+ if(!SecAccessControlAddConstraintForOperation(sac, kAKSKeyOpDelete, kCFBooleanTrue, &error)) {
+ fprintf(stderr, "adding delete operation to sac object failed \n");
+ }
+ }
+
+ SecAccessConstraintRef constraintForEncrypt = SecAccessControlGetConstraint(sac, kAKSKeyOpEncrypt);
+ if (!constraintForEncrypt) {
+ if(!SecAccessControlAddConstraintForOperation(sac, kAKSKeyOpEncrypt, kCFBooleanTrue, &error)) {
+ fprintf(stderr, "adding encrypt operation to sac object failed \n");
+ }
+ }
+
+ CFRetainAssign(result, sac);
+
+out:
+ CFReleaseSafe(tokens);
+ CFReleaseSafe(key);
+ CFReleaseSafe(value);
+ CFReleaseSafe(sac);
+
+ return result;
+}