]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountRecovery.c
Security-57740.51.3.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSAccountRecovery.c
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/SOSBackupSliceKeyBag.h>
34 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
35 #include <Security/SecureObjectSync/SOSViews.h>
36
37 #include "SOSInternal.h"
38 #include "SecADWrapper.h"
39
40
41
42 #include <Security/SecureObjectSync/SOSRecoveryKeyBag.h>
43 #include <Security/SecureObjectSync/SOSRingRecovery.h>
44
45 CFStringRef kRecoveryRingKey = CFSTR("recoveryKeyBag");
46
47 bool SOSAccountSetRecoveryKeyBagEntry(CFAllocatorRef allocator, SOSAccountRef account, SOSRecoveryKeyBagRef rkbg, CFErrorRef *error) {
48 CFDataRef rkbg_as_data = NULL;
49 bool result = false;
50 rkbg_as_data = SOSRecoveryKeyBagCopyEncoded(rkbg, error);
51 result = rkbg_as_data && SOSAccountSetValue(account, kRecoveryRingKey, rkbg_as_data, error);
52 CFReleaseNull(rkbg_as_data);
53 return result;
54 }
55
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);
61 errOut:
62 return retval;
63 }
64
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);
72 errOut:
73 CFReleaseNull(recRing);
74 return rkbg;
75 }
76
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);
83 errOut:
84 CFReleaseNull(rkbg);
85 return recKey;
86 }
87
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);
92 }, modify);
93
94 return result;
95 }
96
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) {
101 return true;
102 });
103 CFMutableSetRef cleared = CFSetCreateMutableForCFTypes(NULL);
104
105 SOSRingSetPeerIDs(existing, cleared);
106 SOSRingAddAll(existing, peerSet);
107
108 require_quiet(SOSRingSetRecoveryKeyBag(existing, SOSAccountGetMyFullPeerInfo(account), keyBag, error), exit);
109
110 newRing = CFRetainSafe(existing);
111 exit:
112 CFReleaseNull(cleared);
113 return newRing;
114 });
115
116 SOSClearErrorIfTrue(result, error);
117
118 if (!result) {
119 secnotice("recovery", "Got error setting keybag for recovery : %@", error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space."));
120 }
121 return result;
122 }
123
124
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;
129 return result;
130 }
131
132 bool SOSAccountSetRecoveryKey(SOSAccountRef account, CFDataRef pubData, CFErrorRef *error) {
133 __block bool result = false;
134 CFDataRef oldRecoveryKey = NULL;
135 SOSRecoveryKeyBagRef rkbg = NULL;
136
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);
140
141 CFDataPerformWithHexString(pubData, ^(CFStringRef recoveryKeyString) {
142 CFDataPerformWithHexString(oldRecoveryKey, ^(CFStringRef oldRecoveryKeyString) {
143 secnotice("recovery", "SetRecoveryPublic: %@ from %@", recoveryKeyString, oldRecoveryKeyString);
144 });
145 });
146
147 rkbg = SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault, account, pubData, error);
148 require_quiet(rkbg, exit);
149
150 result = SOSAccountSetKeybagForRecoveryRing(account, rkbg, error);
151 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, rkbg, NULL);
152
153 account->circle_rings_retirements_need_attention = true;
154
155 exit:
156 CFReleaseNull(oldRecoveryKey);
157 CFReleaseNull(rkbg);
158 SOSClearErrorIfTrue(result, error);
159 if (!result) {
160 secnotice("recovery", "SetRecoveryPublic Failed: %@", error ? (CFTypeRef) *error : (CFTypeRef) CFSTR("No error space"));
161 }
162 return result;
163 }
164
165 bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView(SOSAccountRef account, CFStringRef viewname) {
166 bool result = false;
167 CFErrorRef bsError = NULL;
168 CFDataRef backupSliceData = NULL;
169 SOSRingRef ring = NULL;
170 SOSBackupSliceKeyBagRef backupSlice = NULL;
171
172 CFDataRef rkbg = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL);
173 require_quiet(rkbg, errOut);
174
175 CFStringRef ringName = SOSBackupCopyRingNameForView(viewname);
176 ring = SOSAccountCopyRing(account, ringName, &bsError);
177 CFReleaseNull(ringName);
178
179 require_quiet(ring, errOut);
180
181 //grab the backup slice from the ring
182 backupSliceData = SOSRingGetPayload(ring, &bsError);
183 require_quiet(backupSliceData, errOut);
184
185 backupSlice = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, backupSliceData, &bsError);
186 require_quiet(backupSlice, errOut);
187
188 result = SOSBKSBPrefixedKeyIsInKeyBag(backupSlice, bskbRkbgPrefix, rkbg);
189 CFReleaseNull(backupSlice);
190 errOut:
191 CFReleaseNull(ring);
192 CFReleaseNull(rkbg);
193
194 if (bsError) {
195 secnotice("backup", "Failed to find BKSB: %@, %@ (%@)", backupSliceData, backupSlice, bsError);
196 }
197 CFReleaseNull(bsError);
198 return result;
199
200 }
201
202 static void sosRecoveryAlertAndNotify(SOSAccountRef account, SOSRecoveryKeyBagRef oldRingRKBG, SOSRecoveryKeyBagRef ringRKBG) {
203 secnotice("recovery", "Recovery Key changed: old %@ new %@", oldRingRKBG, ringRKBG);
204 notify_post(kSOSCCRecoveryKeyChanged);
205 }
206
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);
215
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);
227 }
228
229 if(!CFEqualSafe(oldRingRKBG, ringRKBG)) {
230 sosRecoveryAlertAndNotify(account, oldRingRKBG, ringRKBG);
231 CFTransferRetained(oldRingRKBG, ringRKBG);
232 }
233
234 CFReleaseNull(ringRKBG);
235 CFReleaseNull(acctRKBG);
236 }