]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountRecovery.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / 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 "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 }