/ keychain / SecureObjectSync / SOSInternal.m
SOSInternal.m
  1  /*
  2   * Copyright (c) 2012-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 "keychain/SecureObjectSync/SOSInternal.h"
 26  #include "keychain/SecureObjectSync/SOSCircle.h"
 27  #include <Security/SecureObjectSync/SOSCloudCircle.h>
 28  #include "keychain/SecureObjectSync/SOSKVSKeys.h"
 29  #include <Security/SecureObjectSync/SOSViews.h>
 30  #include "utilities/SecCFError.h"
 31  #include "utilities/SecCFRelease.h"
 32  #include "utilities/SecCFWrappers.h"
 33  #include "utilities/iOSforOSX.h"
 34  
 35  #include <CoreFoundation/CoreFoundation.h>
 36  
 37  #include <Security/SecKey.h>
 38  #include <Security/SecKeyPriv.h>
 39  #include <Security/SecItem.h>
 40  #include "keychain/securityd/SecDbItem.h" // For SecError
 41  #include "utilities/iOSforOSX.h"
 42  
 43  #include <Security/SecBase64.h>
 44  #include <utilities/der_plist.h>
 45  #include <utilities/der_plist_internal.h>
 46  #include <corecrypto/ccder.h>
 47  #include <utilities/der_date.h>
 48  
 49  #include <corecrypto/ccrng.h>
 50  #include <corecrypto/ccdigest.h>
 51  #include <corecrypto/ccsha2.h>
 52  #include <corecrypto/ccpbkdf2.h>
 53  
 54  #if !TARGET_OS_OSX
 55  #import <SoftLinking/SoftLinking.h>
 56  #import <ManagedConfiguration/ManagedConfiguration.h>
 57  #import <ManagedConfiguration/MCProfileConnection_Misc.h>
 58  #endif
 59  
 60  #include <os/lock.h>
 61  
 62  #include <AssertMacros.h>
 63  
 64  const CFStringRef kSOSErrorDomain = CFSTR("com.apple.security.sos.error");
 65  const CFStringRef kSOSDSIDKey = CFSTR("AccountDSID");
 66  const CFStringRef SOSTransportMessageTypeIDSV2 = CFSTR("IDS2.0");
 67  const CFStringRef SOSTransportMessageTypeKVS = CFSTR("KVS");
 68  const CFStringRef kSOSCountKey = CFSTR("numberOfErrorsDeep");
 69  
 70  bool SOSErrorCreate(CFIndex errorCode, CFErrorRef *error, CFDictionaryRef formatOptions, CFStringRef format, ...)
 71      CF_FORMAT_FUNCTION(4, 5);
 72  
 73  
 74  bool SOSErrorCreate(CFIndex errorCode, CFErrorRef *error, CFDictionaryRef formatOptions, CFStringRef format, ...) {
 75      if (!errorCode) return true;
 76      if (error && !*error) {
 77          va_list va;
 78          va_start(va, format);
 79          SecCFCreateErrorWithFormatAndArguments(errorCode, kSOSErrorDomain, NULL, error, formatOptions, format, va);
 80          va_end(va);
 81      }
 82      return false;
 83  }
 84  
 85  bool SOSCreateError(CFIndex errorCode, CFStringRef descriptionString, CFErrorRef previousError, CFErrorRef *newError) {
 86      SOSCreateErrorWithFormat(errorCode, previousError, newError, NULL, CFSTR("%@"), descriptionString);
 87      return false;
 88  }
 89  
 90  bool SOSCreateErrorWithFormat(CFIndex errorCode, CFErrorRef previousError, CFErrorRef *newError,
 91                                CFDictionaryRef formatOptions, CFStringRef format, ...) {
 92      va_list va;
 93      va_start(va, format);
 94      bool res = SOSCreateErrorWithFormatAndArguments(errorCode, previousError, newError, formatOptions, format, va);
 95      va_end(va);
 96      return res;
 97  }
 98  
 99  bool SOSCreateErrorWithFormatAndArguments(CFIndex errorCode, CFErrorRef previousError, CFErrorRef *newError,
100                                            CFDictionaryRef formatOptions, CFStringRef format, va_list args) {
101      return SecCFCreateErrorWithFormatAndArguments(errorCode, kSOSErrorDomain, previousError, newError, formatOptions, format, args);
102  }
103  
104  
105  //
106  // Utility Functions
107  //
108  
109  static OSStatus GenerateECPairImp(int keySize, CFBooleanRef permanent, SecKeyRef* public, SecKeyRef *full)
110  {
111      static const CFStringRef sTempNameToUse = CFSTR("GenerateECPair Temporary Key - Shouldn't be live");
112  
113      CFNumberRef signing_bitsize = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &keySize);
114  
115      CFDictionaryRef keygen_parameters = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
116                                                                       kSecAttrKeyType,       kSecAttrKeyTypeEC,
117                                                                       kSecAttrKeySizeInBits, signing_bitsize,
118                                                                       kSecAttrIsPermanent,   permanent,
119                                                                       kSecAttrLabel,         sTempNameToUse,
120                                                                       NULL);
121      CFReleaseNull(signing_bitsize);
122      OSStatus result = SecKeyGeneratePair(keygen_parameters, public, full);
123      CFReleaseNull(keygen_parameters);
124  
125      return result;
126  }
127  
128  OSStatus GenerateECPair(int keySize, SecKeyRef* public, SecKeyRef *full)
129  {
130      return GenerateECPairImp(keySize, kCFBooleanFalse, public, full);
131  }
132  
133  OSStatus GeneratePermanentECPair(int keySize, SecKeyRef* public, SecKeyRef *full)
134  {
135      return GenerateECPairImp(keySize, kCFBooleanTrue, public, full);
136  }
137  
138  static CFStringRef SOSCircleCopyDescriptionFromData(CFDataRef data)
139  {
140      CFErrorRef error = NULL;
141      CFStringRef result = NULL;
142  
143      SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, data, &error);
144  
145      if (circle)
146          result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), circle);
147  
148      CFReleaseSafe(circle);
149  
150      return result;
151  }
152  
153  CFStringRef SOSItemsChangedCopyDescription(CFDictionaryRef changes, bool is_sender)
154  {
155      CFMutableStringRef string = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("<Changes: {\n"));
156  
157      CFDictionaryForEach(changes, ^(const void *key, const void *value) {
158          CFStringRef value_description = NULL;
159          if (isString(key) && isData(value)) {
160              CFDataRef value_data = (CFDataRef) value;
161              switch (SOSKVSKeyGetKeyType(key)) {
162                  case kCircleKey:
163                      value_description = SOSCircleCopyDescriptionFromData(value_data);
164                      break;
165                  case kMessageKey:
166                      value_description = CFCopyDescription(value_data);
167                      break;
168                  default:
169                      break;
170              }
171  
172          }
173          CFStringAppendFormat(string, NULL, CFSTR("    '%@' %s %@\n"),
174                               key,
175                               is_sender ? "<=" : "=>",
176                               value_description ? value_description : value);
177          
178          CFReleaseNull(value_description);
179      });
180  
181      CFStringAppendFormat(string, NULL, CFSTR("}"));
182  
183      return string;
184  }
185  
186  CFStringRef SOSCopyHashBufAsString(uint8_t *digest, size_t len) {
187      char encoded[2 * len + 1]; // Big enough for base64 encoding.
188  
189      size_t length = SecBase64Encode(digest, len, encoded, sizeof(encoded));
190      assert(length && length < sizeof(encoded));
191      if (length > kSOSPeerIDLengthMax)
192          length = kSOSPeerIDLengthMax;
193      encoded[length] = 0;
194      return CFStringCreateWithCString(kCFAllocatorDefault, encoded, kCFStringEncodingASCII);
195  }
196  
197  CFStringRef SOSCopyIDOfDataBuffer(CFDataRef data, CFErrorRef *error) {
198      const struct ccdigest_info * di = ccsha1_di();
199      uint8_t digest[di->output_size];    
200      ccdigest(di, CFDataGetLength(data), CFDataGetBytePtr(data), digest);
201      return SOSCopyHashBufAsString(digest, sizeof(digest));
202  }
203  
204  CFStringRef SOSCopyIDOfDataBufferWithLength(CFDataRef data, CFIndex len, CFErrorRef *error) {
205      CFStringRef retval = NULL;
206      CFStringRef tmp = SOSCopyIDOfDataBuffer(data, error);
207      if(tmp) retval = CFStringCreateWithSubstring(kCFAllocatorDefault, tmp, CFRangeMake(0, len));
208      CFReleaseNull(tmp);
209      return retval;
210  }
211  
212  CFStringRef SOSCopyIDOfKey(SecKeyRef key, CFErrorRef *error) {
213      CFDataRef publicBytes = NULL;
214      CFStringRef result = NULL;
215      require_action_quiet(key, errOut, SOSErrorCreate(kSOSErrorNoKey, error, NULL, CFSTR("NULL key passed to SOSCopyIDOfKey")));
216      require_quiet(SecError(SecKeyCopyPublicBytes(key, &publicBytes), error, CFSTR("Failed to export public bytes %@"), key), errOut);
217      result = SOSCopyIDOfDataBuffer(publicBytes, error);
218  errOut:
219      CFReleaseNull(publicBytes);
220      return result;
221  }
222  
223  CFStringRef SOSCopyIDOfKeyWithLength(SecKeyRef key, CFIndex len, CFErrorRef *error) {
224      CFStringRef retval = NULL;
225      CFStringRef tmp = SOSCopyIDOfKey(key, error);
226      if(tmp) retval = CFStringCreateWithSubstring(kCFAllocatorDefault, tmp, CFRangeMake(0, len));
227      CFReleaseNull(tmp);
228      return retval;
229  }
230  
231  
232  CFGiblisGetSingleton(ccec_const_cp_t, SOSGetBackupKeyCurveParameters, sBackupKeyCurveParameters, ^{
233      *sBackupKeyCurveParameters = ccec_cp_256();
234  });
235  
236  
237  //
238  // We're expecting full entropy here, so we just need to stretch
239  // via the PBKDF entropy rng. We'll choose a few iterations and no salt
240  // since we don't get sent any.
241  //
242  const int kBackupKeyIterations = 20;
243  const uint8_t sBackupKeySalt[] = { 0 };
244  
245  bool SOSPerformWithDeviceBackupFullKey(ccec_const_cp_t cp, CFDataRef entropy, CFErrorRef *error, void (^operation)(ccec_full_ctx_t fullKey))
246  {
247      bool result = false;
248      ccec_full_ctx_decl_cp(cp, fullKey);
249  
250      require_quiet(SOSGenerateDeviceBackupFullKey(fullKey, cp, entropy, error), exit);
251  
252      operation(fullKey);
253  
254      result = true;
255  exit:
256      ccec_full_ctx_clear_cp(cp, fullKey);
257  
258      return result;
259  }
260  
261  
262  bool SOSGenerateDeviceBackupFullKey(ccec_full_ctx_t generatedKey, ccec_const_cp_t cp, CFDataRef entropy, CFErrorRef* error)
263  {
264      bool result = false;
265      int cc_result = 0;
266  
267  #define drbg_output_size 1024
268  
269      uint8_t drbg_output[drbg_output_size];
270      cc_result = ccpbkdf2_hmac(ccsha256_di(), CFDataGetLength(entropy), CFDataGetBytePtr(entropy),
271                                sizeof(sBackupKeySalt), sBackupKeySalt,
272                                kBackupKeyIterations,
273                                drbg_output_size, drbg_output);
274      require_action_quiet(cc_result == 0, exit,
275                           SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("ccpbkdf2_hmac failed: %d"), cc_result));
276  
277      cc_result = ccec_generate_key_deterministic(cp,
278                                                  drbg_output_size,
279                                                  drbg_output,
280                                                  ccrng(NULL),
281                                                  CCEC_GENKEY_DETERMINISTIC_SECBKP,
282                                                  generatedKey);
283      require_action_quiet(cc_result == 0, exit,
284                           SOSErrorCreate(kSOSErrorProcessingFailure, error, NULL, CFSTR("ccec_generate_key_deterministic failed: %d"), cc_result));
285  
286      result = true;
287  exit:
288      return result;
289  }
290  
291  CFDataRef SOSCopyDeviceBackupPublicKey(CFDataRef entropy, CFErrorRef *error)
292  {
293      CFDataRef result = NULL;
294      CFMutableDataRef publicKeyData = NULL;
295  
296      ccec_full_ctx_decl_cp(SOSGetBackupKeyCurveParameters(), fullKey);
297  
298      require_quiet(SOSGenerateDeviceBackupFullKey(fullKey, SOSGetBackupKeyCurveParameters(), entropy, error), exit);
299  
300      size_t space = ccec_compact_export_size(false, ccec_ctx_pub(fullKey));
301      publicKeyData = CFDataCreateMutableWithScratch(kCFAllocatorDefault, space);
302      require_quiet(SecAllocationError(publicKeyData, error, CFSTR("Mutable data allocation")), exit);
303  
304      ccec_compact_export(false, CFDataGetMutableBytePtr(publicKeyData), fullKey);
305  
306      CFTransferRetained(result, publicKeyData);
307  
308  exit:
309      CFReleaseNull(publicKeyData);
310      return result;
311  }
312  
313  
314  CFDataRef SOSDateCreate(void) {
315      CFDateRef now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
316      size_t bufsiz = der_sizeof_date(now, NULL);
317      uint8_t buf[bufsiz];
318      der_encode_date(now, NULL, buf, buf+bufsiz);
319      CFReleaseNull(now);
320      return CFDataCreate(NULL, buf, bufsiz);
321  }
322  
323  
324  CFDataRef CFDataCreateWithDER(CFAllocatorRef allocator, CFIndex size, uint8_t*(^operation)(size_t size, uint8_t *buffer)) {
325      __block CFMutableDataRef result = NULL;
326      if(!size) return NULL;
327      if((result = CFDataCreateMutableWithScratch(allocator, size)) == NULL) return NULL;
328      uint8_t *ptr = CFDataGetMutableBytePtr(result);
329      uint8_t *derptr = operation(size, ptr);
330      if(derptr == ptr) return result; // most probable case
331      if(!derptr || derptr < ptr) { // DER op failed  - or derptr ended up prior to allocated buffer
332          CFReleaseNull(result);
333      } else if(derptr > ptr) { // This is a possible case where we don't end up using the entire allocated buffer
334          size_t diff = derptr - ptr; // The unused space ends up being the beginning of the allocation
335          CFDataDeleteBytes(result, CFRangeMake(0, diff));
336      }
337      return result;
338  }
339  
340  @implementation SOSCachedNotification
341  + (NSString *)notificationName:(const char *)notificationString {
342  #if TARGET_OS_OSX
343      return [NSString stringWithFormat:@"user.uid.%d.%s", getuid(), notificationString];
344  #else
345      return @(notificationString);
346  #endif
347  }
348  
349  @end
350  
351  bool SOSCachedNotificationOperation(const char *notificationString, bool (^operation) (int token, bool gtg)) {
352      static os_unfair_lock token_lock = OS_UNFAIR_LOCK_INIT;
353      static NSMutableDictionary *tokenCache = NULL;
354      int token = NOTIFY_TOKEN_INVALID;
355  
356      @autoreleasepool {
357          os_unfair_lock_lock(&token_lock);
358          if (tokenCache == NULL) {
359              tokenCache = [NSMutableDictionary dictionary];
360          }
361          NSString *notification = [SOSCachedNotification notificationName:notificationString];
362          if (notification == NULL) {
363              os_unfair_lock_unlock(&token_lock);
364              return false;
365          }
366  
367          NSNumber *cachedToken = tokenCache[notification];
368          if (cachedToken == NULL) {
369              uint32_t status;
370  
371              status = notify_register_check([notification UTF8String], &token);
372              if (status == NOTIFY_STATUS_OK) {
373                  tokenCache[notification] = @(token);
374              } else {
375                  secnotice("cachedStatus", "Failed to retreive token for %@: error %d",
376                            notification, status);
377              }
378          } else {
379              token = [cachedToken intValue];
380          }
381          os_unfair_lock_unlock(&token_lock);
382      }
383  
384      return operation(token, (token != NOTIFY_TOKEN_INVALID));
385  }
386  
387  uint64_t SOSGetCachedCircleBitmask(void) {
388      __block uint64_t retval = 0; // If the following call fails and we return 0 the caller, checking CC_STATISVALID will see we didn't get anything.
389      SOSCachedNotificationOperation(kSOSCCCircleChangedNotification, ^bool(int token, bool gtg) {
390          if(gtg) {
391              notify_get_state(token, &retval);
392          }
393          return false;
394      });
395      return retval;
396  }
397  
398  const SOSCCStatus kSOSNoCachedValue = -99;
399  
400  SOSCCStatus SOSGetCachedCircleStatus(CFErrorRef *error) {
401      uint64_t statusMask = SOSGetCachedCircleBitmask();
402      SOSCCStatus retval = kSOSNoCachedValue;
403  
404      if(statusMask & CC_STATISVALID) {
405          if(statusMask & CC_UKEY_TRUSTED) {
406              retval = (SOSCCStatus) statusMask & CC_MASK;
407          } else {
408              retval = kSOSCCError;
409              if(error) {
410                  CFReleaseNull(*error);
411                  if(statusMask & CC_PEER_IS_IN) {
412                      SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available, this peer is in the circle, but invalid. The iCloud Password must be provided to keychain syncing subsystem to repair this."), NULL, error);
413                  } else {
414                      SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available. The iCloud Password must be provided to keychain syncing subsystem to repair this."), NULL, error);
415                  }
416              }
417          }
418      }
419      return retval;
420  }
421  
422  uint64_t SOSCachedViewBitmask(void) {
423      __block uint64_t retval = 0;
424      if(SOSGetCachedCircleStatus(NULL) == kSOSCCInCircle) {
425          SOSCachedNotificationOperation(kSOSCCViewMembershipChangedNotification, ^bool(int token, bool gtg) {
426              if(gtg) {
427                  notify_get_state(token, &retval);
428                  return true;
429              }
430              return false;
431          });
432      }
433      return retval;
434  }
435  
436  CFSetRef SOSCreateCachedViewStatus(void) {
437      __block CFSetRef retval = NULL;
438      uint64_t state = SOSCachedViewBitmask();
439      if(state) {
440          retval = SOSViewCreateSetFromBitmask(state);
441      }
442      return retval;
443  }
444  
445  
446  NSDate *SOSCreateRandomDateBetweenNowPlus(NSTimeInterval starting, NSTimeInterval ending) {
447      uint64_t randomTime;
448      uint64_t span = ending - starting;
449      NSTimeInterval resultInterval = (span/2) + starting; // fallback in case call below fails.
450      if(SecRandomCopyBytes(NULL, sizeof(randomTime), &randomTime) == 0) {
451          resultInterval = (randomTime % span) + starting;
452      }
453      return [[NSDate alloc] initWithTimeIntervalSinceNow:resultInterval];
454  }
455  
456  
457  dispatch_queue_t SOSCCCredentialQueue(void) {
458      static dispatch_queue_t credQueue = NULL;
459      static dispatch_once_t onceToken;
460      dispatch_once(&onceToken, ^{
461          credQueue = dispatch_queue_create("com.apple.SOSCredentialsQueue", DISPATCH_QUEUE_SERIAL);
462      });
463      return credQueue;
464  }
465  
466  
467  #if TARGET_OS_OSX
468  #define KEYCHAINSYNCDISABLE "DisableKeychainCloudSync"
469  #define ICLOUDMANAGEDENVIRONMENT "com.apple.icloud.managed"
470  #else
471  SOFT_LINK_FRAMEWORK(PrivateFrameworks, ManagedConfiguration)
472  SOFT_LINK_CLASS(ManagedConfiguration, MCProfileConnection)
473  #endif
474  
475  bool SOSVisibleKeychainNotAllowed(void) {
476      bool notAllowed = false;
477  
478  #if TARGET_OS_OSX
479      notAllowed = CFPreferencesGetAppBooleanValue(CFSTR(KEYCHAINSYNCDISABLE), CFSTR(ICLOUDMANAGEDENVIRONMENT), NULL);
480  #else
481      Class mpc = getMCProfileConnectionClass();
482      MCProfileConnection *sharedConnection = [mpc sharedConnection];
483      notAllowed = ![sharedConnection isCloudKeychainSyncAllowed];
484  #endif
485      
486      if(notAllowed) {
487          secnotice("views", "V0 views disabled by Managed Preferences Profile");
488      }
489      return notAllowed;
490  }