/ keychain / SecureObjectSync / SOSAccountCredentials.m
SOSAccountCredentials.m
  1  /*
  2   * Copyright (c) 2013-2014 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  #include <stdio.h>
 26  #include <AssertMacros.h>
 27  #include "SOSAccountPriv.h"
 28  #include "SOSPeerInfoCollections.h"
 29  #include "SOSTransport.h"
 30  #import "keychain/SecureObjectSync/SOSAccountTrust.h"
 31  #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
 32  #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
 33  
 34  #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
 35  //
 36  // MARK: User Credential management
 37  //
 38  
 39  
 40  // List of things to do
 41  // Update myFullPeerInfo in circle if needed
 42  // Fix iCloud Identity if needed
 43  // Gen sign if private key changed
 44  
 45  bool SOSAccountGenerationSignatureUpdate(SOSAccount* account, CFErrorRef *error) {
 46      bool result = false;
 47      SecKeyRef priv_key = SOSAccountGetPrivateCredential(account, error);
 48      require_quiet(priv_key, bail);
 49  
 50      [account.trust generationSignatureUpdateWith:account key:priv_key];
 51      
 52      result = true;
 53  bail:
 54      return result;
 55  }
 56  
 57  /* this one is meant to be local - not published over KVS. */
 58  static bool SOSAccountPeerSignatureUpdate(SOSAccount* account, SecKeyRef privKey, CFErrorRef *error) {
 59      SOSFullPeerInfoRef identity = NULL;
 60      SOSAccountTrustClassic *trust = account.trust;
 61      identity = trust.fullPeerInfo;
 62  
 63      return identity && SOSFullPeerInfoUpgradeSignatures(identity, privKey, error);
 64  }
 65  
 66  
 67  void SOSAccountPurgePrivateCredential(SOSAccount* account)
 68  {
 69      secnotice("circleOps", "Purging private account credential");
 70  
 71      if(account.accountPrivateKey)
 72      {
 73          account.accountPrivateKey = NULL;
 74      }
 75      if(account._password_tmp)
 76      {
 77          account._password_tmp = NULL;
 78      }
 79      if (account.user_private_timer) {
 80          dispatch_source_cancel(account.user_private_timer);
 81          account.user_private_timer = NULL;
 82          xpc_transaction_end();
 83      }
 84      if (account.lock_notification_token != NOTIFY_TOKEN_INVALID) {
 85          notify_cancel(account.lock_notification_token);
 86          account.lock_notification_token = NOTIFY_TOKEN_INVALID;
 87      }
 88  }
 89  
 90  
 91  static void SOSAccountSetTrustedUserPublicKey(SOSAccount* account, bool public_was_trusted, SecKeyRef privKey)
 92  {
 93      if (!privKey) return;
 94      SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(privKey);
 95      
 96      if (account.accountKey && account.accountKeyIsTrusted && CFEqual(publicKey, account.accountKey)) {
 97          CFReleaseNull(publicKey);
 98          return;
 99      }
100      
101      if(public_was_trusted && account.accountKey) {
102          account.previousAccountKey = account.accountKey;
103      }
104  
105      account.accountKey = publicKey;
106      account.accountKeyIsTrusted = true;
107      
108      if(!account.previousAccountKey) {
109          account.previousAccountKey = account.accountKey;
110      }
111  
112      CFReleaseNull(publicKey);
113  
114      CFStringRef keyid = SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL);
115  	secnotice("circleOps", "trusting new public key: %@", keyid);
116      CFReleaseNull(keyid);
117      notify_post(kPublicKeyAvailable);
118  }
119  
120  void SOSAccountSetUnTrustedUserPublicKey(SOSAccount* account, SecKeyRef publicKey) {
121      if(account.accountKeyIsTrusted && account.accountKey) {
122          secnotice("circleOps", "Moving : %@ to previousAccountKey", account.accountKey);
123          account.previousAccountKey = account.accountKey;
124      }
125  
126      account.accountKey = publicKey;
127      account.accountKeyIsTrusted = false;
128      
129      if(!account.previousAccountKey) {
130          account.previousAccountKey = account.accountKey;
131      }
132      CFStringRef keyid = SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL);
133      secnotice("circleOps", "not trusting new public key: %@", keyid);
134      CFReleaseNull(keyid);
135  }
136  
137  
138  static void SOSAccountSetPrivateCredential(SOSAccount* account, SecKeyRef private, CFDataRef password) {
139      if (!private)
140          return SOSAccountPurgePrivateCredential(account);
141      
142      secnotice("circleOps", "setting new private credential");
143  
144      account.accountPrivateKey = private;
145  
146      if (password) {
147          account._password_tmp = [[NSData alloc] initWithData:(__bridge NSData * _Nonnull)(password)];
148      } else {
149          account._password_tmp = NULL;
150      }
151  
152      bool resume_timer = false;
153      if (!account.user_private_timer) {
154          xpc_transaction_begin();
155          resume_timer = true;
156          account.user_private_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, account.queue);
157          dispatch_source_set_event_handler(account.user_private_timer, ^{
158              secnotice("keygen", "Timing out, purging private account credential");
159              SOSAccountPurgePrivateCredential(account);
160          });
161          int lockNotification;
162  
163          notify_register_dispatch(kUserKeybagStateChangeNotification, &lockNotification, account.queue, ^(int token) {
164              bool locked = false;
165              CFErrorRef lockCheckError = NULL;
166              
167              if (!SecAKSGetIsLocked(&locked, &lockCheckError)) {
168                  secerror("Checking for locked after change failed: %@", lockCheckError);
169              }
170              
171              if (locked) {
172                  SOSAccountPurgePrivateCredential(account);
173              }
174          });
175          [account setLock_notification_token:lockNotification];
176      }
177  
178      SOSAccountRestartPrivateCredentialTimer(account);
179      
180      if (resume_timer)
181          dispatch_resume(account.user_private_timer);
182  }
183  
184  void SOSAccountRestartPrivateCredentialTimer(SOSAccount*  account)
185  {
186      if (account.user_private_timer) {
187          // (Re)set the timer's fire time to now + 10 minutes with a 5 second fuzz factor.
188          dispatch_time_t purgeTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * 60 * NSEC_PER_SEC));
189          dispatch_source_set_timer(account.user_private_timer, purgeTime, DISPATCH_TIME_FOREVER, (int64_t)(5 * NSEC_PER_SEC));
190      }
191  }
192  
193  SecKeyRef SOSAccountGetPrivateCredential(SOSAccount* account, CFErrorRef* error)
194  {
195      if (account.accountPrivateKey == NULL) {
196          SOSCreateError(kSOSErrorPrivateKeyAbsent, CFSTR("Private Key not available - failed to prompt user recently"), NULL, error);
197      }
198      return account.accountPrivateKey;
199  }
200  
201  CFDataRef SOSAccountGetCachedPassword(SOSAccount* account, CFErrorRef* error)
202  {
203      if (account._password_tmp == NULL) {
204          secnotice("circleOps", "Password cache expired");
205      }
206      return (__bridge CFDataRef)(account._password_tmp);
207  }
208  static NSString *SOSUserCredentialAccount = @"SOSUserCredential";
209  static NSString *SOSUserCredentialAccessGroup = @"com.apple.security.sos-usercredential";
210  
211  void SOSAccountStashAccountKey(SOSAccount* account)
212  {
213      OSStatus status;
214      SecKeyRef user_private = account.accountPrivateKey;
215      NSData *data = CFBridgingRelease(SecKeyCopyExternalRepresentation(user_private, NULL));
216      if (data == NULL){
217          return;
218      }
219  
220      NSDictionary *attributes = @{
221          (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
222          (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
223          (__bridge id)kSecAttrIsInvisible : @YES,
224          (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
225          (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
226          (__bridge id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore),
227          (__bridge id)kSecValueData : data,
228      };
229  
230      status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
231  
232      if (status == errSecDuplicateItem) {
233          attributes = @{
234                         (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
235                         (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
236                         (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
237                         (__bridge id)kSecUseDataProtectionKeychain : @YES,
238                         };
239  
240          status = SecItemUpdate((__bridge CFDictionaryRef)attributes, (__bridge CFDictionaryRef)@{
241               (__bridge id)kSecValueData : data,
242               (__bridge id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore)
243          });
244  
245          if (status) {
246              secnotice("circleOps", "Failed to update user private key to keychain: %d", (int)status);
247          }
248      } else if (status != 0) {
249          secnotice("circleOps", "Failed to add user private key to keychain: %d", (int)status);
250      }
251      
252      if(status == 0) {
253          secnotice("circleOps", "Stored user private key stashed local keychain");
254      }
255      
256      return;
257  }
258  
259  SecKeyRef SOSAccountCopyStashedUserPrivateKey(SOSAccount* account, CFErrorRef *error)
260  {
261      SecKeyRef key = NULL;
262      CFDataRef data = NULL;
263      OSStatus status;
264  
265      NSDictionary *attributes = @{
266          (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
267          (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
268          (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
269          (__bridge id)kSecReturnData : @YES,
270          (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
271      };
272  
273      status = SecItemCopyMatching((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&data);
274      if (status) {
275          SecError(status, error, CFSTR("Failed fetching account credential: %d"), (int)status);
276          return NULL;
277      }
278  
279      NSDictionary *keyAttributes = @{
280          (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
281          (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
282      };
283  
284  
285      key = SecKeyCreateWithData(data, (__bridge CFDictionaryRef)keyAttributes, error);
286      CFReleaseNull(data);
287  
288      return key;
289  }
290  
291  SecKeyRef SOSAccountGetTrustedPublicCredential(SOSAccount* account, CFErrorRef* error)
292  {
293      if (account.accountKey == NULL || account.accountKeyIsTrusted == false) {
294          SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available. The iCloud Password must be provided to the syncing subsystem to repair this."), NULL, error);
295          return NULL;
296      }
297      return account.accountKey;
298  }
299  
300  bool SOSAccountHasPublicKey(SOSAccount* account, CFErrorRef* error)
301  {
302      return SOSAccountGetTrustedPublicCredential(account, error);
303  }
304  
305  static void sosAccountSetTrustedCredentials(SOSAccount* account, CFDataRef user_password, SecKeyRef user_private, bool public_was_trusted) {
306      if(!SOSAccountFullPeerInfoVerify(account, user_private, NULL)){
307          (void) SOSAccountPeerSignatureUpdate(account, user_private, NULL);
308      }
309      SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private);
310      SOSAccountSetPrivateCredential(account, user_private, user_password);
311      SOSAccountCheckForAlwaysOnViews(account);
312  }
313  
314  static SecKeyRef sosAccountCreateKeyIfPasswordIsCorrect(SOSAccount* account, CFDataRef user_password, CFErrorRef *error) {
315      SecKeyRef user_private = NULL;
316      require_quiet(account.accountKey && account.accountKeyDerivationParameters, errOut);
317      user_private = SOSUserKeygen(user_password, (__bridge CFDataRef)(account.accountKeyDerivationParameters), error);
318      require_quiet(user_private, errOut);
319  
320      require_action_quiet(SOSAccountValidateAccountCredential(account, user_private, error), errOut, CFReleaseNull(user_private));
321  errOut:
322      return user_private;
323  }
324  
325  static bool sosAccountValidatePasswordOrFail(SOSAccount* account, CFDataRef user_password, CFErrorRef *error) {
326      SecKeyRef privKey = sosAccountCreateKeyIfPasswordIsCorrect(account, user_password, error);
327      if(!privKey) {
328          if(account.accountKeyDerivationParameters) {
329              SecKeyRef newKey = NULL;
330              CFDataRef pbkdfParams = NULL;
331              CFErrorRef localError = NULL;
332              if(SOSAccountRetrieveCloudParameters(account, &newKey, (__bridge CFDataRef)(account.accountKeyDerivationParameters), &pbkdfParams, &localError)) {
333                  debugDumpUserParameters(CFSTR("sosAccountValidatePasswordOrFail"), pbkdfParams);
334              } else {
335                  secnotice("circleOps", "Failed to retrieve cloud parameters - %@", localError);
336                  if(error) {
337                      CFTransferRetained(*error, localError);
338                  }
339              }
340              CFReleaseNull(newKey);
341              CFReleaseNull(pbkdfParams);
342          }
343          SOSCreateError(kSOSErrorWrongPassword, CFSTR("Could not create correct key with password."), NULL, error);
344          return false;
345      }
346      sosAccountSetTrustedCredentials(account, user_password, privKey, account.accountKeyIsTrusted);
347      CFReleaseNull(privKey);
348      return true;
349  }
350  
351  void SOSAccountSetParameters(SOSAccount* account, CFDataRef parameters) {
352      account.accountKeyDerivationParameters = (__bridge NSData *) parameters;
353  }
354  
355  bool SOSAccountValidateAccountCredential(SOSAccount* account, SecKeyRef accountPrivateKey, CFErrorRef *error)
356  {
357      bool res = false;
358      SecKeyRef publicCandidate = SecKeyCreatePublicFromPrivate(accountPrivateKey);
359  
360      if(CFEqualSafe(account.accountKey, publicCandidate)) {
361          res = true;
362      } else {
363          CFErrorRef localError = NULL;
364          CFStringRef accountHpub = SOSCopyIDOfKey(account.accountKey, NULL);
365          CFStringRef candidateHpub = SOSCopyIDOfKey(publicCandidate, NULL);
366          SOSCreateErrorWithFormat(kSOSErrorWrongPassword, NULL, &localError, NULL, CFSTR("Password generated pubkey doesn't match - candidate: %@  known: %@"), candidateHpub, accountHpub);
367          secnotice("circleop", "Password generated pubkey doesn't match - candidate: %@  known: %@", candidateHpub, accountHpub);
368          if (error) {
369              *error = localError;
370              localError = NULL;
371          } else {
372              CFReleaseNull(localError);
373          }
374          CFReleaseNull(accountHpub);
375          CFReleaseNull(candidateHpub);
376      }
377      CFReleaseNull(publicCandidate);
378      return res;
379  }
380  
381  bool SOSAccountAssertStashedAccountCredential(SOSAccount* account, CFErrorRef *error)
382  {
383      SecKeyRef accountPrivateKey = NULL, publicCandidate = NULL;
384      bool result = false;
385  
386      require_action(account.accountKey, fail, SOSCreateError(kSOSErrorWrongPassword, CFSTR("account public key missing, can't check stashed copy"), NULL, error));
387      require_action(account.accountKeyIsTrusted, fail, SOSCreateError(kSOSErrorWrongPassword, CFSTR("public key no not valid, can't check stashed copy"), NULL, error));
388  
389      accountPrivateKey = SOSAccountCopyStashedUserPrivateKey(account, error);
390      require_action_quiet(accountPrivateKey, fail, secnotice("circleOps", "Looked for a stashed private key, didn't find one"));
391  
392      require(SOSAccountValidateAccountCredential(account, accountPrivateKey, error), fail);
393  
394      sosAccountSetTrustedCredentials(account, NULL, accountPrivateKey, true);
395  
396      result = true;
397  fail:
398      CFReleaseSafe(publicCandidate);
399      CFReleaseSafe(accountPrivateKey);
400  
401      return result;
402  }
403  
404  
405  
406  bool SOSAccountAssertUserCredentials(SOSAccount* account, CFStringRef user_account, CFDataRef user_password, CFErrorRef *error)
407  {
408      bool public_was_trusted = account.accountKeyIsTrusted;
409      account.accountKeyIsTrusted = false;
410      SecKeyRef user_private = NULL;
411      CFDataRef parameters = NULL;
412      
413      // if this succeeds, skip to the end.  Success will update account.accountKeyIsTrusted by side-effect.
414      require_quiet(!sosAccountValidatePasswordOrFail(account, user_password, error), recordCred);
415      
416      // We may or may not have parameters here.
417      // In any case we tried using them and they didn't match
418      // So forget all that and start again, assume we're the first to push anything useful.
419  
420      if (CFDataGetLength(user_password) > 20) {
421          secwarning("Long password (>20 byte utf8) being used to derive account key – this may be a PET by mistake!!");
422      }
423      
424      parameters = SOSUserKeyCreateGenerateParameters(error);
425      require_quiet(user_private = SOSUserKeygen(user_password, parameters, error), errOut);
426      SOSAccountSetParameters(account, parameters);
427      sosAccountSetTrustedCredentials(account, user_password, user_private, public_was_trusted);
428  
429      CFErrorRef publishError = NULL;
430      if (!SOSAccountPublishCloudParameters(account, &publishError)) {
431          secerror("Failed to publish new cloud parameters: %@", publishError);
432      }
433      
434      CFReleaseNull(publishError);
435  recordCred:
436      SOSAccountStashAccountKey(account);
437      SOSAccountSetValue(account, kSOSAccountName, user_account, NULL);
438  errOut:
439      CFReleaseNull(parameters);
440      CFReleaseNull(user_private);
441      secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountAssertUserCredentials");
442      account.key_interests_need_updating = true;
443      return account.accountKeyIsTrusted;
444  }
445  
446  
447  bool SOSAccountTryUserCredentials(SOSAccount* account, CFStringRef user_account, CFDataRef user_password, CFErrorRef *error) {
448      bool success = sosAccountValidatePasswordOrFail(account, user_password, error);
449      if(success) {
450          SOSAccountStashAccountKey(account);
451          SOSAccountSetValue(account, kSOSAccountName, user_account, NULL);
452      }
453      secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountTryUserCredentials");
454      account.key_interests_need_updating = true;
455      return success;
456  }
457  
458  bool SOSAccountTryUserPrivateKey(SOSAccount* account, SecKeyRef user_private, CFErrorRef *error) {
459      bool retval = SOSAccountValidateAccountCredential(account, user_private, error);
460      if(!retval) {
461          secnotice("circleOps", "Failed to accept provided user_private as credential");
462          return retval;
463      }
464      sosAccountSetTrustedCredentials(account, NULL, user_private, account.accountKeyIsTrusted);
465      SOSAccountStashAccountKey(account);
466      secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountTryUserPrivateKey");
467      account.key_interests_need_updating = true;
468      secnotice("circleOps", "Accepted provided user_private as credential");
469      return retval;
470  }
471  
472  bool SOSAccountRetryUserCredentials(SOSAccount* account) {
473      CFDataRef cachedPassword = SOSAccountGetCachedPassword(account, NULL);
474      if (cachedPassword == NULL)
475          return false;
476      /*
477       * SOSAccountTryUserCredentials reset the cached password internally,
478       * so we must have a retain of the password over SOSAccountTryUserCredentials().
479       */
480      CFRetain(cachedPassword);
481      bool res = SOSAccountTryUserCredentials(account, NULL, cachedPassword, NULL);
482      CFRelease(cachedPassword);
483      return res;
484  }
485  
486  
487