SOSInternal.m   [plain text]

/*
* 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;
}