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
);