]> git.saurik.com Git - apple/security.git/blobdiff - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountBackup.c
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSAccountBackup.c
diff --git a/OSX/sec/SOSCircle/SecureObjectSync/SOSAccountBackup.c b/OSX/sec/SOSCircle/SecureObjectSync/SOSAccountBackup.c
new file mode 100644 (file)
index 0000000..be827c1
--- /dev/null
@@ -0,0 +1,559 @@
+//
+//  SOSAccountCircles.c
+//  sec
+//
+
+#include "SOSAccountPriv.h"
+#include "SOSCloudKeychainClient.h"
+
+#include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
+#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
+#include <Security/SecureObjectSync/SOSViews.h>
+
+#include "SOSInternal.h"
+
+//
+// MARK: V0 Keybag keychain stuff
+//
+static bool SecItemUpdateOrAdd(CFDictionaryRef query, CFDictionaryRef update, CFErrorRef *error)
+{
+    OSStatus saveStatus = SecItemUpdate(query, update);
+    
+    if (errSecItemNotFound == saveStatus) {
+        CFMutableDictionaryRef add = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, query);
+        CFDictionaryForEach(update, ^(const void *key, const void *value) {
+            CFDictionaryAddValue(add, key, value);
+        });
+        saveStatus = SecItemAdd(add, NULL);
+        CFReleaseNull(add);
+    }
+    
+    return SecError(saveStatus, error, CFSTR("Error saving %@"), query);
+}
+
+static CFDictionaryRef SOSCopyV0Attributes() {
+    return  CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                         kSecClass,           kSecClassGenericPassword,
+                                         kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
+                                         kSecAttrAccessible,  kSecAttrAccessibleWhenUnlocked,
+                                         kSecAttrAccount,     CFSTR("SecureBackupPublicKeybag"),
+                                         kSecAttrService,     CFSTR("SecureBackupService"),
+                                         kSecAttrSynchronizable, kCFBooleanTrue,
+                                         NULL);
+}
+
+static bool SOSDeleteV0Keybag(CFErrorRef *error) {
+    CFDictionaryRef attributes = SOSCopyV0Attributes();
+    
+    OSStatus result = SecItemDelete(attributes);
+    
+    CFReleaseNull(attributes);
+    
+    return SecError(result != errSecItemNotFound ? result : errSecSuccess, error, CFSTR("Deleting V0 Keybag failed - %ld"), result);
+}
+
+static bool SOSSaveV0Keybag(CFDataRef v0Keybag, CFErrorRef *error) {
+    CFDictionaryRef attributes = SOSCopyV0Attributes();
+    
+    CFDictionaryRef update = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                                          kSecValueData,           v0Keybag,
+                                                          NULL);
+    
+    
+    bool result = SecItemUpdateOrAdd(attributes, update, error);
+    CFReleaseNull(attributes);
+    CFReleaseNull(update);
+    
+    return result;
+}
+
+
+static bool SOSPeerInfoIsViewBackupEnabled(SOSPeerInfoRef peerInfo, CFStringRef viewName) {
+    if (CFEqualSafe(kSOSViewKeychainV0, viewName))
+        return false;
+
+    return SOSPeerInfoHasBackupKey(peerInfo) && SOSPeerInfoIsViewPermitted(peerInfo, viewName);
+}
+
+static CFSetRef SOSAccountCopyBackupPeersForView(SOSAccountRef account, CFStringRef viewName) {
+    CFMutableSetRef backupPeers = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
+
+    SOSCircleRef circle = SOSAccountGetCircle(account, NULL);
+
+    require_quiet(circle, exit);
+
+    SOSCircleForEachActiveValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
+        if (SOSPeerInfoIsViewBackupEnabled(peer, viewName))
+            CFSetAddValue(backupPeers, peer);
+    });
+
+exit:
+    return backupPeers;
+}
+
+static void SOSAccountWithBackupPeersForView(SOSAccountRef account, CFStringRef viewName, void (^action)(CFSetRef peers)) {
+    CFSetRef backupPeersForView = SOSAccountCopyBackupPeersForView(account, viewName);
+
+    action(backupPeersForView);
+
+    CFReleaseNull(backupPeersForView);
+}
+
+static bool SOSAccountWithBSKBForView(SOSAccountRef account, CFStringRef viewName, CFErrorRef *error,
+                                      bool (^action)(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error)) {
+    __block SOSBackupSliceKeyBagRef bskb = NULL;
+    bool result = false;
+
+    SOSAccountWithBackupPeersForView(account, viewName, ^(CFSetRef peers) {
+        bskb = SOSBackupSliceKeyBagCreate(kCFAllocatorDefault, peers, error);
+    });
+
+    require_quiet(bskb, exit);
+
+    action(bskb, error);
+
+    result = true;
+
+exit:
+    CFReleaseNull(bskb);
+    return result;
+}
+
+CFStringRef SOSBackupCopyRingNameForView(CFStringRef viewName) {
+    return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@-tomb"), viewName);
+}
+
+static bool SOSAccountUpdateNamedRing(SOSAccountRef account, CFStringRef ringName, CFErrorRef *error,
+                                      SOSRingRef (^create)(CFStringRef ringName, CFErrorRef *error),
+                                      SOSRingRef (^copyModified)(SOSRingRef existing, CFErrorRef *error)) {
+    bool result = false;
+    SOSRingRef newRing = NULL;
+    SOSRingRef found = (SOSRingRef) CFDictionaryGetValue(account->trusted_rings, ringName);
+    if (isSOSRing(found)) {
+        found = SOSRingCopyRing(found, error);
+    } else {
+        if (found) {
+            secerror("Non ring in ring table: %@, purging!", found);
+            CFDictionaryRemoveValue(account->trusted_rings, ringName);
+        }
+        found = create(ringName, error);
+    }
+    
+    require_quiet(found, exit);
+    newRing = copyModified(found, error);
+    CFReleaseNull(found);
+
+    require_quiet(newRing, exit);
+
+    result = SOSAccountHandleUpdateRing(account, newRing, true, error);
+
+exit:
+    CFReleaseNull(found);
+    CFReleaseNull(newRing);
+    return result;
+}
+
+static bool SOSAccountUpdateBackupRing(SOSAccountRef account, CFStringRef viewName, CFErrorRef *error,
+                                       SOSRingRef (^modify)(SOSRingRef existing, CFErrorRef *error)) {
+
+    CFStringRef ringName = SOSBackupCopyRingNameForView(viewName);
+
+    bool result = SOSAccountUpdateNamedRing(account, ringName, error, ^SOSRingRef(CFStringRef ringName, CFErrorRef *error) {
+        return SOSRingCreate(ringName, SOSAccountGetMyPeerID(account), kSOSRingBackup, error);
+    }, modify);
+
+    CFReleaseNull(ringName);
+
+    return result;
+}
+
+static CFSetRef SOSAccountCopyPeerSetForView(SOSAccountRef account, CFStringRef viewName) {
+    CFMutableSetRef result = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
+    
+    if (account->trusted_circle) {
+        SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
+            if (CFSetContainsValue(SOSPeerInfoGetPermittedViews(peer), viewName)) {
+                CFSetAddValue(result, peer);
+            }
+        });
+    }
+    
+    return result;
+}
+
+static bool SOSAccountSetKeybagForViewBackupRing(SOSAccountRef account, CFStringRef viewName, SOSBackupSliceKeyBagRef keyBag, CFErrorRef *error) {
+    CFMutableSetRef backupViewSet = CFSetCreateMutableForCFTypes(NULL);
+    bool result = false;
+    require_quiet(SecAllocationError(backupViewSet, error, CFSTR("No backup view set created")), errOut);
+    CFSetAddValue(backupViewSet, viewName);
+
+    result = SOSAccountUpdateBackupRing(account, viewName, error, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
+        SOSRingRef newRing = NULL;
+        CFSetRef viewPeerSet = SOSAccountCopyPeerSetForView(account, viewName);
+        CFMutableSetRef cleared = CFSetCreateMutableForCFTypes(NULL);
+
+        SOSRingSetPeerIDs(existing, cleared);
+        SOSRingAddAll(existing, viewPeerSet);
+
+        require_quiet(SOSRingSetBackupKeyBag(existing, SOSAccountGetMyFullPeerInfo(account), backupViewSet, keyBag, error), exit);
+
+        newRing = CFRetainSafe(existing);
+    exit:
+        CFReleaseNull(viewPeerSet);
+        CFReleaseNull(cleared);
+        return newRing;
+    });
+    
+errOut:
+    
+    if (result && *error) {
+        secerror("Got Success and Error (dropping error): %@", *error);
+        CFReleaseNull(*error);
+    }
+    
+    if (!result) {
+        secnotice("backupring", "Got error setting keybag for backup view '%@': %@", viewName, error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space."));
+    }
+
+    CFReleaseNull(backupViewSet);
+    return result;
+}
+
+bool SOSAccountStartNewBackup(SOSAccountRef account, CFStringRef viewName, CFErrorRef *error)
+{
+    return SOSAccountWithBSKBForView(account, viewName, error, ^(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error) {
+        bool result = SOSAccountSetKeybagForViewBackupRing(account, viewName, bskb, error);
+        return result;
+    });
+}
+
+static bool SOSAccountUpdatePeerInfo(SOSAccountRef account, CFStringRef updateDescription, CFErrorRef *error, bool (^update)(SOSFullPeerInfoRef fpi, CFErrorRef *error)) {
+    if (account->my_identity == NULL)
+        return true;
+
+    bool result = update(account->my_identity, error);
+
+    if (result && SOSAccountHasCircle(account, NULL)) {
+        return SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle_to_change) {
+            secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for %@", updateDescription);
+            return SOSCircleUpdatePeerInfo(circle_to_change, SOSAccountGetMyPeerInfo(account));
+        });
+    }
+
+    return result;
+}
+
+bool SOSAccountIsMyPeerInBackupAndCurrentInView(SOSAccountRef account, CFStringRef viewname){
+    bool result = false;
+    CFErrorRef bsError = NULL;
+    CFDataRef backupSliceData = NULL;
+    SOSBackupSliceKeyBagRef backupSlice = NULL;
+    
+    require_quiet(SOSPeerInfoIsViewBackupEnabled(SOSAccountGetMyPeerInfo(account), viewname), exit);
+    
+    CFMutableDictionaryRef trusted_rings = SOSAccountGetRings(account, &bsError);
+    require_quiet(trusted_rings, exit);
+
+    CFStringRef ringName = SOSBackupCopyRingNameForView(viewname);
+    SOSRingRef ring = (SOSRingRef)CFDictionaryGetValue(trusted_rings, ringName);
+    CFReleaseNull(ringName);
+    
+    require_quiet(ring, exit);
+    
+    //grab the backup slice from the ring
+    backupSliceData = SOSRingGetPayload(ring, &bsError);
+    require_quiet(backupSliceData, exit);
+
+    backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError);
+    require_quiet(backupSlice, exit);
+    
+    CFSetRef peers = SOSBSKBGetPeers(backupSlice);
+    SOSPeerInfoRef myPeer = SOSAccountGetMyPeerInfo(account);
+    
+    SOSPeerInfoRef myPeerInBSKB = (SOSPeerInfoRef) CFSetGetValue(peers, myPeer);
+    require_quiet(isSOSPeerInfo(myPeerInBSKB), exit);
+    
+    CFDataRef myBK = SOSPeerInfoCopyBackupKey(myPeer);
+    CFDataRef myPeerInBSKBBK = SOSPeerInfoCopyBackupKey(myPeerInBSKB);
+    result = CFEqualSafe(myBK, myPeerInBSKBBK);
+    CFReleaseNull(myBK);
+    CFReleaseNull(myPeerInBSKB);
+    
+exit:
+    if (bsError) {
+        secnotice("backup", "Failed to find BKSB: %@, %@ (%@)", backupSliceData, backupSlice, bsError);
+    }
+    CFReleaseNull(bsError);
+    return result;
+}
+
+bool SOSAccountIsPeerInBackupAndCurrentInView(SOSAccountRef account, SOSPeerInfoRef testPeer, CFStringRef viewname){
+    bool result = false;
+    CFErrorRef bsError = NULL;
+    CFDataRef backupSliceData = NULL;
+    SOSBackupSliceKeyBagRef backupSlice = NULL;
+
+    require_quiet(testPeer, exit);
+
+    CFMutableDictionaryRef trusted_rings = SOSAccountGetRings(account, &bsError);
+    require_quiet(trusted_rings, exit);
+
+    CFStringRef ringName = SOSBackupCopyRingNameForView(viewname);
+    SOSRingRef ring = (SOSRingRef)CFDictionaryGetValue(trusted_rings, ringName);
+    CFReleaseNull(ringName);
+    
+    require_quiet(ring, exit);
+    
+    //grab the backup slice from the ring
+    backupSliceData = SOSRingGetPayload(ring, &bsError);
+    require_quiet(backupSliceData, exit);
+
+    backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError);
+    require_quiet(backupSlice, exit);
+    
+    CFSetRef peers = SOSBSKBGetPeers(backupSlice);
+    
+    SOSPeerInfoRef peerInBSKB = (SOSPeerInfoRef) CFSetGetValue(peers, testPeer);
+    require_quiet(isSOSPeerInfo(peerInBSKB), exit);
+
+    result = CFEqualSafe(testPeer, peerInBSKB);
+    
+exit:
+    if (bsError) {
+        secnotice("backup", "Failed to find BKSB: %@, %@ (%@)", backupSliceData, backupSlice, bsError);
+    }
+    CFReleaseNull(bsError);
+    return result;
+
+}
+
+bool SOSAccountUpdateOurPeerInBackup(SOSAccountRef account, SOSRingRef oldRing, CFErrorRef *error){
+    bool result = false;
+    CFSetRef viewNames = SOSBackupRingGetViews(oldRing, error);
+    __block CFStringRef viewName = NULL;
+    require_quiet(viewNames, fail);
+    require_quiet(SecRequirementError(1 == CFSetGetCount(viewNames), error, CFSTR("Only support single view backup rings")), fail);
+
+    CFSetForEach(viewNames, ^(const void *value) {
+        if (isString(value)) {
+            viewName = CFRetainSafe((CFStringRef) value);
+        }
+    });
+
+    result = SOSAccountStartNewBackup(account, viewName, error);
+
+fail:
+    CFReleaseNull(viewName);
+    return result;
+}
+
+void SOSAccountForEachBackupRingName(SOSAccountRef account, void (^operation)(CFStringRef value)) {
+    SOSPeerInfoRef myPeer = SOSAccountGetMyPeerInfo(account);
+    if (myPeer) {
+        CFMutableSetRef myBackupViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, SOSPeerInfoGetPermittedViews(myPeer));
+
+        CFSetRemoveValue(myBackupViews, kSOSViewKeychainV0);
+
+        CFSetForEach(myBackupViews, ^(const void *value) {
+            CFStringRef viewName = asString(value, NULL);
+
+            if (viewName) {
+                CFStringRef ringName = SOSBackupCopyRingNameForView(viewName);
+                operation(ringName);
+                CFReleaseNull(ringName);
+            }
+        });
+
+        CFReleaseNull(myBackupViews);
+    }
+}
+
+static
+void SOSAccountForEachBackupView(SOSAccountRef account,  void (^operation)(const void *value)) {
+    SOSPeerInfoRef myPeer = SOSAccountGetMyPeerInfo(account);
+    
+    if (myPeer) {
+        CFMutableSetRef myBackupViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, SOSPeerInfoGetPermittedViews(myPeer));
+        
+        CFSetRemoveValue(myBackupViews, kSOSViewKeychainV0);
+        
+        CFSetForEach(myBackupViews, operation);
+        
+        CFReleaseNull(myBackupViews);
+    }
+}
+
+bool SOSAccountSetBackupPublicKey(SOSAccountRef account, CFDataRef backupKey, CFErrorRef *error)
+{
+    __block bool result = false;
+
+#if !ENABLE_V2_BACKUP
+    // We should fill in an error, but exit does it for us.
+    goto exit;
+#endif
+
+    require_quiet(SOSAccountIsInCircle(account, error), exit);
+
+    if (CFEqualSafe(backupKey, account->backup_key))
+        return true;
+    
+    require_quiet(SOSBSKBIsGoodBackupPublic(backupKey, error), exit);
+    require_quiet(SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), error,
+                                           ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
+        return SOSFullPeerInfoUpdateBackupKey(fpi, backupKey, error);
+    }), exit);
+
+    CFRetainAssign(account->backup_key, backupKey);
+
+    CFErrorRef localError = NULL;
+    if (!SOSDeleteV0Keybag(&localError)) {
+        secerror("Failed to delete v0 keybag: %@", localError);
+    }
+    CFReleaseNull(localError);
+    
+    result = true;
+    
+    SOSAccountForEachBackupView(account, ^(const void *value) {
+        CFStringRef viewName = (CFStringRef)value;
+        result &= SOSAccountStartNewBackup(account, viewName, error);
+    });
+
+exit:
+    if (!result) {
+        secnotice("backupkey", "Failed to setup backup public key: %@", error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space provided"));
+    }
+    return result;
+}
+
+static bool SOSAccountWithBSKBAndPeerInfosForView(SOSAccountRef account, CFArrayRef retiree, CFStringRef viewName, CFErrorRef *error,
+                                                  bool (^action)(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error)) {
+    __block SOSBackupSliceKeyBagRef bskb = NULL;
+    bool result = false;
+    
+    SOSAccountWithBackupPeersForView(account, viewName, ^(CFSetRef peers) {
+        CFMutableSetRef newPeerList = CFSetCreateMutableCopy(kCFAllocatorDefault, CFSetGetCount(peers), peers);
+        CFArrayForEach(retiree, ^(const void *value) {
+            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
+            CFSetRemoveValue(newPeerList, peer);
+        });
+        bskb = SOSBackupSliceKeyBagCreate(kCFAllocatorDefault, newPeerList, error);
+        CFReleaseNull(newPeerList);
+    });
+    
+    require_quiet(bskb, exit);
+    
+    action(bskb, error);
+    
+    result = true;
+    
+exit:
+    CFReleaseNull(bskb);
+    return result;
+}
+
+bool SOSAccountRemoveBackupPublickey(SOSAccountRef account, CFErrorRef *error)
+{
+    __block bool result = false;
+    __block CFMutableArrayRef removals = NULL;
+    
+    require_quiet(SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), error,
+                                           ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
+                                               return SOSFullPeerInfoUpdateBackupKey(fpi, NULL, error);
+                                           }), exit);
+    
+    CFReleaseNull(account->backup_key);
+    
+    removals = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+    CFArrayAppendValue(removals, SOSAccountGetMyPeerInfo(account));
+    
+    SOSAccountForEachBackupView(account, ^(const void *value) {
+        CFStringRef viewName = (CFStringRef)value;
+        result = SOSAccountWithBSKBAndPeerInfosForView(account, removals, viewName, error, ^(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error) {
+            bool result = SOSAccountSetKeybagForViewBackupRing(account, viewName, bskb, error);
+            return result;
+        });
+    });
+   
+
+    result = true;
+    
+exit:
+    return result;
+    
+}
+
+bool SOSAccountSetBSKBagForAllSlices(SOSAccountRef account, CFDataRef aks_bag, bool includeV0, CFErrorRef *error){
+    __block bool result = false;
+    SOSBackupSliceKeyBagRef backup_slice = NULL;
+    
+    require_quiet(SOSAccountIsInCircle(account, error), exit);
+
+    if (includeV0) {
+        result = SOSSaveV0Keybag(aks_bag, error);
+        require_action_quiet(result, exit, secnotice("keybag", "failed to set V0 keybag (%@)", *error));
+    }
+
+    result = true;
+
+
+#if !ENABLE_V2_BACKUP
+    // We always succeed when no V2 backup.
+    goto exit;
+#endif
+    
+    backup_slice = SOSBackupSliceKeyBagCreateDirect(kCFAllocatorDefault, aks_bag, error);
+    
+    SOSAccountForEachBackupView(account, ^(const void *value) {
+        CFStringRef viewname = (CFStringRef) value;
+        result &= SOSAccountSetKeybagForViewBackupRing(account, viewname, backup_slice, error);
+    });
+    
+exit:
+    CFReleaseNull(backup_slice);
+    return result;
+}
+
+static CFMutableArrayRef SOSAccountIsRetiredPeerIDInBackupPeerList(SOSAccountRef account, CFArrayRef peerIDs, CFSetRef peersInBackup){
+    CFMutableArrayRef removals = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+    
+    CFSetForEach(peersInBackup, ^(const void *value) {
+        SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
+        CFArrayForEach(peerIDs, ^(const void *value) {
+            CFStringRef peerID = (CFStringRef)value;
+            CFStringRef piPeerID = SOSPeerInfoGetPeerID(peer);
+            if (piPeerID && CFStringCompare(piPeerID, peerID, 0) == 0){
+                CFArrayAppendValue(removals, peer);
+            }
+        });
+        
+    });
+    
+    return removals;
+
+}
+
+bool SOSAccountRemoveBackupPeers(SOSAccountRef account, CFArrayRef peerIDs, CFErrorRef *error){
+    __block bool result = true;
+
+    SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInfo(account);
+    SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
+    
+    CFSetRef permittedViews = SOSPeerInfoGetPermittedViews(myPeer);
+    CFSetForEach(permittedViews, ^(const void *value) {
+        CFStringRef viewName = (CFStringRef)value;
+        if(SOSPeerInfoIsViewBackupEnabled(myPeer, viewName)){
+            //grab current peers list
+            CFSetRef peersInBackup = SOSAccountCopyBackupPeersForView(account, viewName);
+            //get peer infos that have retired but are still in the backup peer list
+            CFMutableArrayRef removals = SOSAccountIsRetiredPeerIDInBackupPeerList(account, peerIDs, peersInBackup);
+            result = SOSAccountWithBSKBAndPeerInfosForView(account, removals, viewName, error, ^(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error) {
+                bool result = SOSAccountSetKeybagForViewBackupRing(account, viewName, bskb, error);
+                return result;
+            });
+        }
+    });
+    
+    return result;
+    
+}
+