SOSAccountRecovery.m [plain text]
/*
* Copyright (c) 2016 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@
*/
//
// SOSAccountRecovery.c
// Security
//
#include <AssertMacros.h>
#include "SOSAccountPriv.h"
#include "SOSCloudKeychainClient.h"
#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
#include <Security/SecureObjectSync/SOSViews.h>
#include <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
#include <Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h>
#include "SOSInternal.h"
#include "SecADWrapper.h"
#include <Security/SecureObjectSync/SOSRecoveryKeyBag.h>
#include <Security/SecureObjectSync/SOSRingRecovery.h>
CFStringRef kRecoveryRingKey = CFSTR("recoveryKeyBag");
bool SOSAccountSetRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccount* account, SOSRecoveryKeyBagRef rkbg, CFErrorRef *error) {
CFDataRef rkbg_as_data = NULL;
bool result = false;
rkbg_as_data = SOSRecoveryKeyBagCopyEncoded(rkbg, error);
result = rkbg_as_data && SOSAccountSetValue(account, kRecoveryRingKey, rkbg_as_data, error);
CFReleaseNull(rkbg_as_data);
return result;
}
SOSRecoveryKeyBagRef SOSAccountCopyRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) {
SOSRecoveryKeyBagRef retval = NULL;
CFDataRef rkbg_as_data = asData(SOSAccountGetValue(account, kRecoveryRingKey, error), error);
require_quiet(rkbg_as_data, errOut);
retval = SOSRecoveryKeyBagCreateFromData(allocator, rkbg_as_data, error);
errOut:
return retval;
}
SOSRecoveryKeyBagRef SOSAccountCopyRecoveryKeyBag(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) {
SOSRingRef recRing = NULL;
SOSRecoveryKeyBagRef rkbg = NULL;
require_action_quiet(account, errOut, SOSCreateError(kSOSErrorParam, CFSTR("No Account Object"), NULL, error));
recRing = SOSAccountCopyRingNamed(account, kSOSRecoveryRing, error);
require_quiet(recRing, errOut);
rkbg = SOSRingCopyRecoveryKeyBag(recRing, error);
errOut:
CFReleaseNull(recRing);
return rkbg;
}
static CFDataRef SOSRKNullKey(void) {
static CFDataRef localNullKey = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
localNullKey = CFDataCreate(kCFAllocatorDefault, (const UInt8*) "nullkey", 8);
});
return localNullKey;
}
CFDataRef SOSAccountCopyRecoveryPublic(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) {
SOSRecoveryKeyBagRef rkbg = SOSAccountCopyRecoveryKeyBag(allocator, account, error);
CFDataRef recKey = NULL;
require_quiet(rkbg, errOut);
CFDataRef tmpKey = SOSRecoveryKeyBagGetKeyData(rkbg, error);
require_quiet(tmpKey, errOut);
require_quiet(!CFEqualSafe(tmpKey, SOSRKNullKey()), errOut);
recKey = CFDataCreateCopy(kCFAllocatorDefault, tmpKey);
errOut:
CFReleaseNull(rkbg);
if(!recKey) {
if(error && !(*error)) SOSErrorCreate(kSOSErrorNoKey, error, NULL, CFSTR("No recovery key available"));
}
return recKey;
}
static bool SOSAccountUpdateRecoveryRing(SOSAccount* account, CFErrorRef *error,
SOSRingRef (^modify)(SOSRingRef existing, CFErrorRef *error)) {
bool result = SOSAccountUpdateNamedRing(account, kSOSRecoveryRing, error, ^SOSRingRef(CFStringRef ringName, CFErrorRef *error) {
return SOSRingCreate(ringName, (__bridge CFStringRef)(account.peerID), kSOSRingRecovery, error);
}, modify);
return result;
}
static bool SOSAccountSetKeybagForRecoveryRing(SOSAccount* account, SOSRecoveryKeyBagRef keyBag, CFErrorRef *error) {
bool result = SOSAccountUpdateRecoveryRing(account, error, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
SOSRingRef newRing = NULL;
CFSetRef peerSet = [account.trust copyPeerSetMatching:^bool(SOSPeerInfoRef peer) {
return true;
}];
CFMutableSetRef cleared = CFSetCreateMutableForCFTypes(NULL);
SOSRingSetPeerIDs(existing, cleared);
SOSRingAddAll(existing, peerSet);
require_quiet(SOSRingSetRecoveryKeyBag(existing, account.fullPeerInfo, keyBag, error), exit);
newRing = CFRetainSafe(existing);
exit:
CFReleaseNull(cleared);
return newRing;
});
SOSClearErrorIfTrue(result, error);
if (!result) {
secnotice("recovery", "Got error setting keybag for recovery : }
return result;
}
bool SOSAccountRemoveRecoveryKey(SOSAccount* account, CFErrorRef *error) {
return SOSAccountSetRecoveryKey(account, SOSRKNullKey(), error);
}
bool SOSAccountSetRecoveryKey(SOSAccount* account, CFDataRef pubData, CFErrorRef *error) {
__block bool result = false;
CFDataRef oldRecoveryKey = NULL;
SOSRecoveryKeyBagRef rkbg = NULL;
require_quiet([account isInCircle:error], exit);
oldRecoveryKey = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL); // ok to fail here. don't collect error
require_action_quiet(!CFEqualSafe(pubData, oldRecoveryKey), exit, result = true);
CFDataPerformWithHexString(pubData, ^(CFStringRef recoveryKeyString) {
CFDataPerformWithHexString(oldRecoveryKey, ^(CFStringRef oldRecoveryKeyString) {
secnotice("recovery", "SetRecoveryPublic: });
});
rkbg = SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault, (__bridge CFTypeRef)account, pubData, error);
require_quiet(rkbg, exit);
result = SOSAccountSetKeybagForRecoveryRing(account, rkbg, error);
SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, rkbg, NULL);
{
SOSAccountForEachBackupView(account, ^(const void *value) {
CFStringRef viewName = (CFStringRef)value;
SOSAccountNewBKSBForView(account, viewName, error);
});
}
account.circle_rings_retirements_need_attention = true;
exit:
CFReleaseNull(oldRecoveryKey);
CFReleaseNull(rkbg);
SOSClearErrorIfTrue(result, error);
if (!result) {
// if we're failing and something above failed to give an error - make a generic one.
if(error && !(*error)) SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("Failed to set Recovery Key"));
secnotice("recovery", "SetRecoveryPublic Failed: }
return result;
}
bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView(SOSAccount* account, CFStringRef viewname) {
bool result = false;
CFErrorRef bsError = NULL;
CFDataRef backupSliceData = NULL;
SOSRingRef ring = NULL;
SOSBackupSliceKeyBagRef backupSlice = NULL;
CFDataRef rkbg = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL);
require_quiet(rkbg, 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);
result = SOSBKSBPrefixedKeyIsInKeyBag(backupSlice, bskbRkbgPrefix, rkbg);
CFReleaseNull(backupSlice);
errOut:
CFReleaseNull(ring);
CFReleaseNull(rkbg);
if (bsError) {
secnotice("backup", "Failed to find BKSB: }
CFReleaseNull(bsError);
return result;
}
static void sosRecoveryAlertAndNotify(SOSAccount* account, SOSRecoveryKeyBagRef oldRingRKBG, SOSRecoveryKeyBagRef ringRKBG) {
secnotice("recovery", "Recovery Key changed: old notify_post(kSOSCCRecoveryKeyChanged);
}
void SOSAccountEnsureRecoveryRing(SOSAccount* account) {
static SOSRecoveryKeyBagRef oldRingRKBG = NULL;
if(![account isInCircle:NULL]) {
return;
}
CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
SOSRecoveryKeyBagRef acctRKBG = SOSAccountCopyRecoveryKeyBagEntry(kCFAllocatorDefault, account, NULL);
SOSRecoveryKeyBagRef ringRKBG = SOSAccountCopyRecoveryKeyBag(kCFAllocatorDefault, account, NULL);
if(!SOSRecoveryKeyBagDSIDIs(ringRKBG, accountDSID)) CFReleaseNull(ringRKBG);
if(!SOSRecoveryKeyBagDSIDIs(acctRKBG, accountDSID)) CFReleaseNull(acctRKBG);
if(acctRKBG == NULL && ringRKBG == NULL) {
// Nothing to do at this time - notify if this is a change down below.
} else if(acctRKBG == NULL) { // then we have a ringRKBG
secnotice("recovery", "Harvesting account recovery key from ring");
SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, ringRKBG, NULL);
} else if(ringRKBG == NULL) {
// Nothing to do at this time - notify if this is a change down below.
secnotice("recovery", "Account has a recovery key, but none found in recovery ring");
} else if(!CFEqualSafe(acctRKBG, ringRKBG)) {
secnotice("recovery", "Harvesting account recovery key from ring");
SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, ringRKBG, NULL);
}
if(!CFEqualSafe(oldRingRKBG, ringRKBG)) {
sosRecoveryAlertAndNotify(account, oldRingRKBG, ringRKBG);
CFTransferRetained(oldRingRKBG, ringRKBG);
}
CFReleaseNull(ringRKBG);
CFReleaseNull(acctRKBG);
}