SecKeybagSupport.c [plain text]
#include <securityd/SecKeybagSupport.h>
#include <securityd/SecItemServer.h>
#if USE_KEYSTORE
#include <IOKit/IOKitLib.h>
#include <libaks.h>
#include <libaks_acl_cf_keys.h>
#include <utilities/der_plist.h>
#include <corecrypto/ccder.h>
#include <ACMLib.h>
#if TARGET_OS_EMBEDDED
#include <MobileKeyBag/MobileKeyBag.h>
#endif
#else
#include <utilities/SecInternalReleasePriv.h>
#endif
#include <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonCryptorSPI.h>
#if USE_KEYSTORE
#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
keybag_handle_t g_keychain_keybag = session_keybag_handle;
#else
keybag_handle_t g_keychain_keybag = device_keybag_handle;
#endif
#else
keybag_handle_t g_keychain_keybag = 0;
#endif
const CFStringRef kSecKSKeyData1 = CFSTR("d1");
const CFStringRef kSecKSKeyData2 = CFSTR("d2");
void SecItemServerSetKeychainKeybag(int32_t keybag)
{
g_keychain_keybag=keybag;
}
void SecItemServerResetKeychainKeybag(void)
{
#if USE_KEYSTORE
#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
g_keychain_keybag = session_keybag_handle;
#else
g_keychain_keybag = device_keybag_handle;
#endif
#else
g_keychain_keybag = 0;
#endif
}
#if USE_KEYSTORE
static bool hwaes_key_available(void)
{
keybag_handle_t handle = bad_keybag_handle;
keybag_handle_t special_handle = bad_keybag_handle;
#if TARGET_OS_OSX
special_handle = session_keybag_handle;
#elif TARGET_OS_EMBEDDED
special_handle = device_keybag_handle;
#else
#error "supported keybag target"
#endif
kern_return_t kr = aks_get_system(special_handle, &handle);
if (kr != kIOReturnSuccess) {
#if TARGET_OS_EMBEDDED
int kb_state = MKBGetDeviceLockState(NULL);
secinfo("aks", "AppleKeyStore lock state: %d", kb_state);
#endif
}
return true;
}
#else
static bool hwaes_key_available(void)
{
return false;
}
#endif
bool ks_crypt(CFTypeRef operation, keybag_handle_t keybag,
keyclass_t keyclass, uint32_t textLength, const uint8_t *source, keyclass_t *actual_class, CFMutableDataRef dest, CFErrorRef *error) {
#if USE_KEYSTORE
kern_return_t kernResult = kIOReturnBadArgument;
int dest_len = (int)CFDataGetLength(dest);
if (CFEqual(operation, kAKSKeyOpEncrypt)) {
kernResult = aks_wrap_key(source, textLength, keyclass, keybag, CFDataGetMutableBytePtr(dest), &dest_len, actual_class);
} else if (CFEqual(operation, kAKSKeyOpDecrypt) || CFEqual(operation, kAKSKeyOpDelete)) {
kernResult = aks_unwrap_key(source, textLength, keyclass, keybag, CFDataGetMutableBytePtr(dest), &dest_len);
}
if (kernResult != KERN_SUCCESS) {
if ((kernResult == kIOReturnNotPermitted) || (kernResult == kIOReturnNotPrivileged)) {
const char *substatus = "";
if (keyclass == key_class_ck || keyclass == key_class_cku)
substatus = " (hibernation?)";
return SecError(errSecInteractionNotAllowed, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked%s."),
kernResult, operation, keyclass, keybag, substatus);
} else if (kernResult == kIOReturnNotFound) {
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") No key available for class."), kernResult, operation, keyclass, keybag);
} else if (kernResult == kIOReturnError || kernResult == kAKSReturnDecodeError) {
return SecError(errSecDecode, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") Item can't be decrypted on this device, ever, so drop the item."),
kernResult, operation, keyclass, keybag);
} else {
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32")"),
kernResult, operation, keyclass, keybag);
}
}
else
CFDataSetLength(dest, dest_len);
return true;
#else
uint32_t dest_len = (uint32_t)CFDataGetLength(dest);
if (CFEqual(operation, kAKSKeyOpEncrypt)) {
if (dest_len >= textLength + 8) {
memcpy(CFDataGetMutableBytePtr(dest), source, textLength);
memset(CFDataGetMutableBytePtr(dest) + textLength, 8, 8);
CFDataSetLength(dest, textLength + 8);
*actual_class = keyclass;
} else
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to wrap item (class %"PRId32")"), keyclass);
} else if (CFEqual(operation, kAKSKeyOpDecrypt) || CFEqual(operation, kAKSKeyOpDelete)) {
if (dest_len + 8 >= textLength) {
memcpy(CFDataGetMutableBytePtr(dest), source, textLength - 8);
CFDataSetLength(dest, textLength - 8);
} else
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to unwrap item (class %"PRId32")"), keyclass);
}
return true;
#endif
}
#if USE_KEYSTORE
bool ks_access_control_needed_error(CFErrorRef *error, CFDataRef access_control_data, CFTypeRef operation) {
if (error == NULL)
return false;
if (*error && CFErrorGetCode(*error) != errSecAuthNeeded) {
return false;
}
CFMutableDictionaryRef user_info;
if (*error) {
CFDictionaryRef old_user_info = CFErrorCopyUserInfo(*error);
user_info = CFDictionaryCreateMutableCopy(NULL, 0, old_user_info);
CFRelease(old_user_info);
CFReleaseNull(*error);
} else {
user_info = CFDictionaryCreateMutableForCFTypes(NULL);
}
if (access_control_data) {
CFNumberRef key = CFNumberCreateWithCFIndex(NULL, errSecAuthNeeded);
CFMutableArrayRef acls;
CFArrayRef old_acls = CFDictionaryGetValue(user_info, key);
if (old_acls)
acls = CFArrayCreateMutableCopy(NULL, 0, old_acls);
else
acls = CFArrayCreateMutableForCFTypes(NULL);
CFArrayRef pair = CFArrayCreateForCFTypes(NULL, access_control_data, operation, NULL);
CFArrayAppendValue(acls, pair);
CFRelease(pair);
CFDictionarySetValue(user_info, key, acls);
CFRelease(key);
CFRelease(acls);
*error = CFErrorCreate(NULL, kSecErrorDomain, errSecAuthNeeded, user_info);
}
else
*error = CFErrorCreate(NULL, kSecErrorDomain, errSecAuthNeeded, NULL);
CFReleaseSafe(user_info);
return false;
}
static bool merge_der_in_to_data(const void *ed_blob, size_t ed_blob_len, const void *key_blob, size_t key_blob_len, CFMutableDataRef mergedData)
{
bool ok = false;
CFDataRef ed_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, ed_blob, ed_blob_len, kCFAllocatorNull);
CFDataRef key_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, key_blob, key_blob_len, kCFAllocatorNull);
if (ed_data && key_data) {
CFDictionaryRef result_dict = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSecKSKeyData1, ed_data, kSecKSKeyData2, key_data, NULL);
CFDataSetLength(mergedData, 0);
CFDataRef der_data = CFPropertyListCreateDERData(kCFAllocatorDefault, result_dict, NULL);
if (der_data) {
CFDataAppend(mergedData, der_data);
CFRelease(der_data);
ok = CFDataGetLength(mergedData) > 0;
}
CFRelease(result_dict);
}
CFReleaseSafe(ed_data);
CFReleaseSafe(key_data);
return ok;
}
bool ks_separate_data_and_key(CFDictionaryRef blob_dict, CFDataRef *ed_data, CFDataRef *key_data)
{
bool ok = false;
CFDataRef tmp_ed_data = CFDictionaryGetValue(blob_dict, kSecKSKeyData1);
CFDataRef tmp_key_data = CFDictionaryGetValue(blob_dict, kSecKSKeyData2);
if (tmp_ed_data && tmp_key_data &&
CFDataGetTypeID() == CFGetTypeID(tmp_ed_data) &&
CFDataGetTypeID() == CFGetTypeID(tmp_key_data)) {
*ed_data = CFRetain(tmp_ed_data);
*key_data = CFRetain(tmp_key_data);
ok = true;
}
return ok;
}
bool create_cferror_from_aks(int aks_return, CFTypeRef operation, keybag_handle_t keybag, keyclass_t keyclass, CFDataRef access_control_data, CFDataRef acm_context_data, CFErrorRef *error)
{
const char *operation_string = "";
if (CFEqual(operation, kAKSKeyOpDecrypt)) {
operation_string = "decrypt";
} else if (CFEqual(operation, kAKSKeyOpEncrypt)) {
operation_string = "encrypt";
} if (CFEqual(operation, kAKSKeyOpDelete)) {
operation_string = "delete";
}
if (aks_return == kAKSReturnNoPermission) {
SecError(errSecInteractionNotAllowed, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked."),
aks_return, operation_string, keyclass, keybag);
} else if (aks_return == kAKSReturnPolicyError || aks_return == kAKSReturnBadPassword) {
if (aks_return == kAKSReturnBadPassword) {
ACMContextRef acm_context_ref = NULL;
acm_context_ref = ACMContextCreateWithExternalForm(CFDataGetBytePtr(acm_context_data), CFDataGetLength(acm_context_data));
if (acm_context_ref) {
ACMContextRemovePassphraseCredentialsByPurposeAndScope(acm_context_ref, kACMPassphrasePurposeGeneral, kACMScopeContext);
ACMContextDelete(acm_context_ref, false);
}
}
ks_access_control_needed_error(error, access_control_data, operation);
} else if (aks_return == kAKSReturnError || aks_return == kAKSReturnPolicyInvalid || aks_return == kAKSReturnDecodeError) {
SecError(errSecDecode, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Item can't be decrypted on this device, ever, so drop the item."),
aks_return, operation_string, keyclass, keybag);
} else if (aks_return == kIOReturnNotFound) {
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") No key available for class."), aks_return, operation, keyclass, keybag);
} else {
SecError(errSecNotAvailable, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32")"),
aks_return, operation_string, keyclass, keybag);
}
return false;
}
bool ks_encrypt_acl(keybag_handle_t keybag, keyclass_t keyclass, uint32_t textLength, const uint8_t *source,
CFMutableDataRef dest, CFDataRef auth_data, CFDataRef acm_context,
SecAccessControlRef access_control, CFErrorRef *error) {
void *params = NULL, *der = NULL;
size_t params_len = 0, der_len = 0;
CFDataRef access_control_data = SecAccessControlCopyData(access_control);
int aks_return = kAKSReturnSuccess;
aks_ref_key_t key_handle = NULL;
bool ok = false;
if (!acm_context || !SecAccessControlIsBound(access_control)) {
require_quiet(ok = ks_access_control_needed_error(error, access_control_data, SecAccessControlIsBound(access_control) ? kAKSKeyOpEncrypt : CFSTR("")), out);
}
aks_operation_optional_params(0, 0, CFDataGetBytePtr(auth_data), CFDataGetLength(auth_data), CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), ¶ms, ¶ms_len);
require_noerr_action_quiet(aks_return = aks_ref_key_create(keybag, keyclass, key_type_sym, params, params_len, &key_handle), out,
create_cferror_from_aks(aks_return, kAKSKeyOpEncrypt, keybag, keyclass, access_control_data, acm_context, error));
require_noerr_action_quiet(aks_return = aks_ref_key_encrypt(key_handle, params, params_len, source, textLength, &der, &der_len), out,
create_cferror_from_aks(aks_return, kAKSKeyOpEncrypt, keybag, keyclass, access_control_data, acm_context, error));
size_t key_blob_len;
const void *key_blob = aks_ref_key_get_blob(key_handle, &key_blob_len);
require_action_quiet(key_blob, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Item can't be encrypted due to invalid key data, so drop the item."),
aks_return, "encrypt", keyclass, keybag));
require_action_quiet(merge_der_in_to_data(der, der_len, key_blob, key_blob_len, dest), out,
SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Item can't be encrypted due to merge failed, so drop the item."),
aks_return, "encrypt", keyclass, keybag));
ok = true;
out:
if (key_handle)
aks_ref_key_free(&key_handle);
if(params)
free(params);
if(der)
free(der);
CFReleaseSafe(access_control_data);
return ok;
}
bool ks_decrypt_acl(aks_ref_key_t ref_key, CFDataRef encrypted_data, CFMutableDataRef dest,
CFDataRef acm_context, CFDataRef caller_access_groups,
SecAccessControlRef access_control, CFErrorRef *error) {
void *params = NULL, *der = NULL;
const uint8_t *access_groups = caller_access_groups?CFDataGetBytePtr(caller_access_groups):NULL;
size_t params_len = 0, der_len = 0, access_groups_len = caller_access_groups?CFDataGetLength(caller_access_groups):0;
CFDataRef access_control_data = SecAccessControlCopyData(access_control);
int aks_return = kAKSReturnSuccess;
bool ok = false;
if (!acm_context) {
require_quiet(ok = ks_access_control_needed_error(error, NULL, NULL), out);
}
aks_operation_optional_params(access_groups, access_groups_len, 0, 0, CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), ¶ms, ¶ms_len);
require_noerr_action_quiet(aks_return = aks_ref_key_decrypt(ref_key, params, params_len, CFDataGetBytePtr(encrypted_data), CFDataGetLength(encrypted_data), &der, &der_len), out,
create_cferror_from_aks(aks_return, kAKSKeyOpDecrypt, 0, 0, access_control_data, acm_context, error));
require_action_quiet(der, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item, Item can't be decrypted due to invalid der data, so drop the item."),
aks_return, "decrypt"));
CFPropertyListRef decoded_data = NULL;
der_decode_plist(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_data, NULL, der, der + der_len);
require_action_quiet(decoded_data, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item, Item can't be decrypted due to failed decode der, so drop the item."),
aks_return, "decrypt"));
if (CFGetTypeID(decoded_data) == CFDataGetTypeID()) {
CFDataSetLength(dest, 0);
CFDataAppend(dest, decoded_data);
CFRelease(decoded_data);
}
else {
CFRelease(decoded_data);
require_action_quiet(false, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item, Item can't be decrypted due to wrong data, so drop the item."),
aks_return, "decrypt"));
}
ok = true;
out:
if(params)
free(params);
if(der)
free(der);
CFReleaseSafe(access_control_data);
return ok;
}
bool ks_delete_acl(aks_ref_key_t ref_key, CFDataRef encrypted_data,
CFDataRef acm_context, CFDataRef caller_access_groups,
SecAccessControlRef access_control, CFErrorRef *error) {
void *params = NULL;
CFDataRef access_control_data = NULL;
int aks_return = kAKSReturnSuccess;
bool ok = false;
nrequire_action_quiet(CFEqual(SecAccessControlGetConstraint(access_control, kAKSKeyOpDelete), kCFBooleanTrue), out, ok = true);
if (!acm_context) {
require_quiet(ok = ks_access_control_needed_error(error, NULL, NULL), out);
}
access_control_data = SecAccessControlCopyData(access_control);
const uint8_t *access_groups = caller_access_groups?CFDataGetBytePtr(caller_access_groups):NULL;
size_t params_len = 0, access_groups_len = caller_access_groups?CFDataGetLength(caller_access_groups):0;
aks_operation_optional_params(access_groups, access_groups_len, 0, 0, CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), ¶ms, ¶ms_len);
require_noerr_action_quiet(aks_return = aks_ref_key_delete(ref_key, params, params_len), out,
create_cferror_from_aks(aks_return, kAKSKeyOpDelete, 0, 0, access_control_data, acm_context, error));
ok = true;
out:
if(params)
free(params);
CFReleaseSafe(access_control_data);
return ok;
}
const void* ks_ref_key_get_external_data(keybag_handle_t keybag, CFDataRef key_data, aks_ref_key_t *ref_key, size_t *external_data_len, CFErrorRef *error) {
const void* result = NULL;
int aks_return = kAKSReturnSuccess;
require_noerr_action_quiet(aks_ref_key_create_with_blob(keybag, CFDataGetBytePtr(key_data), CFDataGetLength(key_data), ref_key), out,
SecError(errSecNotAvailable, error, CFSTR("aks_ref_key: %x failed to '%s' item (bag: %"PRId32")"), aks_return, "create ref key with blob", keybag));
result = aks_ref_key_get_external_data(*ref_key, external_data_len);
out:
return result;
}
#endif
bool use_hwaes(void) {
static bool use_hwaes;
static dispatch_once_t check_once;
dispatch_once(&check_once, ^{
use_hwaes = hwaes_key_available();
if (use_hwaes) {
secinfo("aks", "using hwaes key");
} else {
secerror("unable to access hwaes key");
}
});
return use_hwaes;
}
bool ks_open_keybag(CFDataRef keybag, CFDataRef password, keybag_handle_t *handle, CFErrorRef *error) {
#if USE_KEYSTORE
kern_return_t kernResult;
if (!asData(keybag, error)) return false;
kernResult = aks_load_bag(CFDataGetBytePtr(keybag), (int)CFDataGetLength(keybag), handle);
if (kernResult)
return SecKernError(kernResult, error, CFSTR("aks_load_bag failed: %@"), keybag);
if (password) {
kernResult = aks_unlock_bag(*handle, CFDataGetBytePtr(password), (int)CFDataGetLength(password));
if (kernResult) {
aks_unload_bag(*handle);
return SecKernError(kernResult, error, CFSTR("aks_unlock_bag failed"));
}
}
return true;
#else
*handle = KEYBAG_NONE;
return true;
#endif
}
bool ks_close_keybag(keybag_handle_t keybag, CFErrorRef *error) {
#if USE_KEYSTORE
IOReturn kernResult = aks_unload_bag(keybag);
if (kernResult) {
return SecKernError(kernResult, error, CFSTR("aks_unload_bag failed"));
}
#endif
return true;
}