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