2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
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"
34 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
36 // MARK: User Credential management
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
45 bool SOSAccountGenerationSignatureUpdate(SOSAccount* account, CFErrorRef *error) {
47 SecKeyRef priv_key = SOSAccountGetPrivateCredential(account, error);
48 require_quiet(priv_key, bail);
50 [account.trust generationSignatureUpdateWith:account key:priv_key];
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;
63 return identity && SOSFullPeerInfoUpgradeSignatures(identity, privKey, error);
67 void SOSAccountPurgePrivateCredential(SOSAccount* account)
69 secnotice("circleOps", "Purging private account credential");
71 if(account.accountPrivateKey)
73 account.accountPrivateKey = NULL;
75 if(account._password_tmp)
77 account._password_tmp = NULL;
79 if (account.user_private_timer) {
80 dispatch_source_cancel(account.user_private_timer);
81 account.user_private_timer = NULL;
82 xpc_transaction_end();
84 if (account.lock_notification_token != NOTIFY_TOKEN_INVALID) {
85 notify_cancel(account.lock_notification_token);
86 account.lock_notification_token = NOTIFY_TOKEN_INVALID;
91 static void SOSAccountSetTrustedUserPublicKey(SOSAccount* account, bool public_was_trusted, SecKeyRef privKey)
94 SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(privKey);
96 if (account.accountKey && account.accountKeyIsTrusted && CFEqual(publicKey, account.accountKey)) {
97 CFReleaseNull(publicKey);
101 if(public_was_trusted && account.accountKey) {
102 account.previousAccountKey = account.accountKey;
105 account.accountKey = publicKey;
106 account.accountKeyIsTrusted = true;
108 if(!account.previousAccountKey) {
109 account.previousAccountKey = account.accountKey;
112 CFReleaseNull(publicKey);
114 secnotice("circleOps", "trusting new public key: %@", account.accountKey);
115 notify_post(kPublicKeyAvailable);
118 void SOSAccountSetUnTrustedUserPublicKey(SOSAccount* account, SecKeyRef publicKey) {
119 if(account.accountKeyIsTrusted && account.accountKey) {
120 secnotice("circleOps", "Moving : %@ to previousAccountKey", account.accountKey);
121 account.previousAccountKey = account.accountKey;
124 account.accountKey = publicKey;
125 account.accountKeyIsTrusted = false;
127 if(!account.previousAccountKey) {
128 account.previousAccountKey = account.accountKey;
131 secnotice("circleOps", "not trusting new public key: %@", account.accountKey);
135 static void SOSAccountSetPrivateCredential(SOSAccount* account, SecKeyRef private, CFDataRef password) {
137 return SOSAccountPurgePrivateCredential(account);
139 secnotice("circleOps", "setting new private credential");
141 account.accountPrivateKey = private;
144 account._password_tmp = [[NSData alloc] initWithData:(__bridge NSData * _Nonnull)(password)];
146 account._password_tmp = NULL;
149 bool resume_timer = false;
150 if (!account.user_private_timer) {
151 xpc_transaction_begin();
153 account.user_private_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, account.queue);
154 dispatch_source_set_event_handler(account.user_private_timer, ^{
155 secnotice("keygen", "Timing out, purging private account credential");
156 SOSAccountPurgePrivateCredential(account);
158 int lockNotification;
160 notify_register_dispatch(kUserKeybagStateChangeNotification, &lockNotification, account.queue, ^(int token) {
162 CFErrorRef lockCheckError = NULL;
164 if (!SecAKSGetIsLocked(&locked, &lockCheckError)) {
165 secerror("Checking for locked after change failed: %@", lockCheckError);
169 SOSAccountPurgePrivateCredential(account);
172 [account setLock_notification_token:lockNotification];
175 SOSAccountRestartPrivateCredentialTimer(account);
178 dispatch_resume(account.user_private_timer);
181 void SOSAccountRestartPrivateCredentialTimer(SOSAccount* account)
183 if (account.user_private_timer) {
184 // (Re)set the timer's fire time to now + 10 minutes with a 5 second fuzz factor.
185 dispatch_time_t purgeTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * 60 * NSEC_PER_SEC));
186 dispatch_source_set_timer(account.user_private_timer, purgeTime, DISPATCH_TIME_FOREVER, (int64_t)(5 * NSEC_PER_SEC));
190 SecKeyRef SOSAccountGetPrivateCredential(SOSAccount* account, CFErrorRef* error)
192 if (account.accountPrivateKey == NULL) {
193 SOSCreateError(kSOSErrorPrivateKeyAbsent, CFSTR("Private Key not available - failed to prompt user recently"), NULL, error);
195 return account.accountPrivateKey;
198 CFDataRef SOSAccountGetCachedPassword(SOSAccount* account, CFErrorRef* error)
200 if (account._password_tmp == NULL) {
201 secnotice("circleOps", "Password cache expired");
203 return (__bridge CFDataRef)(account._password_tmp);
205 static NSString *SOSUserCredentialAccount = @"SOSUserCredential";
206 static NSString *SOSUserCredentialAccessGroup = @"com.apple.security.sos-usercredential";
208 void SOSAccountStashAccountKey(SOSAccount* account)
211 SecKeyRef user_private = account.accountPrivateKey;
212 NSData *data = CFBridgingRelease(SecKeyCopyExternalRepresentation(user_private, NULL));
217 NSDictionary *attributes = @{
218 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
219 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
220 (__bridge id)kSecAttrIsInvisible : @YES,
221 (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
222 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
223 (__bridge id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore),
224 (__bridge id)kSecValueData : data,
227 status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
229 if (status == errSecDuplicateItem) {
231 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
232 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
233 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
234 (__bridge id)kSecUseDataProtectionKeychain : @YES,
237 status = SecItemUpdate((__bridge CFDictionaryRef)attributes, (__bridge CFDictionaryRef)@{
238 (__bridge id)kSecValueData : data,
239 (__bridge id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore)
243 secnotice("circleOps", "Failed to update user private key to keychain: %d", (int)status);
245 } else if (status != 0) {
246 secnotice("circleOps", "Failed to add user private key to keychain: %d", (int)status);
250 secnotice("circleOps", "Stored user private key stashed local keychain");
256 SecKeyRef SOSAccountCopyStashedUserPrivateKey(SOSAccount* account, CFErrorRef *error)
258 SecKeyRef key = NULL;
259 CFDataRef data = NULL;
262 NSDictionary *attributes = @{
263 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
264 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
265 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
266 (__bridge id)kSecReturnData : @YES,
267 (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
270 status = SecItemCopyMatching((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&data);
272 SecError(status, error, CFSTR("Failed fetching account credential: %d"), (int)status);
276 NSDictionary *keyAttributes = @{
277 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
278 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
282 key = SecKeyCreateWithData(data, (__bridge CFDictionaryRef)keyAttributes, error);
288 SecKeyRef SOSAccountGetTrustedPublicCredential(SOSAccount* account, CFErrorRef* error)
290 if (account.accountKey == NULL || account.accountKeyIsTrusted == false) {
291 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available. The iCloud Password must be provided to the syncing subsystem to repair this."), NULL, error);
294 return account.accountKey;
297 bool SOSAccountHasPublicKey(SOSAccount* account, CFErrorRef* error)
299 return SOSAccountGetTrustedPublicCredential(account, error);
302 static void sosAccountSetTrustedCredentials(SOSAccount* account, CFDataRef user_password, SecKeyRef user_private, bool public_was_trusted) {
303 if(!SOSAccountFullPeerInfoVerify(account, user_private, NULL)){
304 (void) SOSAccountPeerSignatureUpdate(account, user_private, NULL);
306 SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private);
307 SOSAccountSetPrivateCredential(account, user_private, user_password);
308 SOSAccountCheckForAlwaysOnViews(account);
311 static SecKeyRef sosAccountCreateKeyIfPasswordIsCorrect(SOSAccount* account, CFDataRef user_password, CFErrorRef *error) {
312 SecKeyRef user_private = NULL;
313 require_quiet(account.accountKey && account.accountKeyDerivationParamters, errOut);
314 user_private = SOSUserKeygen(user_password, (__bridge CFDataRef)(account.accountKeyDerivationParamters), error);
315 require_quiet(user_private, errOut);
317 require_action_quiet(SOSAccountValidateAccountCredential(account, user_private, error), errOut, CFReleaseNull(user_private));
322 static bool sosAccountValidatePasswordOrFail(SOSAccount* account, CFDataRef user_password, CFErrorRef *error) {
323 SecKeyRef privKey = sosAccountCreateKeyIfPasswordIsCorrect(account, user_password, error);
325 if(account.accountKeyDerivationParamters) debugDumpUserParameters(CFSTR("sosAccountValidatePasswordOrFail"), (__bridge CFDataRef)(account.accountKeyDerivationParamters));
326 SOSCreateError(kSOSErrorWrongPassword, CFSTR("Could not create correct key with password."), NULL, error);
329 sosAccountSetTrustedCredentials(account, user_password, privKey, account.accountKeyIsTrusted);
330 CFReleaseNull(privKey);
334 void SOSAccountSetParameters(SOSAccount* account, CFDataRef parameters) {
335 account.accountKeyDerivationParamters = (__bridge NSData *) parameters;
338 bool SOSAccountValidateAccountCredential(SOSAccount* account, SecKeyRef accountPrivateKey, CFErrorRef *error)
341 SecKeyRef publicCandidate = SecKeyCreatePublicFromPrivate(accountPrivateKey);
343 if(CFEqualSafe(account.accountKey, publicCandidate)) {
346 CFErrorRef localError = NULL;
347 CFStringRef accountHpub = SOSCopyIDOfKey(account.accountKey, NULL);
348 CFStringRef candidateHpub = SOSCopyIDOfKey(publicCandidate, NULL);
349 SOSCreateErrorWithFormat(kSOSErrorWrongPassword, NULL, &localError, NULL, CFSTR("Password generated pubkey doesn't match - candidate: %@ known: %@"), candidateHpub, accountHpub);
350 secnotice("circleop", "%@", localError);
355 CFReleaseNull(localError);
357 CFReleaseNull(accountHpub);
358 CFReleaseNull(candidateHpub);
360 CFReleaseNull(publicCandidate);
364 bool SOSAccountAssertStashedAccountCredential(SOSAccount* account, CFErrorRef *error)
366 SecKeyRef accountPrivateKey = NULL, publicCandidate = NULL;
369 require_action(account.accountKey, fail, SOSCreateError(kSOSErrorWrongPassword, CFSTR("account public key missing, can't check stashed copy"), NULL, error));
370 require_action(account.accountKeyIsTrusted, fail, SOSCreateError(kSOSErrorWrongPassword, CFSTR("public key no not valid, can't check stashed copy"), NULL, error));
372 accountPrivateKey = SOSAccountCopyStashedUserPrivateKey(account, error);
373 require_action_quiet(accountPrivateKey, fail, secnotice("circleOps", "Looked for a stashed private key, didn't find one"));
375 require(SOSAccountValidateAccountCredential(account, accountPrivateKey, error), fail);
377 sosAccountSetTrustedCredentials(account, NULL, accountPrivateKey, true);
381 CFReleaseSafe(publicCandidate);
382 CFReleaseSafe(accountPrivateKey);
389 bool SOSAccountAssertUserCredentials(SOSAccount* account, CFStringRef user_account, CFDataRef user_password, CFErrorRef *error)
391 bool public_was_trusted = account.accountKeyIsTrusted;
392 account.accountKeyIsTrusted = false;
393 SecKeyRef user_private = NULL;
394 CFDataRef parameters = NULL;
396 // if this succeeds, skip to the end. Success will update account.accountKeyIsTrusted by side-effect.
397 require_quiet(!sosAccountValidatePasswordOrFail(account, user_password, error), recordCred);
399 // We may or may not have parameters here.
400 // In any case we tried using them and they didn't match
401 // So forget all that and start again, assume we're the first to push anything useful.
403 if (CFDataGetLength(user_password) > 20) {
404 secwarning("Long password (>20 byte utf8) being used to derive account key – this may be a PET by mistake!!");
407 parameters = SOSUserKeyCreateGenerateParameters(error);
408 require_quiet(user_private = SOSUserKeygen(user_password, parameters, error), errOut);
409 SOSAccountSetParameters(account, parameters);
410 sosAccountSetTrustedCredentials(account, user_password, user_private, public_was_trusted);
412 CFErrorRef publishError = NULL;
413 if (!SOSAccountPublishCloudParameters(account, &publishError)) {
414 secerror("Failed to publish new cloud parameters: %@", publishError);
417 CFReleaseNull(publishError);
419 SOSAccountStashAccountKey(account);
420 SOSAccountSetValue(account, kSOSAccountName, user_account, NULL);
422 CFReleaseNull(parameters);
423 CFReleaseNull(user_private);
424 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountAssertUserCredentials");
425 account.key_interests_need_updating = true;
426 return account.accountKeyIsTrusted;
430 bool SOSAccountTryUserCredentials(SOSAccount* account, CFStringRef user_account, CFDataRef user_password, CFErrorRef *error) {
431 bool success = sosAccountValidatePasswordOrFail(account, user_password, error);
433 SOSAccountStashAccountKey(account);
434 SOSAccountSetValue(account, kSOSAccountName, user_account, NULL);
436 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountTryUserCredentials");
437 account.key_interests_need_updating = true;
441 bool SOSAccountTryUserPrivateKey(SOSAccount* account, SecKeyRef user_private, CFErrorRef *error) {
442 bool retval = SOSAccountValidateAccountCredential(account, user_private, error);
444 secnotice("circleOps", "Failed to accept provided user_private as credential");
447 sosAccountSetTrustedCredentials(account, NULL, user_private, account.accountKeyIsTrusted);
448 SOSAccountStashAccountKey(account);
449 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountTryUserPrivateKey");
450 account.key_interests_need_updating = true;
451 secnotice("circleOps", "Accepted provided user_private as credential");
455 bool SOSAccountRetryUserCredentials(SOSAccount* account) {
456 CFDataRef cachedPassword = SOSAccountGetCachedPassword(account, NULL);
457 if (cachedPassword == NULL)
460 * SOSAccountTryUserCredentials reset the cached password internally,
461 * so we must have a retain of the password over SOSAccountTryUserCredentials().
463 CFRetain(cachedPassword);
464 bool res = SOSAccountTryUserCredentials(account, NULL, cachedPassword, NULL);
465 CFRelease(cachedPassword);