/ keychain / SecureObjectSync / SOSAccountRecovery.m
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  }