SOSAccountRecovery.m
1 /* 2 * Copyright (c) 2016 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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 11 * file. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 // 25 // SOSAccountRecovery.c 26 // Security 27 // 28 29 #include <AssertMacros.h> 30 #include "SOSAccountPriv.h" 31 #include "SOSCloudKeychainClient.h" 32 33 #include "keychain/SecureObjectSync/SOSPeerInfoCollections.h" 34 #include <Security/SecureObjectSync/SOSViews.h> 35 #include "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h" 36 #include "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h" 37 38 #include "keychain/SecureObjectSync/SOSInternal.h" 39 40 #include "keychain/SecureObjectSync/SOSRecoveryKeyBag.h" 41 #include "keychain/SecureObjectSync/SOSRingRecovery.h" 42 43 CFStringRef kRecoveryRingKey = CFSTR("recoveryKeyBag"); 44 45 // When a recovery key is harvested from a recovery ring it's sent here 46 bool SOSAccountSetRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccount* account, SOSRecoveryKeyBagRef rkbg, CFErrorRef *error) { 47 bool result = false; 48 if(rkbg == NULL) { 49 result = SOSAccountClearValue(account, kRecoveryRingKey, error); 50 } else { 51 CFDataRef recoverKeyData = SOSRecoveryKeyBagGetKeyData(rkbg, NULL); 52 if(CFEqualSafe(recoverKeyData, SOSRKNullKey())) { 53 result = SOSAccountClearValue(account, kRecoveryRingKey, error); 54 } else { 55 CFDataRef rkbg_as_data = SOSRecoveryKeyBagCopyEncoded(rkbg, error); 56 result = rkbg_as_data && SOSAccountSetValue(account, kRecoveryRingKey, rkbg_as_data, error); 57 CFReleaseNull(rkbg_as_data); 58 } 59 } 60 return result; 61 } 62 63 SOSRecoveryKeyBagRef SOSAccountCopyRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) { 64 SOSRecoveryKeyBagRef retval = NULL; 65 CFDataRef rkbg_as_data = asData(SOSAccountGetValue(account, kRecoveryRingKey, error), error); 66 require_quiet(rkbg_as_data, errOut); 67 retval = SOSRecoveryKeyBagCreateFromData(allocator, rkbg_as_data, error); 68 errOut: 69 return retval; 70 } 71 72 CFDataRef SOSAccountCopyRecoveryPublic(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) { 73 SOSRecoveryKeyBagRef rkbg = SOSAccountCopyRecoveryKeyBagEntry(allocator, account, error); 74 CFDataRef recKey = NULL; 75 require_quiet(rkbg, errOut); 76 CFDataRef tmpKey = SOSRecoveryKeyBagGetKeyData(rkbg, error); 77 require_quiet(tmpKey, errOut); 78 require_quiet(!CFEqualSafe(tmpKey, SOSRKNullKey()), errOut); 79 recKey = CFDataCreateCopy(kCFAllocatorDefault, tmpKey); 80 errOut: 81 CFReleaseNull(rkbg); 82 if(!recKey) { 83 if(error && !(*error)) SOSErrorCreate(kSOSErrorNoKey, error, NULL, CFSTR("No recovery key available")); 84 } 85 return recKey; 86 } 87 88 static bool SOSAccountUpdateRecoveryRing(SOSAccount* 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, (__bridge CFStringRef)(account.peerID), kSOSRingRecovery, error); 92 }, modify); 93 94 return result; 95 } 96 97 bool SOSAccountRemoveRecoveryKey(SOSAccount* account, CFErrorRef *error) { 98 return SOSAccountSetRecoveryKey(account, NULL, error); 99 } 100 101 102 // Recovery Setting From a Local Client goes through here 103 bool SOSAccountSetRecoveryKey(SOSAccount* account, CFDataRef pubData, CFErrorRef *error) { 104 __block bool result = false; 105 CFDataRef oldRecoveryKey = NULL; 106 SOSRecoveryKeyBagRef rkbg = NULL; 107 108 if(![account isInCircle:error]) return false; 109 oldRecoveryKey = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL); // ok to fail here. don't collect error 110 111 CFDataPerformWithHexString(pubData, ^(CFStringRef recoveryKeyString) { 112 CFDataPerformWithHexString(oldRecoveryKey, ^(CFStringRef oldRecoveryKeyString) { 113 secnotice("recovery", "SetRecoveryPublic: %@ from %@", recoveryKeyString, oldRecoveryKeyString); 114 }); 115 }); 116 CFReleaseNull(oldRecoveryKey); 117 118 rkbg = SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault, (__bridge CFTypeRef)account, pubData, error); 119 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, rkbg, NULL); 120 SOSAccountUpdateRecoveryRing(account, error, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) { 121 SOSRingRef newRing = NULL; 122 CFMutableSetRef peerInfoIDs = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); 123 SOSCircleForEachValidSyncingPeer(account.trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) { 124 CFSetAddValue(peerInfoIDs, SOSPeerInfoGetPeerID(peer)); 125 }); 126 SOSRingSetPeerIDs(existing, peerInfoIDs); 127 if(rkbg) { 128 SOSRingSetRecoveryKeyBag(existing, account.fullPeerInfo, rkbg, error); 129 } else { 130 SOSRecoveryKeyBagRef ringrkbg = SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault, (__bridge CFTypeRef)account, SOSRKNullKey(), error); 131 SOSRingSetRecoveryKeyBag(existing, account.fullPeerInfo, ringrkbg, error); 132 CFRelease(ringrkbg); 133 } 134 SOSRingGenerationSign(existing, NULL, account.trust.fullPeerInfo, error); 135 newRing = CFRetainSafe(existing); 136 return newRing; 137 }); 138 CFReleaseNull(rkbg); 139 140 if(SOSPeerInfoHasBackupKey(account.trust.peerInfo)) { 141 SOSAccountProcessBackupRings(account); 142 } 143 account.circle_rings_retirements_need_attention = true; 144 result = true; 145 146 SOSClearErrorIfTrue(result, error); 147 if (!result) { 148 // if we're failing and something above failed to give an error - make a generic one. 149 if(error && !(*error)) SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("Failed to set Recovery Key")); 150 secnotice("recovery", "SetRecoveryPublic Failed: %@", error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space")); 151 } 152 return result; 153 } 154 155 bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView(SOSAccount* account, CFStringRef viewname) { 156 bool result = false; 157 CFErrorRef bsError = NULL; 158 CFDataRef backupSliceData = NULL; 159 SOSRingRef ring = NULL; 160 SOSBackupSliceKeyBagRef backupSlice = NULL; 161 162 CFDataRef recoveryKeyFromAccount = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL); 163 require_quiet(recoveryKeyFromAccount, errOut); 164 165 CFStringRef ringName = SOSBackupCopyRingNameForView(viewname); 166 ring = [account.trust copyRing:ringName err:&bsError]; 167 CFReleaseNull(ringName); 168 169 require_quiet(ring, errOut); 170 171 //grab the backup slice from the ring 172 backupSliceData = SOSRingGetPayload(ring, &bsError); 173 require_quiet(backupSliceData, errOut); 174 175 backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError); 176 require_quiet(backupSlice, errOut); 177 178 result = SOSBKSBPrefixedKeyIsInKeyBag(backupSlice, bskbRkbgPrefix, recoveryKeyFromAccount); 179 CFReleaseNull(backupSlice); 180 errOut: 181 CFReleaseNull(ring); 182 CFReleaseNull(recoveryKeyFromAccount); 183 184 if (bsError) { 185 secnotice("backup", "Failed to find BKSB: %@, %@ (%@)", backupSliceData, backupSlice, bsError); 186 } 187 CFReleaseNull(bsError); 188 return result; 189 190 } 191 192 static void sosRecoveryAlertAndNotify(SOSAccount* account, SOSRecoveryKeyBagRef oldRingRKBG, SOSRecoveryKeyBagRef ringRKBG) { 193 secnotice("recovery", "Recovery Key changed: old %@ new %@", oldRingRKBG, ringRKBG); 194 notify_post(kSOSCCRecoveryKeyChanged); 195 } 196 197 void SOSAccountEnsureRecoveryRing(SOSAccount* account) { 198 dispatch_assert_queue(account.queue); 199 200 static SOSRecoveryKeyBagRef oldRingRKBG = NULL; 201 SOSRecoveryKeyBagRef acctRKBG = SOSAccountCopyRecoveryKeyBagEntry(kCFAllocatorDefault, account, NULL); 202 if(!CFEqualSafe(acctRKBG, oldRingRKBG)) { 203 sosRecoveryAlertAndNotify(account, oldRingRKBG, acctRKBG); 204 CFRetainAssign(oldRingRKBG, acctRKBG); 205 } 206 }