]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountRecovery.m
Security-58286.220.15.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / 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 <Security/SecureObjectSync/SOSPeerInfoCollections.h>
34 #include <Security/SecureObjectSync/SOSViews.h>
35 #include <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
36 #include <Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h>
37
38 #include "SOSInternal.h"
39 #include "SecADWrapper.h"
40
41 #include <Security/SecureObjectSync/SOSRecoveryKeyBag.h>
42 #include <Security/SecureObjectSync/SOSRingRecovery.h>
43
44 CFStringRef kRecoveryRingKey = CFSTR("recoveryKeyBag");
45
46 bool SOSAccountSetRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccount* account, SOSRecoveryKeyBagRef rkbg, CFErrorRef *error) {
47 CFDataRef rkbg_as_data = NULL;
48 bool result = false;
49 rkbg_as_data = SOSRecoveryKeyBagCopyEncoded(rkbg, error);
50 result = rkbg_as_data && SOSAccountSetValue(account, kRecoveryRingKey, rkbg_as_data, error);
51 CFReleaseNull(rkbg_as_data);
52 return result;
53 }
54
55 SOSRecoveryKeyBagRef SOSAccountCopyRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) {
56 SOSRecoveryKeyBagRef retval = NULL;
57 CFDataRef rkbg_as_data = asData(SOSAccountGetValue(account, kRecoveryRingKey, error), error);
58 require_quiet(rkbg_as_data, errOut);
59 retval = SOSRecoveryKeyBagCreateFromData(allocator, rkbg_as_data, error);
60 errOut:
61 return retval;
62 }
63
64 SOSRecoveryKeyBagRef SOSAccountCopyRecoveryKeyBag(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) {
65 SOSRingRef recRing = NULL;
66 SOSRecoveryKeyBagRef rkbg = NULL;
67 require_action_quiet(account, errOut, SOSCreateError(kSOSErrorParam, CFSTR("No Account Object"), NULL, error));
68 recRing = SOSAccountCopyRingNamed(account, kSOSRecoveryRing, error);
69 require_quiet(recRing, errOut);
70 rkbg = SOSRingCopyRecoveryKeyBag(recRing, error);
71 errOut:
72 CFReleaseNull(recRing);
73 return rkbg;
74 }
75
76
77 static CFDataRef SOSRKNullKey(void) {
78 static CFDataRef localNullKey = NULL;
79 static dispatch_once_t onceToken;
80 dispatch_once(&onceToken, ^{
81 localNullKey = CFDataCreate(kCFAllocatorDefault, (const UInt8*) "nullkey", 8);
82 });
83 return localNullKey;
84 }
85
86 CFDataRef SOSAccountCopyRecoveryPublic(CFAllocatorRef allocator, SOSAccount* account, CFErrorRef *error) {
87 SOSRecoveryKeyBagRef rkbg = SOSAccountCopyRecoveryKeyBag(allocator, account, error);
88 CFDataRef recKey = NULL;
89 require_quiet(rkbg, errOut);
90 CFDataRef tmpKey = SOSRecoveryKeyBagGetKeyData(rkbg, error);
91 require_quiet(tmpKey, errOut);
92 require_quiet(!CFEqualSafe(tmpKey, SOSRKNullKey()), errOut);
93 recKey = CFDataCreateCopy(kCFAllocatorDefault, tmpKey);
94 errOut:
95 CFReleaseNull(rkbg);
96 if(!recKey) {
97 if(error && !(*error)) SOSErrorCreate(kSOSErrorNoKey, error, NULL, CFSTR("No recovery key available"));
98 }
99 return recKey;
100 }
101
102 static bool SOSAccountUpdateRecoveryRing(SOSAccount* account, CFErrorRef *error,
103 SOSRingRef (^modify)(SOSRingRef existing, CFErrorRef *error)) {
104 bool result = SOSAccountUpdateNamedRing(account, kSOSRecoveryRing, error, ^SOSRingRef(CFStringRef ringName, CFErrorRef *error) {
105 return SOSRingCreate(ringName, (__bridge CFStringRef)(account.peerID), kSOSRingRecovery, error);
106 }, modify);
107
108 return result;
109 }
110
111 static bool SOSAccountSetKeybagForRecoveryRing(SOSAccount* account, SOSRecoveryKeyBagRef keyBag, CFErrorRef *error) {
112 bool result = SOSAccountUpdateRecoveryRing(account, error, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
113 SOSRingRef newRing = NULL;
114 CFSetRef peerSet = [account.trust copyPeerSetMatching:^bool(SOSPeerInfoRef peer) {
115 return true;
116 }];
117 CFMutableSetRef cleared = CFSetCreateMutableForCFTypes(NULL);
118
119 SOSRingSetPeerIDs(existing, cleared);
120 SOSRingAddAll(existing, peerSet);
121
122 require_quiet(SOSRingSetRecoveryKeyBag(existing, account.fullPeerInfo, keyBag, error), exit);
123
124 newRing = CFRetainSafe(existing);
125 exit:
126 CFReleaseNull(cleared);
127 return newRing;
128 });
129
130 SOSClearErrorIfTrue(result, error);
131
132 if (!result) {
133 secnotice("recovery", "Got error setting keybag for recovery : %@", error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space."));
134 }
135 return result;
136 }
137
138 bool SOSAccountRemoveRecoveryKey(SOSAccount* account, CFErrorRef *error) {
139 return SOSAccountSetRecoveryKey(account, SOSRKNullKey(), error);
140 }
141
142 bool SOSAccountSetRecoveryKey(SOSAccount* account, CFDataRef pubData, CFErrorRef *error) {
143 __block bool result = false;
144 CFDataRef oldRecoveryKey = NULL;
145 SOSRecoveryKeyBagRef rkbg = NULL;
146
147 require_quiet([account isInCircle:error], exit);
148 oldRecoveryKey = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL); // ok to fail here. don't collect error
149 require_action_quiet(!CFEqualSafe(pubData, oldRecoveryKey), exit, result = true);
150
151 CFDataPerformWithHexString(pubData, ^(CFStringRef recoveryKeyString) {
152 CFDataPerformWithHexString(oldRecoveryKey, ^(CFStringRef oldRecoveryKeyString) {
153 secnotice("recovery", "SetRecoveryPublic: %@ from %@", recoveryKeyString, oldRecoveryKeyString);
154 });
155 });
156
157 rkbg = SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault, (__bridge CFTypeRef)account, pubData, error);
158 require_quiet(rkbg, exit);
159
160 result = SOSAccountSetKeybagForRecoveryRing(account, rkbg, error);
161 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, rkbg, NULL);
162 {
163 SOSAccountForEachBackupView(account, ^(const void *value) {
164 CFStringRef viewName = (CFStringRef)value;
165 SOSAccountNewBKSBForView(account, viewName, error);
166 });
167 }
168
169 account.circle_rings_retirements_need_attention = true;
170
171 exit:
172 CFReleaseNull(oldRecoveryKey);
173 CFReleaseNull(rkbg);
174 SOSClearErrorIfTrue(result, error);
175 if (!result) {
176 // if we're failing and something above failed to give an error - make a generic one.
177 if(error && !(*error)) SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("Failed to set Recovery Key"));
178 secnotice("recovery", "SetRecoveryPublic Failed: %@", error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space"));
179 }
180 return result;
181 }
182
183 bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView(SOSAccount* account, CFStringRef viewname) {
184 bool result = false;
185 CFErrorRef bsError = NULL;
186 CFDataRef backupSliceData = NULL;
187 SOSRingRef ring = NULL;
188 SOSBackupSliceKeyBagRef backupSlice = NULL;
189
190 CFDataRef rkbg = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL);
191 require_quiet(rkbg, errOut);
192
193 CFStringRef ringName = SOSBackupCopyRingNameForView(viewname);
194 ring = [account.trust copyRing:ringName err:&bsError];
195 CFReleaseNull(ringName);
196
197 require_quiet(ring, errOut);
198
199 //grab the backup slice from the ring
200 backupSliceData = SOSRingGetPayload(ring, &bsError);
201 require_quiet(backupSliceData, errOut);
202
203 backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError);
204 require_quiet(backupSlice, errOut);
205
206 result = SOSBKSBPrefixedKeyIsInKeyBag(backupSlice, bskbRkbgPrefix, rkbg);
207 CFReleaseNull(backupSlice);
208 errOut:
209 CFReleaseNull(ring);
210 CFReleaseNull(rkbg);
211
212 if (bsError) {
213 secnotice("backup", "Failed to find BKSB: %@, %@ (%@)", backupSliceData, backupSlice, bsError);
214 }
215 CFReleaseNull(bsError);
216 return result;
217
218 }
219
220 static void sosRecoveryAlertAndNotify(SOSAccount* account, SOSRecoveryKeyBagRef oldRingRKBG, SOSRecoveryKeyBagRef ringRKBG) {
221 secnotice("recovery", "Recovery Key changed: old %@ new %@", oldRingRKBG, ringRKBG);
222 notify_post(kSOSCCRecoveryKeyChanged);
223 }
224
225 void SOSAccountEnsureRecoveryRing(SOSAccount* account) {
226 static SOSRecoveryKeyBagRef oldRingRKBG = NULL;
227
228 if(![account isInCircle:NULL]) {
229 return;
230 }
231
232 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
233 SOSRecoveryKeyBagRef acctRKBG = SOSAccountCopyRecoveryKeyBagEntry(kCFAllocatorDefault, account, NULL);
234 SOSRecoveryKeyBagRef ringRKBG = SOSAccountCopyRecoveryKeyBag(kCFAllocatorDefault, account, NULL);
235 if(!SOSRecoveryKeyBagDSIDIs(ringRKBG, accountDSID)) CFReleaseNull(ringRKBG);
236 if(!SOSRecoveryKeyBagDSIDIs(acctRKBG, accountDSID)) CFReleaseNull(acctRKBG);
237
238 if(acctRKBG == NULL && ringRKBG == NULL) {
239 // Nothing to do at this time - notify if this is a change down below.
240 } else if(acctRKBG == NULL) { // then we have a ringRKBG
241 secnotice("recovery", "Harvesting account recovery key from ring");
242 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, ringRKBG, NULL);
243 } else if(ringRKBG == NULL) {
244 // Nothing to do at this time - notify if this is a change down below.
245 secnotice("recovery", "Account has a recovery key, but none found in recovery ring");
246 } else if(!CFEqualSafe(acctRKBG, ringRKBG)) {
247 secnotice("recovery", "Harvesting account recovery key from ring");
248 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, ringRKBG, NULL);
249 }
250
251 if(!CFEqualSafe(oldRingRKBG, ringRKBG)) {
252 sosRecoveryAlertAndNotify(account, oldRingRKBG, ringRKBG);
253 CFTransferRetained(oldRingRKBG, ringRKBG);
254 }
255
256 CFReleaseNull(ringRKBG);
257 CFReleaseNull(acctRKBG);
258 }