SecAccessControl.m [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.m - CoreFoundation based access control object
*/
#include <TargetConditionals.h>
#include <AssertMacros.h>
#include "SecAccessControl.h"
#include "SecAccessControlPriv.h"
#include "SecItem.h"
#include "SecItemPriv.h"
#include <utilities/SecCFWrappers.h>
#include <utilities/SecCFError.h>
#include <utilities/der_plist.h>
#include <libaks_acl_cf_keys.h>
#include <ACMDefs.h>
#include <ACMAclDefs.h>
static CFTypeRef kSecAccessControlKeyProtection = CFSTR("prot");
static CFTypeRef kSecAccessControlKeyBound = CFSTR("bound");
struct __SecAccessControl {
CFRuntimeBase _base;
CFMutableDictionaryRef dict;
};
static SecAccessConstraintRef SecAccessConstraintCreateValueOfKofN(CFAllocatorRef allocator, size_t numRequired, CFArrayRef constraints, CFErrorRef *error);
static void dumpValue(id value, NSMutableString *target, NSString *separator) {
if (value == nil) {
// Do nothing.
} else if (CFGetTypeID((__bridge CFTypeRef)value) == CFBooleanGetTypeID()) {
[target appendString:[value boolValue] ? @"true" : @"false"];
} else if ([value isKindOfClass:NSNumber.class]) {
[target appendString:[value string]];
} else if ([value isKindOfClass:NSString.class]) {
[target appendString:value];
} else if ([value isKindOfClass:NSDictionary.class]) {
[value enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[target appendString:separator];
dumpValue(key, target, @"");
[target appendString:@"("];
dumpValue(obj, target, @"");
[target appendString:@")"];
}];
}
}
static CFStringRef SecAccessControlCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
SecAccessControlRef access_control = (SecAccessControlRef)cf;
NSDictionary *contents = (__bridge NSDictionary *)access_control->dict;
NSMutableString *dump = [NSMutableString string];
dumpValue(contents[(__bridge id)kSecAccessControlKeyProtection], dump, @"");
dumpValue(contents[(__bridge id)kAKSKeyAcl], dump, @";");
return CFBridgingRetain([NSString stringWithFormat:@"<SecAccessControlRef: }
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);
static CFMutableDictionaryRef SecAccessControlGetMutableConstraints(SecAccessControlRef access_control) {
CFMutableDictionaryRef constraints = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
if (!constraints) {
CFMutableDictionaryRef newConstraints = CFDictionaryCreateMutableForCFTypes(CFGetAllocator(access_control));
CFDictionarySetValue(access_control->dict, kAKSKeyAcl, newConstraints);
CFRelease(newConstraints);
constraints = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
}
return constraints;
}
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;
}
static CFDataRef _getEmptyData() {
static CFMutableDataRef emptyData = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
emptyData = CFDataCreateMutable(kCFAllocatorDefault, 0);
});
return emptyData;
}
SecAccessControlRef SecAccessControlCreateWithFlags(CFAllocatorRef allocator, CFTypeRef protection,
SecAccessControlCreateFlags flags, CFErrorRef *error) {
SecAccessControlRef access_control = NULL;
CFTypeRef constraint = NULL;
CFMutableArrayRef constraints = NULL;
require_quiet(access_control = SecAccessControlCreate(allocator, error), errOut);
if (!SecAccessControlSetProtection(access_control, protection, error))
goto errOut;
if (flags) {
bool or = (flags & kSecAccessControlOr) ? true : false;
bool and = (flags & kSecAccessControlAnd) ? true : false;
if (or && and) {
SecError(errSecParam, error, CFSTR("only one logical operation can be set"));
goto errOut;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
SecAccessControlCreateFlags maskedFlags = flags & (kSecAccessControlBiometryAny | kSecAccessControlBiometryCurrentSet);
if (maskedFlags && maskedFlags != kSecAccessControlBiometryAny && maskedFlags != kSecAccessControlBiometryCurrentSet) {
SecError(errSecParam, error, CFSTR("only one bio constraint can be set"));
goto errOut;
}
if (flags & kSecAccessControlUserPresence && flags & ~(kSecAccessControlUserPresence | kSecAccessControlApplicationPassword | kSecAccessControlPrivateKeyUsage)) {
SecError(errSecParam, error, CFSTR("kSecAccessControlUserPresence can be combined only with kSecAccessControlApplicationPassword and kSecAccessControlPrivateKeyUsage"));
goto errOut;
}
constraints = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks);
if (flags & kSecAccessControlUserPresence) {
require_quiet(constraint = SecAccessConstraintCreatePolicy(allocator, CFSTR(kACMPolicyDeviceOwnerAuthentication), error), errOut);
CFArrayAppendValue(constraints, constraint);
CFReleaseNull(constraint);
}
if (flags & kSecAccessControlDevicePasscode) {
require_quiet(constraint = SecAccessConstraintCreatePasscode(allocator), errOut);
CFArrayAppendValue(constraints, constraint);
CFReleaseNull(constraint);
}
if (flags & kSecAccessControlBiometryAny) {
require_quiet(constraint = SecAccessConstraintCreateBiometryAny(allocator, _getEmptyData()), errOut);
CFArrayAppendValue(constraints, constraint);
CFReleaseNull(constraint);
}
if (flags & kSecAccessControlBiometryCurrentSet) {
require_quiet(constraint = SecAccessConstraintCreateBiometryCurrentSet(allocator, _getEmptyData(), _getEmptyData()), errOut);
CFArrayAppendValue(constraints, constraint);
CFReleaseNull(constraint);
}
#pragma clang diagnostic pop
if (flags & kSecAccessControlApplicationPassword) {
SecAccessControlSetRequirePassword(access_control, true);
}
CFIndex constraints_count = CFArrayGetCount(constraints);
if (constraints_count > 1) {
require_quiet(constraint = SecAccessConstraintCreateValueOfKofN(allocator, or?1:constraints_count, constraints, error), errOut);
if (flags & kSecAccessControlPrivateKeyUsage) {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpSign, constraint, error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpComputeKey, constraint, error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpAttest, kCFBooleanTrue, error), errOut);
}
else {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDecrypt, constraint, error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpEncrypt, kCFBooleanTrue, error), errOut);
}
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDelete, kCFBooleanTrue, error), errOut);
CFReleaseNull(constraint);
} else if (constraints_count == 1) {
if (flags & kSecAccessControlPrivateKeyUsage) {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpSign, CFArrayGetValueAtIndex(constraints, 0), error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpComputeKey, CFArrayGetValueAtIndex(constraints, 0), error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpAttest, kCFBooleanTrue, error), errOut);
}
else {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDecrypt, CFArrayGetValueAtIndex(constraints, 0), error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpEncrypt, kCFBooleanTrue, error), errOut);
}
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDelete, kCFBooleanTrue, error), errOut);
} else {
if (flags & kSecAccessControlPrivateKeyUsage) {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpSign, kCFBooleanTrue, error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpComputeKey, kCFBooleanTrue, error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpAttest, kCFBooleanTrue, error), errOut);
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDelete, kCFBooleanTrue, error), errOut);
}
else {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDefaultAcl, kCFBooleanTrue, error), errOut);
}
}
CFReleaseNull(constraints);
}
else {
require_quiet(SecAccessControlAddConstraintForOperation(access_control, kAKSKeyOpDefaultAcl, kCFBooleanTrue, error), errOut);
}
return access_control;
errOut:
CFReleaseSafe(access_control);
CFReleaseSafe(constraints);
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, CFSTR("}
#define CheckItemInArray(item, values, msg) \
{ \
const CFTypeRef vals[] = values; \
if (!checkItemInArray(item, vals, sizeof(vals)/sizeof(*vals), msg, error)) { \
return false; \
} \
}
#define ItemArray(...) { __VA_ARGS__ }
bool SecAccessControlSetProtection(SecAccessControlRef access_control, CFTypeRef protection, CFErrorRef *error) {
if (!protection || CFGetTypeID(protection) != CFDictionaryGetTypeID()) {
// Verify protection type.
CheckItemInArray(protection, ItemArray(kSecAttrAccessibleAlwaysPrivate, kSecAttrAccessibleAfterFirstUnlock,
kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAttrAccessibleUntilReboot),
CFSTR("SecAccessControl: invalid protection"));
}
// Protection valid, use it.
CFDictionarySetValue(access_control->dict, kSecAccessControlKeyProtection, protection);
return true;
}
SecAccessConstraintRef SecAccessConstraintCreatePolicy(CFAllocatorRef allocator, CFTypeRef policy, CFErrorRef *error) {
return CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclConstraintPolicy), policy, NULL);
}
SecAccessConstraintRef SecAccessConstraintCreatePasscode(CFAllocatorRef allocator) {
return CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclConstraintUserPasscode), kCFBooleanTrue, NULL);
}
SecAccessConstraintRef SecAccessConstraintCreateBiometryAny(CFAllocatorRef allocator, CFDataRef catacombUUID) {
CFMutableDictionaryRef bioDict = CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclParamBioCatacombUUID), catacombUUID, NULL);
SecAccessConstraintRef constraint = CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclConstraintBio), bioDict, NULL);
CFReleaseSafe(bioDict);
return constraint;
}
SecAccessConstraintRef SecAccessConstraintCreateTouchIDAny(CFAllocatorRef allocator, CFDataRef catacombUUID) {
return SecAccessConstraintCreateBiometryAny(allocator, catacombUUID);
}
SecAccessConstraintRef SecAccessConstraintCreateBiometryCurrentSet(CFAllocatorRef allocator, CFDataRef catacombUUID, CFDataRef bioDbHash) {
CFMutableDictionaryRef bioDict = CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclParamBioCatacombUUID), catacombUUID, NULL);
CFDictionarySetValue(bioDict, CFSTR(kACMKeyAclParamBioDatabaseHash), bioDbHash);
SecAccessConstraintRef constraint = CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclConstraintBio), bioDict, NULL);
CFReleaseSafe(bioDict);
return constraint;
}
SecAccessConstraintRef SecAccessConstraintCreateTouchIDCurrentSet(CFAllocatorRef allocator, CFDataRef catacombUUID, CFDataRef bioDbHash) {
return SecAccessConstraintCreateBiometryCurrentSet(allocator, catacombUUID, bioDbHash);
}
static SecAccessConstraintRef SecAccessConstraintCreateValueOfKofN(CFAllocatorRef allocator, size_t numRequired, CFArrayRef constraints, CFErrorRef *error) {
CFNumberRef k = CFNumberCreateWithCFIndex(allocator, numRequired);
CFMutableDictionaryRef kofn = CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclParamKofN), 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[] = { CFSTR(kACMKeyAclConstraintBio), CFSTR(kACMKeyAclConstraintPolicy),
CFSTR(kACMKeyAclConstraintUserPasscode) };
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);
}
return kofn;
errOut:
SecError(errSecParam, error, CFSTR("SecAccessControl: invalid constraint for k-of-n"));
CFReleaseSafe(kofn);
return NULL;
}
SecAccessConstraintRef SecAccessConstraintCreateKofN(CFAllocatorRef allocator, size_t numRequired, CFArrayRef constraints, CFErrorRef *error) {
SecAccessConstraintRef valueOfKofN = SecAccessConstraintCreateValueOfKofN(allocator, numRequired, constraints, error);
require_quiet(valueOfKofN, errOut);
SecAccessConstraintRef constraint = CFDictionaryCreateMutableForCFTypesWith(allocator, CFSTR(kACMKeyAclConstraintKofN), valueOfKofN, NULL);
CFReleaseSafe(valueOfKofN);
return constraint;
errOut:
return NULL;
}
bool SecAccessControlAddConstraintForOperation(SecAccessControlRef access_control, CFTypeRef operation, CFTypeRef constraint, CFErrorRef *error) {
if (!isDictionary(constraint) && !CFEqual(constraint, kCFBooleanTrue) && !CFEqual(constraint, kCFBooleanFalse) ) {
return SecError(errSecParam, error, CFSTR("invalid constraint"));
}
CFMutableDictionaryRef constraints = SecAccessControlGetMutableConstraints(access_control);
CFDictionarySetValue(constraints, operation, constraint);
return true;
}
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;
}
CFDataRef SecAccessControlCopyConstraintData(SecAccessControlRef access_control, CFTypeRef operation) {
SecAccessConstraintRef constraint = SecAccessControlGetConstraint(access_control, operation);
size_t len = der_sizeof_plist(constraint, 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(constraint, NULL, der, der_end);
if (!der_end) {
CFReleaseNull(encoded);
}
return encoded;
}
CFDictionaryRef SecAccessControlGetConstraints(SecAccessControlRef access_control) {
return CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
}
void SecAccessControlSetConstraints(SecAccessControlRef access_control, CFDictionaryRef constraints) {
CFMutableDictionaryRef mutableConstraints = CFDictionaryCreateMutableCopy(CFGetAllocator(access_control), 0, constraints);
CFDictionarySetValue(access_control->dict, kAKSKeyAcl, mutableConstraints);
CFReleaseSafe(mutableConstraints);
}
void SecAccessControlSetRequirePassword(SecAccessControlRef access_control, bool require) {
CFMutableDictionaryRef constraints = SecAccessControlGetMutableConstraints(access_control);
CFDictionarySetValue(constraints, kAKSKeyAclParamRequirePasscode, require?kCFBooleanTrue:kCFBooleanFalse);
}
bool SecAccessControlGetRequirePassword(SecAccessControlRef access_control) {
CFMutableDictionaryRef acl = (CFMutableDictionaryRef)CFDictionaryGetValue(access_control->dict, kAKSKeyAcl);
if (acl) {
return CFEqualSafe(CFDictionaryGetValue(acl, kAKSKeyAclParamRequirePasscode), kCFBooleanTrue);
}
return false;
}
void SecAccessControlSetBound(SecAccessControlRef access_control, bool bound) {
CFDictionarySetValue(access_control->dict, kSecAccessControlKeyBound, bound ? kCFBooleanTrue : kCFBooleanFalse);
}
bool SecAccessControlIsBound(SecAccessControlRef access_control) {
CFTypeRef bound = CFDictionaryGetValue(access_control->dict, kSecAccessControlKeyBound);
return bound != NULL && CFEqualSafe(bound, kCFBooleanTrue);
}
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;
}