]> git.saurik.com Git - apple/security.git/blobdiff - keychain/SecureObjectSync/SOSInternal.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / SecureObjectSync / SOSInternal.m
diff --git a/keychain/SecureObjectSync/SOSInternal.m b/keychain/SecureObjectSync/SOSInternal.m
new file mode 100644 (file)
index 0000000..0506af6
--- /dev/null
@@ -0,0 +1,442 @@
+/*
+ * 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 <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/ccrng_pbkdf2_prng.h>
+
+#include <CommonCrypto/CommonRandomSPI.h>
+
+#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");
+
+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;
+    struct ccrng_pbkdf2_prng_state pbkdf2_prng;
+    const int kBackupKeyMaxBytes = 1024; // This may be a function of the cp but will be updated when we use a formally deterministic key generation.
+
+    cc_result = ccrng_pbkdf2_prng_init(&pbkdf2_prng, kBackupKeyMaxBytes,
+                                       CFDataGetLength(entropy), CFDataGetBytePtr(entropy),
+                                       sizeof(sBackupKeySalt), sBackupKeySalt,
+                                       kBackupKeyIterations);
+    require_action_quiet(cc_result == 0, exit, SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("pbkdf rng init failed: %d"), cc_result));
+
+    cc_result = ccec_compact_generate_key(cp, (struct ccrng_state *) &pbkdf2_prng, generatedKey);
+    require_action_quiet(cc_result == 0, exit, SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("Generate key failed: %d"), cc_result));
+
+    result = true;
+exit:
+    bzero(&pbkdf2_prng, sizeof(pbkdf2_prng));
+    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];
+}
+