keychain_util.c   [plain text]


/*
 * 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 "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, "SecItemCopyMatching returned unexpected results:");
                        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;
}