SOSAccountBackup.m [plain text]
//
// SOSAccountCircles.c
// sec
//
#include "SOSAccount.h"
#include "SOSCloudKeychainClient.h"
#include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecureObjectSync/SOSViews.h>
#include <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
#include <Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.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 }
static CFDictionaryRef SOSCopyV0Attributes() {
return CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked,
kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
kSecAttrService, CFSTR("SecureBackupService"),
kSecAttrSynchronizable, kCFBooleanTrue,
NULL);
}
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 - }
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(SOSAccount* account, CFStringRef viewName) {
CFMutableSetRef backupPeers = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
SOSCircleRef circle = [account.trust getCircle:NULL];
require_quiet(circle, exit);
SOSCircleForEachValidPeer(circle, account.accountKey, ^(SOSPeerInfoRef peer) {
if (SOSPeerInfoIsViewBackupEnabled(peer, viewName))
CFSetAddValue(backupPeers, peer);
});
exit:
return backupPeers;
}
static void SOSAccountWithBackupPeersForView(SOSAccount* account, CFStringRef viewName, void (^action)(CFSetRef peers)) {
CFSetRef backupPeersForView = SOSAccountCopyBackupPeersForView(account, viewName);
action(backupPeersForView);
CFReleaseNull(backupPeersForView);
}
static bool SOSAccountWithBSKBForView(SOSAccount* account, CFStringRef viewName, CFErrorRef *error,
bool (^action)(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error)) {
__block SOSBackupSliceKeyBagRef bskb = NULL;
bool result = false;
CFDataRef rkbg = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL);
SOSAccountWithBackupPeersForView(account, viewName, ^(CFSetRef peers) {
if(! rkbg) {
bskb = SOSBackupSliceKeyBagCreate(kCFAllocatorDefault, peers, error);
} else {
CFMutableDictionaryRef additionalKeys = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFDictionaryAddValue(additionalKeys, bskbRkbgPrefix, rkbg);
bskb = SOSBackupSliceKeyBagCreateWithAdditionalKeys(kCFAllocatorDefault, peers, additionalKeys, error);
CFReleaseNull(additionalKeys);
}
});
CFReleaseNull(rkbg);
require_quiet(bskb, exit);
action(bskb, error);
result = true;
exit:
CFReleaseNull(bskb);
return result;
}
CFStringRef SOSBackupCopyRingNameForView(CFStringRef viewName) {
return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("}
static bool SOSAccountUpdateBackupRing(SOSAccount* 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, (__bridge CFStringRef) account.peerID, kSOSRingBackup, error);
}, modify);
CFReleaseNull(ringName);
return result;
}
static bool SOSAccountSetKeybagForViewBackupRing(SOSAccount* account, CFStringRef viewName, SOSBackupSliceKeyBagRef keyBag, CFErrorRef *error) {
CFMutableSetRef backupViewSet = CFSetCreateMutableForCFTypes(NULL);
bool result = false;
if(!SecAllocationError(backupViewSet, error, CFSTR("No backup view set created"))){
secnotice("backupring", "Got error setting keybag for backup view '%@':
return result;
}
CFSetAddValue(backupViewSet, viewName);
result = SOSAccountUpdateBackupRing(account, viewName, error, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
SOSRingRef newRing = NULL;
CFSetRef viewPeerSet = [account.trust copyPeerSetForView:viewName];
CFMutableSetRef cleared = CFSetCreateMutableForCFTypes(NULL);
SOSRingSetPeerIDs(existing, cleared);
SOSRingAddAll(existing, viewPeerSet);
require_quiet(SOSRingSetBackupKeyBag(existing, account.fullPeerInfo, backupViewSet, keyBag, error), exit);
newRing = CFRetainSafe(existing);
exit:
CFReleaseNull(viewPeerSet);
CFReleaseNull(cleared);
return newRing;
});
if (result && NULL != error && NULL != *error) {
secerror("Got Success and Error (dropping error): CFReleaseNull(*error);
}
if (!result) {
secnotice("backupring", "Got error setting keybag for backup view '%@': }
CFReleaseNull(backupViewSet);
return result;
}
bool SOSAccountNewBKSBForView(SOSAccount* account, CFStringRef viewName, CFErrorRef *error)
{
return SOSAccountWithBSKBForView(account, viewName, error, ^(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error) {
bool result = SOSAccountSetKeybagForViewBackupRing(account, viewName, bskb, error);
return result;
});
}
bool SOSAccountIsBackupRingEmpty(SOSAccount* account, CFStringRef viewName) {
CFStringRef backupRing = SOSBackupCopyRingNameForView(viewName);
SOSRingRef ring = [account.trust copyRing:backupRing err:NULL];
CFReleaseNull(backupRing);
int peercnt = 0;
if(ring) peercnt = SOSRingCountPeers(ring);
CFReleaseNull(ring);
return peercnt == 0;
}
bool SOSAccountIsMyPeerInBackupAndCurrentInView(SOSAccount* account, CFStringRef viewname){
bool result = false;
CFErrorRef bsError = NULL;
CFDataRef backupSliceData = NULL;
SOSRingRef ring = NULL;
SOSBackupSliceKeyBagRef backupSlice = NULL;
require_quiet(SOSPeerInfoIsViewBackupEnabled(account.peerInfo, viewname), errOut);
CFStringRef ringName = SOSBackupCopyRingNameForView(viewname);
ring = [account.trust copyRing:ringName err:&bsError];
CFReleaseNull(ringName);
require_quiet(ring, errOut);
//grab the backup slice from the ring
backupSliceData = SOSRingGetPayload(ring, &bsError);
require_quiet(backupSliceData, errOut);
backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError);
require_quiet(backupSlice, errOut);
CFSetRef peers = SOSBSKBGetPeers(backupSlice);
SOSPeerInfoRef myPeer = account.peerInfo;
SOSPeerInfoRef myPeerInBSKB = (SOSPeerInfoRef) CFSetGetValue(peers, myPeer);
require_quiet(isSOSPeerInfo(myPeerInBSKB), errOut);
CFDataRef myBK = SOSPeerInfoCopyBackupKey(myPeer);
CFDataRef myPeerInBSKBBK = SOSPeerInfoCopyBackupKey(myPeerInBSKB);
result = CFEqualSafe(myBK, myPeerInBSKBBK);
CFReleaseNull(myBK);
CFReleaseNull(myPeerInBSKBBK);
errOut:
CFReleaseNull(ring);
CFReleaseNull(backupSlice);
if (bsError) {
secnotice("backup", "Failed to find BKSB: }
CFReleaseNull(bsError);
return result;
}
bool SOSAccountIsPeerInBackupAndCurrentInView(SOSAccount* account, SOSPeerInfoRef testPeer, CFStringRef viewname){
bool result = false;
CFErrorRef bsError = NULL;
CFDataRef backupSliceData = NULL;
SOSRingRef ring = NULL;
SOSBackupSliceKeyBagRef backupSlice = NULL;
require_quiet(testPeer, errOut);
CFStringRef ringName = SOSBackupCopyRingNameForView(viewname);
ring = [account.trust copyRing:ringName err:&bsError];
CFReleaseNull(ringName);
require_quiet(ring, errOut);
//grab the backup slice from the ring
backupSliceData = SOSRingGetPayload(ring, &bsError);
require_quiet(backupSliceData, errOut);
backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError);
require_quiet(backupSlice, errOut);
CFSetRef peers = SOSBSKBGetPeers(backupSlice);
SOSPeerInfoRef peerInBSKB = (SOSPeerInfoRef) CFSetGetValue(peers, testPeer);
require_quiet(isSOSPeerInfo(peerInBSKB), errOut);
result = CFEqualSafe(testPeer, peerInBSKB);
errOut:
CFReleaseNull(ring);
CFReleaseNull(backupSlice);
if (bsError) {
secnotice("backup", "Failed to find BKSB: }
CFReleaseNull(bsError);
return result;
}
bool SOSAccountUpdateOurPeerInBackup(SOSAccount* 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 = SOSAccountNewBKSBForView(account, viewName, error);
fail:
CFReleaseNull(viewName);
return result;
}
void SOSAccountForEachBackupRingName(SOSAccount* account, void (^operation)(CFStringRef value)) {
SOSPeerInfoRef myPeer = account.peerInfo;
if (myPeer) {
CFSetRef allViews = SOSViewCopyViewSet(kViewSetAll); // All non virtual views.
CFSetForEach(allViews, ^(const void *value) {
CFStringRef viewName = asString(value, NULL);
if (viewName) {
CFStringRef ringName = SOSBackupCopyRingNameForView(viewName);
operation(ringName);
CFReleaseNull(ringName);
}
});
CFReleaseNull(allViews);
// Only one "ring" now (other than backup rings) when there's more this will need to be modified.
operation(kSOSRecoveryRing);
}
}
void SOSAccountForEachRingName(SOSAccount* account, void (^operation)(CFStringRef value)) {
SOSPeerInfoRef myPeer = account.peerInfo;
if (myPeer) {
CFSetRef allViews = SOSViewCopyViewSet(kViewSetAll); // All non virtual views.
CFSetForEach(allViews, ^(const void *value) {
CFStringRef viewName = asString(value, NULL);
if (viewName) {
CFStringRef ringName = SOSBackupCopyRingNameForView(viewName);
operation(ringName);
CFReleaseNull(ringName);
}
});
CFReleaseNull(allViews);
// Only one "ring" now (other than backup rings) when there's more this will need to be modified.
operation(kSOSRecoveryRing);
}
}
void SOSAccountForEachBackupView(SOSAccount* account, void (^operation)(const void *value)) {
SOSPeerInfoRef myPeer = account.peerInfo;
if (myPeer) {
CFMutableSetRef myBackupViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, SOSPeerInfoGetPermittedViews(myPeer));
CFSetRemoveValue(myBackupViews, kSOSViewKeychainV0);
CFSetForEach(myBackupViews, operation);
CFReleaseNull(myBackupViews);
}
}
bool SOSAccountSetBackupPublicKey(SOSAccountTransaction* aTxn, CFDataRef cfBackupKey, CFErrorRef *error)
{
SOSAccount* account = aTxn.account;
NSData* backupKey = [[NSData alloc]initWithData:(__bridge NSData * _Nonnull)(cfBackupKey)];
__block bool result = false;
if(![account isInCircle:error]) {
return result;
}
CFDataPerformWithHexString((__bridge CFDataRef)(backupKey), ^(CFStringRef backupKeyString) {
CFDataPerformWithHexString((__bridge CFDataRef)((account.backup_key)), ^(CFStringRef oldBackupKey) {
secnotice("backup", "SetBackupPublic: });
});
if ([backupKey isEqual:account.backup_key])
return true;
account.backup_key = [[NSData alloc] initWithData:backupKey];
account.circle_rings_retirements_need_attention = true;
result = true;
if (!result) {
secnotice("backupkey", "SetBackupPublic Failed: }
return result;
}
static bool SOSAccountWithBSKBAndPeerInfosForView(SOSAccount* 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) {
if (!isSOSPeerInfo(value)) {
secerror("Peer list contains a non-peerInfo element");
} else {
SOSPeerInfoRef retiringPeer = (SOSPeerInfoRef)value;
CFStringRef retiringPeerID = SOSPeerInfoGetPeerID(retiringPeer);
CFSetForEach(newPeerList, ^(const void *peerFromAccount) {
CFStringRef peerFromAccountID = SOSPeerInfoGetPeerID((SOSPeerInfoRef)peerFromAccount);
if (peerFromAccountID && retiringPeerID && CFStringCompare(peerFromAccountID, retiringPeerID, 0) == 0){
CFSetRemoveValue(newPeerList, peerFromAccount);
}
});
}
});
bskb = SOSBackupSliceKeyBagCreate(kCFAllocatorDefault, newPeerList, error);
CFReleaseNull(newPeerList);
});
require_quiet(bskb, exit);
action(bskb, error);
result = true;
exit:
CFReleaseNull(bskb);
return result;
}
bool SOSAccountRemoveBackupPublickey(SOSAccountTransaction* aTxn, CFErrorRef *error)
{
SOSAccount* account = aTxn.account;
__block bool result = false;
__block CFArrayRef removals = NULL;
account.backup_key = nil;
if(!SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), error,
^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
return SOSFullPeerInfoUpdateBackupKey(fpi, NULL, error);
})){
return result;
}
removals = CFArrayCreateForCFTypes(kCFAllocatorDefault,
account.peerInfo, NULL);
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;
return result;
}
bool SOSAccountSetBSKBagForAllSlices(SOSAccount* account, CFDataRef aks_bag, bool setupV0Only, CFErrorRef *error){
__block bool result = false;
SOSBackupSliceKeyBagRef backup_slice = NULL;
if(![account isInCircle:error]) {
return result;
}
if (setupV0Only) {
result = SOSSaveV0Keybag(aks_bag, error);
require_action_quiet(result, exit, secnotice("keybag", "failed to set V0 keybag ( } else {
result = true;
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 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountIsRetiredPeerIDInBackupPeerList(SOSAccount* account, CFArrayRef peers, CFSetRef peersInBackup){
CFMutableArrayRef removals = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFSetForEach(peersInBackup, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
CFArrayForEach(peers, ^(const void *value) {
CFStringRef peerID = SOSPeerInfoGetPeerID((SOSPeerInfoRef)value);
CFStringRef piPeerID = SOSPeerInfoGetPeerID(peer);
if (peerID && piPeerID && CFStringCompare(piPeerID, peerID, 0) == 0){
CFArrayAppendValue(removals, peer);
}
});
});
return removals;
}
bool SOSAccountRemoveBackupPeers(SOSAccount* account, CFArrayRef peers, CFErrorRef *error){
__block bool result = true;
SOSFullPeerInfoRef fpi = account.fullPeerInfo;
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, peers, peersInBackup);
result = SOSAccountWithBSKBAndPeerInfosForView(account, removals, viewName, error, ^(SOSBackupSliceKeyBagRef bskb, CFErrorRef *error) {
bool result = SOSAccountSetKeybagForViewBackupRing(account, viewName, bskb, error);
return result;
});
CFReleaseNull(removals);
CFReleaseNull(peersInBackup);
}
});
return result;
}
SOSBackupSliceKeyBagRef SOSAccountBackupSliceKeyBagForView(SOSAccount* account, CFStringRef viewName, CFErrorRef* error){
CFDataRef backupSliceData = NULL;
CFStringRef ringName = NULL;
SOSRingRef ring = NULL;
SOSBackupSliceKeyBagRef bskb = NULL;
ringName = SOSBackupCopyRingNameForView(viewName);
ring = [account.trust copyRing:ringName err:NULL];
require_action_quiet(ring, exit, SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("failed to get ring")));
//grab the backup slice from the ring
backupSliceData = SOSRingGetPayload(ring, error);
require_action_quiet(backupSliceData, exit, secnotice("backup", "failed to get backup slice (
bskb = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, error);
exit:
CFReleaseNull(ring);
CFReleaseNull(ringName);
return bskb;
}
bool SOSAccountIsLastBackupPeer(SOSAccount* account, CFErrorRef *error) {
__block bool retval = false;
SOSPeerInfoRef pi = account.peerInfo;
if(![account isInCircle:error]) {
return retval;
}
if(!SOSPeerInfoHasBackupKey(pi))
return retval;
SOSCircleRef circle = [account.trust getCircle:error];
if(SOSCircleCountValidSyncingPeers(circle, SOSAccountGetTrustedPublicCredential(account, error)) == 1){
retval = true;
return retval;
}
// We're in a circle with more than 1 ActiveValidPeers - are they in the backups?
SOSAccountForEachBackupView(account, ^(const void *value) {
CFStringRef viewname = (CFStringRef) value;
SOSBackupSliceKeyBagRef keybag = SOSAccountBackupSliceKeyBagForView(account, viewname, error);
require_quiet(keybag, inner_errOut);
retval |= ((SOSBSKBCountPeers(keybag) == 1) && (SOSBSKBPeerIsInKeyBag(keybag, pi)));
inner_errOut:
CFReleaseNull(keybag);
});
return retval;
}