mirror of
https://github.com/darlinghq/darling-security.git
synced 2025-02-20 02:14:06 +00:00
491 lines
18 KiB
Objective-C
491 lines
18 KiB
Objective-C
/*
|
|
* Copyright (c) 2012-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 "keychain/SecureObjectSync/SOSInternal.h"
|
|
#include "keychain/SecureObjectSync/SOSCircle.h"
|
|
#include <Security/SecureObjectSync/SOSCloudCircle.h>
|
|
#include "keychain/SecureObjectSync/SOSKVSKeys.h"
|
|
#include <Security/SecureObjectSync/SOSViews.h>
|
|
#include "utilities/SecCFError.h"
|
|
#include "utilities/SecCFRelease.h"
|
|
#include "utilities/SecCFWrappers.h"
|
|
#include "utilities/iOSforOSX.h"
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
|
|
#include <Security/SecKey.h>
|
|
#include <Security/SecKeyPriv.h>
|
|
#include <Security/SecItem.h>
|
|
#include "keychain/securityd/SecDbItem.h" // For SecError
|
|
#include "utilities/iOSforOSX.h"
|
|
|
|
#include <Security/SecBase64.h>
|
|
#include <utilities/der_plist.h>
|
|
#include <utilities/der_plist_internal.h>
|
|
#include <corecrypto/ccder.h>
|
|
#include <utilities/der_date.h>
|
|
|
|
#include <corecrypto/ccrng.h>
|
|
#include <corecrypto/ccdigest.h>
|
|
#include <corecrypto/ccsha2.h>
|
|
#include <corecrypto/ccpbkdf2.h>
|
|
|
|
#if !TARGET_OS_OSX
|
|
#import <SoftLinking/SoftLinking.h>
|
|
#import <ManagedConfiguration/ManagedConfiguration.h>
|
|
#import <ManagedConfiguration/MCProfileConnection_Misc.h>
|
|
#endif
|
|
|
|
#include <os/lock.h>
|
|
|
|
#include <AssertMacros.h>
|
|
|
|
const CFStringRef kSOSErrorDomain = CFSTR("com.apple.security.sos.error");
|
|
const CFStringRef kSOSDSIDKey = CFSTR("AccountDSID");
|
|
const CFStringRef SOSTransportMessageTypeIDSV2 = CFSTR("IDS2.0");
|
|
const CFStringRef SOSTransportMessageTypeKVS = CFSTR("KVS");
|
|
const CFStringRef kSOSCountKey = CFSTR("numberOfErrorsDeep");
|
|
|
|
bool SOSErrorCreate(CFIndex errorCode, CFErrorRef *error, CFDictionaryRef formatOptions, CFStringRef format, ...)
|
|
CF_FORMAT_FUNCTION(4, 5);
|
|
|
|
|
|
bool SOSErrorCreate(CFIndex errorCode, CFErrorRef *error, CFDictionaryRef formatOptions, CFStringRef format, ...) {
|
|
if (!errorCode) return true;
|
|
if (error && !*error) {
|
|
va_list va;
|
|
va_start(va, format);
|
|
SecCFCreateErrorWithFormatAndArguments(errorCode, kSOSErrorDomain, NULL, error, formatOptions, format, va);
|
|
va_end(va);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SOSCreateError(CFIndex errorCode, CFStringRef descriptionString, CFErrorRef previousError, CFErrorRef *newError) {
|
|
SOSCreateErrorWithFormat(errorCode, previousError, newError, NULL, CFSTR("%@"), descriptionString);
|
|
return false;
|
|
}
|
|
|
|
bool SOSCreateErrorWithFormat(CFIndex errorCode, CFErrorRef previousError, CFErrorRef *newError,
|
|
CFDictionaryRef formatOptions, CFStringRef format, ...) {
|
|
va_list va;
|
|
va_start(va, format);
|
|
bool res = SOSCreateErrorWithFormatAndArguments(errorCode, previousError, newError, formatOptions, format, va);
|
|
va_end(va);
|
|
return res;
|
|
}
|
|
|
|
bool SOSCreateErrorWithFormatAndArguments(CFIndex errorCode, CFErrorRef previousError, CFErrorRef *newError,
|
|
CFDictionaryRef formatOptions, CFStringRef format, va_list args) {
|
|
return SecCFCreateErrorWithFormatAndArguments(errorCode, kSOSErrorDomain, previousError, newError, formatOptions, format, args);
|
|
}
|
|
|
|
|
|
//
|
|
// Utility Functions
|
|
//
|
|
|
|
static OSStatus GenerateECPairImp(int keySize, CFBooleanRef permanent, SecKeyRef* public, SecKeyRef *full)
|
|
{
|
|
static const CFStringRef sTempNameToUse = CFSTR("GenerateECPair Temporary Key - Shouldn't be live");
|
|
|
|
CFNumberRef signing_bitsize = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &keySize);
|
|
|
|
CFDictionaryRef keygen_parameters = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
|
|
kSecAttrKeyType, kSecAttrKeyTypeEC,
|
|
kSecAttrKeySizeInBits, signing_bitsize,
|
|
kSecAttrIsPermanent, permanent,
|
|
kSecAttrLabel, sTempNameToUse,
|
|
NULL);
|
|
CFReleaseNull(signing_bitsize);
|
|
OSStatus result = SecKeyGeneratePair(keygen_parameters, public, full);
|
|
CFReleaseNull(keygen_parameters);
|
|
|
|
return result;
|
|
}
|
|
|
|
OSStatus GenerateECPair(int keySize, SecKeyRef* public, SecKeyRef *full)
|
|
{
|
|
return GenerateECPairImp(keySize, kCFBooleanFalse, public, full);
|
|
}
|
|
|
|
OSStatus GeneratePermanentECPair(int keySize, SecKeyRef* public, SecKeyRef *full)
|
|
{
|
|
return GenerateECPairImp(keySize, kCFBooleanTrue, public, full);
|
|
}
|
|
|
|
static CFStringRef SOSCircleCopyDescriptionFromData(CFDataRef data)
|
|
{
|
|
CFErrorRef error = NULL;
|
|
CFStringRef result = NULL;
|
|
|
|
SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, data, &error);
|
|
|
|
if (circle)
|
|
result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), circle);
|
|
|
|
CFReleaseSafe(circle);
|
|
|
|
return result;
|
|
}
|
|
|
|
CFStringRef SOSItemsChangedCopyDescription(CFDictionaryRef changes, bool is_sender)
|
|
{
|
|
CFMutableStringRef string = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("<Changes: {\n"));
|
|
|
|
CFDictionaryForEach(changes, ^(const void *key, const void *value) {
|
|
CFStringRef value_description = NULL;
|
|
if (isString(key) && isData(value)) {
|
|
CFDataRef value_data = (CFDataRef) value;
|
|
switch (SOSKVSKeyGetKeyType(key)) {
|
|
case kCircleKey:
|
|
value_description = SOSCircleCopyDescriptionFromData(value_data);
|
|
break;
|
|
case kMessageKey:
|
|
value_description = CFCopyDescription(value_data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
CFStringAppendFormat(string, NULL, CFSTR(" '%@' %s %@\n"),
|
|
key,
|
|
is_sender ? "<=" : "=>",
|
|
value_description ? value_description : value);
|
|
|
|
CFReleaseNull(value_description);
|
|
});
|
|
|
|
CFStringAppendFormat(string, NULL, CFSTR("}"));
|
|
|
|
return string;
|
|
}
|
|
|
|
CFStringRef SOSCopyHashBufAsString(uint8_t *digest, size_t len) {
|
|
char encoded[2 * len + 1]; // Big enough for base64 encoding.
|
|
|
|
size_t length = SecBase64Encode(digest, len, encoded, sizeof(encoded));
|
|
assert(length && length < sizeof(encoded));
|
|
if (length > kSOSPeerIDLengthMax)
|
|
length = kSOSPeerIDLengthMax;
|
|
encoded[length] = 0;
|
|
return CFStringCreateWithCString(kCFAllocatorDefault, encoded, kCFStringEncodingASCII);
|
|
}
|
|
|
|
CFStringRef SOSCopyIDOfDataBuffer(CFDataRef data, CFErrorRef *error) {
|
|
const struct ccdigest_info * di = ccsha1_di();
|
|
uint8_t digest[di->output_size];
|
|
ccdigest(di, CFDataGetLength(data), CFDataGetBytePtr(data), digest);
|
|
return SOSCopyHashBufAsString(digest, sizeof(digest));
|
|
}
|
|
|
|
CFStringRef SOSCopyIDOfDataBufferWithLength(CFDataRef data, CFIndex len, CFErrorRef *error) {
|
|
CFStringRef retval = NULL;
|
|
CFStringRef tmp = SOSCopyIDOfDataBuffer(data, error);
|
|
if(tmp) retval = CFStringCreateWithSubstring(kCFAllocatorDefault, tmp, CFRangeMake(0, len));
|
|
CFReleaseNull(tmp);
|
|
return retval;
|
|
}
|
|
|
|
CFStringRef SOSCopyIDOfKey(SecKeyRef key, CFErrorRef *error) {
|
|
CFDataRef publicBytes = NULL;
|
|
CFStringRef result = NULL;
|
|
require_action_quiet(key, errOut, SOSErrorCreate(kSOSErrorNoKey, error, NULL, CFSTR("NULL key passed to SOSCopyIDOfKey")));
|
|
require_quiet(SecError(SecKeyCopyPublicBytes(key, &publicBytes), error, CFSTR("Failed to export public bytes %@"), key), errOut);
|
|
result = SOSCopyIDOfDataBuffer(publicBytes, error);
|
|
errOut:
|
|
CFReleaseNull(publicBytes);
|
|
return result;
|
|
}
|
|
|
|
CFStringRef SOSCopyIDOfKeyWithLength(SecKeyRef key, CFIndex len, CFErrorRef *error) {
|
|
CFStringRef retval = NULL;
|
|
CFStringRef tmp = SOSCopyIDOfKey(key, error);
|
|
if(tmp) retval = CFStringCreateWithSubstring(kCFAllocatorDefault, tmp, CFRangeMake(0, len));
|
|
CFReleaseNull(tmp);
|
|
return retval;
|
|
}
|
|
|
|
|
|
CFGiblisGetSingleton(ccec_const_cp_t, SOSGetBackupKeyCurveParameters, sBackupKeyCurveParameters, ^{
|
|
*sBackupKeyCurveParameters = ccec_cp_256();
|
|
});
|
|
|
|
|
|
//
|
|
// We're expecting full entropy here, so we just need to stretch
|
|
// via the PBKDF entropy rng. We'll choose a few iterations and no salt
|
|
// since we don't get sent any.
|
|
//
|
|
const int kBackupKeyIterations = 20;
|
|
const uint8_t sBackupKeySalt[] = { 0 };
|
|
|
|
bool SOSPerformWithDeviceBackupFullKey(ccec_const_cp_t cp, CFDataRef entropy, CFErrorRef *error, void (^operation)(ccec_full_ctx_t fullKey))
|
|
{
|
|
bool result = false;
|
|
ccec_full_ctx_decl_cp(cp, fullKey);
|
|
|
|
require_quiet(SOSGenerateDeviceBackupFullKey(fullKey, cp, entropy, error), exit);
|
|
|
|
operation(fullKey);
|
|
|
|
result = true;
|
|
exit:
|
|
ccec_full_ctx_clear_cp(cp, fullKey);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool SOSGenerateDeviceBackupFullKey(ccec_full_ctx_t generatedKey, ccec_const_cp_t cp, CFDataRef entropy, CFErrorRef* error)
|
|
{
|
|
bool result = false;
|
|
int cc_result = 0;
|
|
|
|
#define drbg_output_size 1024
|
|
|
|
uint8_t drbg_output[drbg_output_size];
|
|
cc_result = ccpbkdf2_hmac(ccsha256_di(), CFDataGetLength(entropy), CFDataGetBytePtr(entropy),
|
|
sizeof(sBackupKeySalt), sBackupKeySalt,
|
|
kBackupKeyIterations,
|
|
drbg_output_size, drbg_output);
|
|
require_action_quiet(cc_result == 0, exit,
|
|
SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("ccpbkdf2_hmac failed: %d"), cc_result));
|
|
|
|
cc_result = ccec_generate_key_deterministic(cp,
|
|
drbg_output_size,
|
|
drbg_output,
|
|
ccrng(NULL),
|
|
CCEC_GENKEY_DETERMINISTIC_SECBKP,
|
|
generatedKey);
|
|
require_action_quiet(cc_result == 0, exit,
|
|
SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("ccec_generate_key_deterministic failed: %d"), cc_result));
|
|
|
|
result = true;
|
|
exit:
|
|
return result;
|
|
}
|
|
|
|
CFDataRef SOSCopyDeviceBackupPublicKey(CFDataRef entropy, CFErrorRef *error)
|
|
{
|
|
CFDataRef result = NULL;
|
|
CFMutableDataRef publicKeyData = NULL;
|
|
|
|
ccec_full_ctx_decl_cp(SOSGetBackupKeyCurveParameters(), fullKey);
|
|
|
|
require_quiet(SOSGenerateDeviceBackupFullKey(fullKey, SOSGetBackupKeyCurveParameters(), entropy, error), exit);
|
|
|
|
size_t space = ccec_compact_export_size(false, ccec_ctx_pub(fullKey));
|
|
publicKeyData = CFDataCreateMutableWithScratch(kCFAllocatorDefault, space);
|
|
require_quiet(SecAllocationError(publicKeyData, error, CFSTR("Mutable data allocation")), exit);
|
|
|
|
ccec_compact_export(false, CFDataGetMutableBytePtr(publicKeyData), fullKey);
|
|
|
|
CFTransferRetained(result, publicKeyData);
|
|
|
|
exit:
|
|
CFReleaseNull(publicKeyData);
|
|
return result;
|
|
}
|
|
|
|
|
|
CFDataRef SOSDateCreate(void) {
|
|
CFDateRef now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
|
|
size_t bufsiz = der_sizeof_date(now, NULL);
|
|
uint8_t buf[bufsiz];
|
|
der_encode_date(now, NULL, buf, buf+bufsiz);
|
|
CFReleaseNull(now);
|
|
return CFDataCreate(NULL, buf, bufsiz);
|
|
}
|
|
|
|
|
|
CFDataRef CFDataCreateWithDER(CFAllocatorRef allocator, CFIndex size, uint8_t*(^operation)(size_t size, uint8_t *buffer)) {
|
|
__block CFMutableDataRef result = NULL;
|
|
if(!size) return NULL;
|
|
if((result = CFDataCreateMutableWithScratch(allocator, size)) == NULL) return NULL;
|
|
uint8_t *ptr = CFDataGetMutableBytePtr(result);
|
|
uint8_t *derptr = operation(size, ptr);
|
|
if(derptr == ptr) return result; // most probable case
|
|
if(!derptr || derptr < ptr) { // DER op failed - or derptr ended up prior to allocated buffer
|
|
CFReleaseNull(result);
|
|
} else if(derptr > ptr) { // This is a possible case where we don't end up using the entire allocated buffer
|
|
size_t diff = derptr - ptr; // The unused space ends up being the beginning of the allocation
|
|
CFDataDeleteBytes(result, CFRangeMake(0, diff));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@implementation SOSCachedNotification
|
|
+ (NSString *)notificationName:(const char *)notificationString {
|
|
#if TARGET_OS_OSX
|
|
return [NSString stringWithFormat:@"user.uid.%d.%s", getuid(), notificationString];
|
|
#else
|
|
return @(notificationString);
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
bool SOSCachedNotificationOperation(const char *notificationString, bool (^operation) (int token, bool gtg)) {
|
|
static os_unfair_lock token_lock = OS_UNFAIR_LOCK_INIT;
|
|
static NSMutableDictionary *tokenCache = NULL;
|
|
int token = NOTIFY_TOKEN_INVALID;
|
|
|
|
@autoreleasepool {
|
|
os_unfair_lock_lock(&token_lock);
|
|
if (tokenCache == NULL) {
|
|
tokenCache = [NSMutableDictionary dictionary];
|
|
}
|
|
NSString *notification = [SOSCachedNotification notificationName:notificationString];
|
|
if (notification == NULL) {
|
|
os_unfair_lock_unlock(&token_lock);
|
|
return false;
|
|
}
|
|
|
|
NSNumber *cachedToken = tokenCache[notification];
|
|
if (cachedToken == NULL) {
|
|
uint32_t status;
|
|
|
|
status = notify_register_check([notification UTF8String], &token);
|
|
if (status == NOTIFY_STATUS_OK) {
|
|
tokenCache[notification] = @(token);
|
|
} else {
|
|
secnotice("cachedStatus", "Failed to retreive token for %@: error %d",
|
|
notification, status);
|
|
}
|
|
} else {
|
|
token = [cachedToken intValue];
|
|
}
|
|
os_unfair_lock_unlock(&token_lock);
|
|
}
|
|
|
|
return operation(token, (token != NOTIFY_TOKEN_INVALID));
|
|
}
|
|
|
|
uint64_t SOSGetCachedCircleBitmask(void) {
|
|
__block uint64_t retval = 0; // If the following call fails and we return 0 the caller, checking CC_STATISVALID will see we didn't get anything.
|
|
SOSCachedNotificationOperation(kSOSCCCircleChangedNotification, ^bool(int token, bool gtg) {
|
|
if(gtg) {
|
|
notify_get_state(token, &retval);
|
|
}
|
|
return false;
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
const SOSCCStatus kSOSNoCachedValue = -99;
|
|
|
|
SOSCCStatus SOSGetCachedCircleStatus(CFErrorRef *error) {
|
|
uint64_t statusMask = SOSGetCachedCircleBitmask();
|
|
SOSCCStatus retval = kSOSNoCachedValue;
|
|
|
|
if(statusMask & CC_STATISVALID) {
|
|
if(statusMask & CC_UKEY_TRUSTED) {
|
|
retval = (SOSCCStatus) statusMask & CC_MASK;
|
|
} else {
|
|
retval = kSOSCCError;
|
|
if(error) {
|
|
CFReleaseNull(*error);
|
|
if(statusMask & CC_PEER_IS_IN) {
|
|
SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available, this peer is in the circle, but invalid. The iCloud Password must be provided to keychain syncing subsystem to repair this."), NULL, error);
|
|
} else {
|
|
SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available. The iCloud Password must be provided to keychain syncing subsystem to repair this."), NULL, error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
uint64_t SOSCachedViewBitmask(void) {
|
|
__block uint64_t retval = 0;
|
|
if(SOSGetCachedCircleStatus(NULL) == kSOSCCInCircle) {
|
|
SOSCachedNotificationOperation(kSOSCCViewMembershipChangedNotification, ^bool(int token, bool gtg) {
|
|
if(gtg) {
|
|
notify_get_state(token, &retval);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
CFSetRef SOSCreateCachedViewStatus(void) {
|
|
__block CFSetRef retval = NULL;
|
|
uint64_t state = SOSCachedViewBitmask();
|
|
if(state) {
|
|
retval = SOSViewCreateSetFromBitmask(state);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
|
|
NSDate *SOSCreateRandomDateBetweenNowPlus(NSTimeInterval starting, NSTimeInterval ending) {
|
|
uint64_t randomTime;
|
|
uint64_t span = ending - starting;
|
|
NSTimeInterval resultInterval = (span/2) + starting; // fallback in case call below fails.
|
|
if(SecRandomCopyBytes(NULL, sizeof(randomTime), &randomTime) == 0) {
|
|
resultInterval = (randomTime % span) + starting;
|
|
}
|
|
return [[NSDate alloc] initWithTimeIntervalSinceNow:resultInterval];
|
|
}
|
|
|
|
|
|
dispatch_queue_t SOSCCCredentialQueue(void) {
|
|
static dispatch_queue_t credQueue = NULL;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
credQueue = dispatch_queue_create("com.apple.SOSCredentialsQueue", DISPATCH_QUEUE_SERIAL);
|
|
});
|
|
return credQueue;
|
|
}
|
|
|
|
|
|
#if TARGET_OS_OSX
|
|
#define KEYCHAINSYNCDISABLE "DisableKeychainCloudSync"
|
|
#define ICLOUDMANAGEDENVIRONMENT "com.apple.icloud.managed"
|
|
#else
|
|
SOFT_LINK_FRAMEWORK(PrivateFrameworks, ManagedConfiguration)
|
|
SOFT_LINK_CLASS(ManagedConfiguration, MCProfileConnection)
|
|
#endif
|
|
|
|
bool SOSVisibleKeychainNotAllowed(void) {
|
|
bool notAllowed = false;
|
|
|
|
#if TARGET_OS_OSX
|
|
notAllowed = CFPreferencesGetAppBooleanValue(CFSTR(KEYCHAINSYNCDISABLE), CFSTR(ICLOUDMANAGEDENVIRONMENT), NULL);
|
|
#else
|
|
Class mpc = getMCProfileConnectionClass();
|
|
MCProfileConnection *sharedConnection = [mpc sharedConnection];
|
|
notAllowed = ![sharedConnection isCloudKeychainSyncAllowed];
|
|
#endif
|
|
|
|
if(notAllowed) {
|
|
secnotice("views", "V0 views disabled by Managed Preferences Profile");
|
|
}
|
|
return notAllowed;
|
|
}
|