2  * Copyright (c) 2016 Apple Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   6  * This file contains Original Code and/or Modifications of Original Code 
   7  * as defined in and that are subject to the Apple Public Source License 
   8  * Version 2.0 (the 'License'). You may not use this file except in 
   9  * compliance with the License. Please obtain a copy of the License at 
  10  * http://www.opensource.apple.com/apsl/ and read it before using this 
  13  * The Original Code and all software distributed under the License are 
  14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 
  15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 
  16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 
  17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 
  18  * Please see the License for the specific language governing rights and 
  19  * limitations under the License. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  25 //  SOSAccountRecovery.c 
  29 #include <AssertMacros.h> 
  30 #include "SOSAccountPriv.h" 
  31 #include "SOSCloudKeychainClient.h" 
  33 // #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h> 
  34 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h> 
  35 #include <Security/SecureObjectSync/SOSViews.h> 
  37 #include "SOSInternal.h" 
  38 #include "SecADWrapper.h" 
  42 #include <Security/SecureObjectSync/SOSRecoveryKeyBag.h> 
  43 #include <Security/SecureObjectSync/SOSRingRecovery.h> 
  45 CFStringRef kRecoveryRingKey 
= CFSTR("recoveryKeyBag"); 
  47 bool SOSAccountSetRecoveryKeyBagEntry(CFAllocatorRef allocator
, SOSAccountRef account
, SOSRecoveryKeyBagRef rkbg
, CFErrorRef 
*error
) { 
  48     CFDataRef rkbg_as_data 
= NULL
; 
  50     rkbg_as_data 
= SOSRecoveryKeyBagCopyEncoded(rkbg
, error
); 
  51     result 
= rkbg_as_data 
&& SOSAccountSetValue(account
, kRecoveryRingKey
, rkbg_as_data
, error
); 
  52     CFReleaseNull(rkbg_as_data
); 
  56 SOSRecoveryKeyBagRef 
SOSAccountCopyRecoveryKeyBagEntry(CFAllocatorRef allocator
, SOSAccountRef account
, CFErrorRef 
*error
) { 
  57     SOSRecoveryKeyBagRef retval 
= NULL
; 
  58     CFDataRef rkbg_as_data 
= asData(SOSAccountGetValue(account
, kRecoveryRingKey
, error
), error
); 
  59     require_quiet(rkbg_as_data
, errOut
); 
  60     retval 
= SOSRecoveryKeyBagCreateFromData(allocator
, rkbg_as_data
, error
); 
  65 SOSRecoveryKeyBagRef 
SOSAccountCopyRecoveryKeyBag(CFAllocatorRef allocator
, SOSAccountRef account
, CFErrorRef 
*error
) { 
  66     SOSRingRef recRing 
= NULL
; 
  67     SOSRecoveryKeyBagRef rkbg 
= NULL
; 
  68     require_action_quiet(account
, errOut
, SOSCreateError(kSOSErrorParam
, CFSTR("No Account Object"), NULL
, error
)); 
  69     recRing 
= SOSAccountCopyRingNamed(account
, kSOSRecoveryRing
, error
); 
  70     require_quiet(recRing
, errOut
); 
  71     rkbg 
= SOSRingCopyRecoveryKeyBag(recRing
, error
); 
  73     CFReleaseNull(recRing
); 
  77 CFDataRef 
SOSAccountCopyRecoveryPublic(CFAllocatorRef allocator
, SOSAccountRef account
, CFErrorRef 
*error
) { 
  78     SOSRecoveryKeyBagRef rkbg 
= SOSAccountCopyRecoveryKeyBag(allocator
, account
, error
); 
  79     CFDataRef recKey 
= NULL
; 
  80     require_quiet(rkbg
, errOut
); 
  81     CFDataRef tmpKey 
= SOSRecoveryKeyBagGetKeyData(rkbg
, error
); 
  82     if(tmpKey
) recKey 
= CFDataCreateCopy(kCFAllocatorDefault
, tmpKey
); 
  88 static bool SOSAccountUpdateRecoveryRing(SOSAccountRef account
, CFErrorRef 
*error
, 
  89                                          SOSRingRef (^modify
)(SOSRingRef existing
, CFErrorRef 
*error
)) { 
  90     bool result 
= SOSAccountUpdateNamedRing(account
, kSOSRecoveryRing
, error
, ^SOSRingRef(CFStringRef ringName
, CFErrorRef 
*error
) { 
  91         return SOSRingCreate(ringName
, SOSAccountGetMyPeerID(account
), kSOSRingRecovery
, error
); 
  97 static bool SOSAccountSetKeybagForRecoveryRing(SOSAccountRef account
, SOSRecoveryKeyBagRef keyBag
, CFErrorRef 
*error
) { 
  98     bool result 
= SOSAccountUpdateRecoveryRing(account
, error
, ^SOSRingRef(SOSRingRef existing
, CFErrorRef 
*error
) { 
  99         SOSRingRef newRing 
= NULL
; 
 100         CFSetRef peerSet 
= SOSAccountCopyPeerSetMatching(account
, ^bool(SOSPeerInfoRef peer
) { 
 103         CFMutableSetRef cleared 
= CFSetCreateMutableForCFTypes(NULL
); 
 105         SOSRingSetPeerIDs(existing
, cleared
); 
 106         SOSRingAddAll(existing
, peerSet
); 
 108         require_quiet(SOSRingSetRecoveryKeyBag(existing
, SOSAccountGetMyFullPeerInfo(account
), keyBag
, error
), exit
); 
 110         newRing 
= CFRetainSafe(existing
); 
 112         CFReleaseNull(cleared
); 
 116     SOSClearErrorIfTrue(result
, error
); 
 119         secnotice("recovery", "Got error setting keybag for recovery : %@", error 
? (CFTypeRef
) *error 
: (CFTypeRef
) CFSTR("No error space.")); 
 125 bool SOSAccountRemoveRecoveryKey(SOSAccountRef account
, CFErrorRef 
*error
) { 
 126     bool result 
= SOSAccountSetKeybagForRecoveryRing(account
, NULL
, error
); 
 127     SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault
, account
, NULL
, NULL
); 
 128     account
->circle_rings_retirements_need_attention 
= true; 
 132 bool SOSAccountSetRecoveryKey(SOSAccountRef account
, CFDataRef pubData
, CFErrorRef 
*error
) { 
 133     __block 
bool result 
= false; 
 134     CFDataRef oldRecoveryKey 
= NULL
; 
 135     SOSRecoveryKeyBagRef rkbg 
= NULL
; 
 137     require_quiet(SOSAccountIsInCircle(account
, error
), exit
); 
 138     oldRecoveryKey 
= SOSAccountCopyRecoveryPublic(kCFAllocatorDefault
, account
, NULL
); // ok to fail here. don't collect error 
 139     require_action_quiet(!CFEqualSafe(pubData
, oldRecoveryKey
), exit
, result 
= true); 
 141     CFDataPerformWithHexString(pubData
, ^(CFStringRef recoveryKeyString
) { 
 142         CFDataPerformWithHexString(oldRecoveryKey
, ^(CFStringRef oldRecoveryKeyString
) { 
 143             secnotice("recovery", "SetRecoveryPublic: %@ from %@", recoveryKeyString
, oldRecoveryKeyString
); 
 147     rkbg 
= SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault
, account
, pubData
, error
); 
 148     require_quiet(rkbg
, exit
); 
 150     result 
= SOSAccountSetKeybagForRecoveryRing(account
, rkbg
, error
); 
 151     SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault
, account
, rkbg
, NULL
); 
 153     account
->circle_rings_retirements_need_attention 
= true; 
 156     CFReleaseNull(oldRecoveryKey
); 
 158     SOSClearErrorIfTrue(result
, error
); 
 160         secnotice("recovery", "SetRecoveryPublic Failed: %@", error 
? (CFTypeRef
) *error 
: (CFTypeRef
) CFSTR("No error space")); 
 165 bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView(SOSAccountRef account
, CFStringRef viewname
) { 
 167     CFErrorRef bsError 
= NULL
; 
 168     CFDataRef backupSliceData 
= NULL
; 
 169     SOSRingRef ring 
= NULL
; 
 170     SOSBackupSliceKeyBagRef backupSlice 
= NULL
; 
 172     CFDataRef rkbg 
= SOSAccountCopyRecoveryPublic(kCFAllocatorDefault
, account
, NULL
); 
 173     require_quiet(rkbg
, errOut
); 
 175     CFStringRef ringName 
= SOSBackupCopyRingNameForView(viewname
); 
 176     ring 
= SOSAccountCopyRing(account
, ringName
, &bsError
); 
 177     CFReleaseNull(ringName
); 
 179     require_quiet(ring
, errOut
); 
 181     //grab the backup slice from the ring 
 182     backupSliceData 
= SOSRingGetPayload(ring
, &bsError
); 
 183     require_quiet(backupSliceData
, errOut
); 
 185     backupSlice 
= SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault
, backupSliceData
, &bsError
); 
 186     require_quiet(backupSlice
, errOut
); 
 188     result 
= SOSBKSBPrefixedKeyIsInKeyBag(backupSlice
, bskbRkbgPrefix
, rkbg
); 
 189     CFReleaseNull(backupSlice
); 
 195         secnotice("backup", "Failed to find BKSB: %@, %@ (%@)", backupSliceData
, backupSlice
, bsError
); 
 197     CFReleaseNull(bsError
); 
 202 static void sosRecoveryAlertAndNotify(SOSAccountRef account
, SOSRecoveryKeyBagRef oldRingRKBG
, SOSRecoveryKeyBagRef ringRKBG
) { 
 203     secnotice("recovery", "Recovery Key changed: old %@ new %@", oldRingRKBG
, ringRKBG
); 
 204     notify_post(kSOSCCRecoveryKeyChanged
); 
 207 void SOSAccountEnsureRecoveryRing(SOSAccountRef account
) { 
 208     static SOSRecoveryKeyBagRef oldRingRKBG 
= NULL
; 
 209     bool inCircle 
= SOSAccountIsInCircle(account
, NULL
); 
 210     CFStringRef accountDSID 
= SOSAccountGetValue(account
, kSOSDSIDKey
, NULL
); // murfxxx this needs to be consulted still 
 211     SOSRecoveryKeyBagRef acctRKBG 
= SOSAccountCopyRecoveryKeyBagEntry(kCFAllocatorDefault
, account
, NULL
); 
 212     SOSRecoveryKeyBagRef ringRKBG 
= SOSAccountCopyRecoveryKeyBag(kCFAllocatorDefault
, account
, NULL
); 
 213     if(!SOSRecoveryKeyBagDSIDIs(ringRKBG
, accountDSID
)) CFReleaseNull(ringRKBG
); 
 214     if(!SOSRecoveryKeyBagDSIDIs(acctRKBG
, accountDSID
)) CFReleaseNull(acctRKBG
); 
 216     if(inCircle 
&& acctRKBG 
== NULL 
&& ringRKBG 
== NULL
) { 
 217         // Nothing to do at this time - notify if this is a change down below. 
 218     } else if(inCircle 
&& acctRKBG 
== NULL
) { // then we have a ringRKBG 
 219         secnotice("recovery", "Harvesting account recovery key from ring"); 
 220         SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault
, account
, ringRKBG
, NULL
); 
 221     } else if(ringRKBG 
== NULL
) { 
 222         // Nothing to do at this time - notify if this is a change down below. 
 223         secnotice("recovery", "Account has a recovery key, but none found in recovery ring"); 
 224     } else if(!CFEqual(acctRKBG
, ringRKBG
)) { 
 225         secnotice("recovery", "Harvesting account recovery key from ring"); 
 226         SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault
, account
, ringRKBG
, NULL
); 
 229     if(!CFEqualSafe(oldRingRKBG
, ringRKBG
)) { 
 230         sosRecoveryAlertAndNotify(account
, oldRingRKBG
, ringRKBG
); 
 231         CFTransferRetained(oldRingRKBG
, ringRKBG
); 
 234     CFReleaseNull(ringRKBG
); 
 235     CFReleaseNull(acctRKBG
);