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 }