]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountCredentials.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / SecureObjectSync / 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