SecAccessControl.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@
 */

/*
 * SecAccessControl.c - CoreFoundation based access control object
 */

#include <AssertMacros.h>
#include <Security/SecAccessControl.h>
#include <Security/SecAccessControlPriv.h>
#include <Security/SecItem.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/SecCFError.h>
#include <utilities/der_plist.h>

#if TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR)
#define USE_KEYSTORE  1
#elif TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
#define USE_KEYSTORE  1
#else /* no keystore on this platform */
#define USE_KEYSTORE  0
#endif

#include <libaks_acl_cf_keys.h>

static CFTypeRef kSecAccessControlKeyProtection = CFSTR("prot");

// TODO: Use real name of this policy from SCred/AppleCredentialManager
CFTypeRef kSecAccessControlPolicyUserPresent = CFSTR("DeviceOwnerAuthenticated");

struct __SecAccessControl {
    CFRuntimeBase _base;
    CFMutableDictionaryRef dict;
};

static CFStringRef SecAccessControlCopyDescription(CFTypeRef cf) {
    SecAccessControlRef access_control = (SecAccessControlRef)cf;
    return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<SecAccessControlRef: %p>"), access_control);
}

static Boolean SecAccessControlCompare(CFTypeRef lhs, CFTypeRef rhs) {
    SecAccessControlRef laccess_control = (SecAccessControlRef)lhs;
    SecAccessControlRef raccess_control = (SecAccessControlRef)rhs;
    return (laccess_control == raccess_control) || CFEqual(laccess_control->dict, raccess_control->dict);
}

static void SecAccessControlDestroy(CFTypeRef cf) {
    SecAccessControlRef access_control = (SecAccessControlRef)cf;
    CFReleaseSafe(access_control->dict);
}

CFGiblisWithCompareFor(SecAccessControl);

SecAccessControlRef SecAccessControlCreate(CFAllocatorRef allocator, CFErrorRef *error) {
    SecAccessControlRef access_control = CFTypeAllocate(SecAccessControl, struct __SecAccessControl, allocator);
	if (!access_control) {
        SecError(errSecAllocate, error, CFSTR("allocate memory for SecAccessControl"));
        return NULL;
    }

    access_control->dict = CFDictionaryCreateMutableForCFTypes(allocator);
    return access_control;
}


SecAccessControlRef SecAccessControlCreateWithFlags(CFAllocatorRef allocator, CFTypeRef protection,
                                                    SecAccessControlCreateFlags flags, CFErrorRef *error) {
    SecAccessControlRef access_control = NULL;
    CFTypeRef constraint = NULL;

    require_quiet(access_control = SecAccessControlCreate(allocator, error), errOut);

    if (!SecAccessControlSetProtection(access_control, protection, error))
        goto errOut;

    if (flags & kSecAccessControlUserPresence) {
        require_quiet(constraint = SecAccessConstraintCreatePolicy(kSecAccessControlPolicyUserPresent, error), errOut);
        require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDecrypt,
                                                                constraint, error), errOut);
        CFReleaseNull(constraint);
        require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDelete, kCFBooleanTrue, error), errOut);
    }

    return access_control;

errOut:
    CFReleaseSafe(access_control);
    CFReleaseSafe(constraint);
    return NULL;
}

CFTypeRef SecAccessControlGetProtection(SecAccessControlRef access_control) {
    return CFDictionaryGetValue(access_control->dict, kSecAccessControlKeyProtection);
}

static bool checkItemInArray(CFTypeRef item, const CFTypeRef *values, CFIndex count, CFStringRef errMessage, CFErrorRef *error) {
    for (CFIndex i = 0; i < count; i++) {
        if (CFEqualSafe(item, values[i])) {
            return true;
        }
    }
    return SecError(errSecParam, error, errMessage, item);
}

#define CheckItemInArray(item, values, msg) \
{ \
    const CFTypeRef vals[] = values; \
    if (!checkItemInArray(item, vals, sizeof(vals)/sizeof(*vals), CFSTR(msg), error)) { \
        return false; \
    } \
}

#define ItemArray(...) { __VA_ARGS__ }


bool SecAccessControlSetProtection(SecAccessControlRef access_control, CFTypeRef protection, CFErrorRef *error) {
    // Verify protection type.
    CheckItemInArray(protection, ItemArray(kSecAttrAccessibleAlways, kSecAttrAccessibleAfterFirstUnlock,
                                           kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAlwaysThisDeviceOnly,
                                           kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
                                           kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                                           kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly),
                     "SecAccessControl: invalid protection %@");

    // Protection valid, use it.
    CFDictionarySetValue(access_control->dict, kSecAccessControlKeyProtection, protection);
    return true;
}

SecAccessConstraintRef SecAccessConstraintCreatePolicy(CFTypeRef policy, CFErrorRef *error) {
    return CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kAKSKeyAclConstraintPolicy, policy, NULL);
}

SecAccessConstraintRef SecAccessConstraintCreatePasscode(bool systemPasscode) {
    return CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kAKSKeyAclConstraintUserPasscode, kCFBooleanTrue, NULL);
}

SecAccessConstraintRef SecAccessConstraintCreateTouchID(CFDataRef uuid, CFErrorRef *error) {
    return CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kAKSKeyAclConstraintBio,
                                                   uuid ? uuid : (const void *)kCFBooleanTrue, NULL);
}

SecAccessConstraintRef SecAccessConstraintCreateKofN(size_t numRequired, CFArrayRef constraints, CFErrorRef *error) {
    CFNumberRef k = CFNumberCreateWithCFIndex(kCFAllocatorDefault, numRequired);
    CFMutableDictionaryRef kofn = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kAKSKeyAclParamKofN, k, NULL);
    CFRelease(k);

    /* Populate kofn dictionary with constraint keys from the array. note that for now we just ignore any additional
       constraint parameters, but we might err-out if some parameter is found, since we cannot propagate parameteres
       into k-of-n dictionary. */
    const CFTypeRef keysToCopy[] = { kAKSKeyAclConstraintBio, kAKSKeyAclConstraintPolicy,
        kAKSKeyAclConstraintUserPasscode };
    SecAccessConstraintRef constraint;
    CFArrayForEachC(constraints, constraint) {
        require_quiet(isDictionary(constraint), errOut);
        bool found = false;
        for (CFIndex i = 0; i < (CFIndex)(sizeof(keysToCopy) / sizeof(keysToCopy[0])); i++) {
            CFTypeRef value = CFDictionaryGetValue(constraint, keysToCopy[i]);
            if (value) {
                CFDictionarySetValue(kofn, keysToCopy[i], value);
                found = true;
                break;
            }
        }
        require_quiet(found, errOut);
    }

    constraint = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kAKSKeyAclConstraintKofN, kofn, NULL);
    CFRelease(kofn);
    return constraint;

errOut:
    SecError(errSecParam, error, CFSTR("SecAccessControl: invalid constraint for k-of-n"));
    CFReleaseSafe(kofn);
    return NULL;
}

bool SecAccessConstraintSetOption(SecAccessConstraintRef constraint, CFTypeRef option, CFTypeRef value, CFErrorRef *error) {
    CheckItemInArray(option, ItemArray(kAKSKeyAclConstraintAccessGroups, kAKSKeyAclParamCredentialMaxAge),
                     "SecAccessControl: invalid constraint option %@");
    CFDictionarySetValue((CFMutableDictionaryRef)constraint, option, value);
    return true;
}

bool SecAccessControlAddConstraintForOperation(SecAccessControlRef access_control, CFTypeRef operation, CFTypeRef constraint, CFErrorRef *error) {
    CheckItemInArray(operation, ItemArray(kAKSKeyOpEncrypt, kAKSKeyOpDecrypt,
                                          kAKSKeyOpSync, kAKSKeyOpDefaultAcl, kAKSKeyOpDelete),
                     "SecAccessControl: invalid operation %@");
    if (!isDictionary(constraint) && !CFEqual(constraint, kCFBooleanTrue) && !CFEqual(constraint, kCFBooleanFalse) ) {
        return SecError(errSecParam, error, CFSTR("invalid constraint"));
    }

    CFMutableDictionaryRef ops = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
    if (!ops) {
        ops = CFDictionaryCreateMutableForCFTypes(CFGetAllocator(access_control));
        CFDictionarySetValue(access_control->dict, kAKSKeyAcl, ops);
    }
    CFDictionarySetValue(ops, operation, constraint);
    return true;
}

void SecAccessControlRemoveConstraintForOperation(SecAccessControlRef access_control, CFTypeRef operation) {
    CFMutableDictionaryRef ops = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
    if (ops)
        CFDictionaryRemoveValue(ops, operation);
}

SecAccessConstraintRef SecAccessControlGetConstraint(SecAccessControlRef access_control, CFTypeRef operation) {
    CFMutableDictionaryRef ops = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
    if (!ops || CFDictionaryGetCount(ops) == 0)
        // No ACL is present, this means that everything is allowed.
        return kCFBooleanTrue;

    SecAccessConstraintRef constraint = CFDictionaryGetValue(ops, operation);
    if (!constraint) {
        constraint = CFDictionaryGetValue(ops, kAKSKeyOpDefaultAcl);
    }
    return constraint;
}

CFDictionaryRef SecAccessControlGetConstraints(SecAccessControlRef access_control) {
    return CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
}

void SecAccessControlSetConstraints(SecAccessControlRef access_control, CFDictionaryRef constraints) {
    CFMutableDictionaryRef mutableConstraints = CFDictionaryCreateMutableCopy(NULL, 0, constraints);
    CFDictionarySetValue(access_control->dict, kAKSKeyAcl, mutableConstraints);
    CFReleaseSafe(mutableConstraints);
}

void SecAccessControlSetAccessGroups(SecAccessControlRef access_control, CFArrayRef access_groups) {
    CFMutableDictionaryRef ops = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
    if (!ops) {
        ops = CFDictionaryCreateMutableForCFTypes(CFGetAllocator(access_control));
        CFDictionarySetValue(access_control->dict, kAKSKeyAcl, ops);
    }
    CFDictionarySetValue(ops, kAKSKeyAccessGroups, access_groups);
}

CFArrayRef SecAccessControlGetAccessGroups(SecAccessControlRef access_control, CFTypeRef operation) {
    CFMutableDictionaryRef ops = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
    if (!ops)
        return NULL;

    CFArrayRef access_groups = NULL;
    SecAccessConstraintRef constraint = CFDictionaryGetValue(ops, operation);
    if (!constraint) {
        constraint = CFDictionaryGetValue(ops, kAKSKeyOpDefaultAcl);
    }
    if (constraint && isDictionary(constraint)) {
        access_groups = CFDictionaryGetValue(constraint, kAKSKeyAclConstraintAccessGroups);
    }
    if (!access_groups) {
        access_groups = CFDictionaryGetValue(ops, kAKSKeyAccessGroups);
    }
    return access_groups;
}

CFDataRef SecAccessControlCopyData(SecAccessControlRef access_control) {
    size_t len = der_sizeof_plist(access_control->dict, NULL);
    CFMutableDataRef encoded = CFDataCreateMutable(0, len);
    CFDataSetLength(encoded, len);
    uint8_t *der_end = CFDataGetMutableBytePtr(encoded);
    const uint8_t *der = der_end;
    der_end += len;
    der_end = der_encode_plist(access_control->dict, NULL, der, der_end);
    if (!der_end) {
        CFReleaseNull(encoded);
    }
    return encoded;
}

SecAccessControlRef SecAccessControlCreateFromData(CFAllocatorRef allocator, CFDataRef data, CFErrorRef *error) {
    SecAccessControlRef access_control;
    require_quiet(access_control = SecAccessControlCreate(allocator, error), errOut);

    CFPropertyListRef plist;
    const uint8_t *der = CFDataGetBytePtr(data);
    const uint8_t *der_end = der + CFDataGetLength(data);
    require_quiet(der = der_decode_plist(0, kCFPropertyListMutableContainers, &plist, error, der, der_end), errOut);
    if (der != der_end) {
        SecError(errSecDecode, error, CFSTR("trailing garbage at end of SecAccessControl data"));
        goto errOut;
    }

    CFReleaseSafe(access_control->dict);
    access_control->dict = (CFMutableDictionaryRef)plist;
    return access_control;

errOut:
    CFReleaseSafe(access_control);
    return NULL;
}