]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountCredentials.m
Security-59306.11.20.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
33 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
34 //
35 // MARK: User Credential management
36 //
37
38
39 // List of things to do
40 // Update myFullPeerInfo in circle if needed
41 // Fix iCloud Identity if needed
42 // Gen sign if private key changed
43
44 bool SOSAccountGenerationSignatureUpdate(SOSAccount* account, CFErrorRef *error) {
45 bool result = false;
46 SecKeyRef priv_key = SOSAccountGetPrivateCredential(account, error);
47 require_quiet(priv_key, bail);
48
49 [account.trust generationSignatureUpdateWith:account key:priv_key];
50
51 result = true;
52 bail:
53 return result;
54 }
55
56 /* this one is meant to be local - not published over KVS. */
57 static bool SOSAccountPeerSignatureUpdate(SOSAccount* account, SecKeyRef privKey, CFErrorRef *error) {
58 SOSFullPeerInfoRef identity = NULL;
59 SOSAccountTrustClassic *trust = account.trust;
60 identity = trust.fullPeerInfo;
61
62 return identity && SOSFullPeerInfoUpgradeSignatures(identity, privKey, error);
63 }
64
65
66 void SOSAccountPurgePrivateCredential(SOSAccount* account)
67 {
68 secnotice("circleOps", "Purging private account credential");
69
70 if(account.accountPrivateKey)
71 {
72 account.accountPrivateKey = NULL;
73 }
74 if(account._password_tmp)
75 {
76 account._password_tmp = NULL;
77 }
78 if (account.user_private_timer) {
79 dispatch_source_cancel(account.user_private_timer);
80 account.user_private_timer = NULL;
81 xpc_transaction_end();
82 }
83 if (account.lock_notification_token != NOTIFY_TOKEN_INVALID) {
84 notify_cancel(account.lock_notification_token);
85 account.lock_notification_token = NOTIFY_TOKEN_INVALID;
86 }
87 }
88
89
90 static void SOSAccountSetTrustedUserPublicKey(SOSAccount* account, bool public_was_trusted, SecKeyRef privKey)
91 {
92 if (!privKey) return;
93 SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(privKey);
94
95 if (account.accountKey && account.accountKeyIsTrusted && CFEqual(publicKey, account.accountKey)) {
96 CFReleaseNull(publicKey);
97 return;
98 }
99
100 if(public_was_trusted && account.accountKey) {
101 account.previousAccountKey = account.accountKey;
102 }
103
104 account.accountKey = publicKey;
105 account.accountKeyIsTrusted = true;
106
107 if(!account.previousAccountKey) {
108 account.previousAccountKey = account.accountKey;
109 }
110
111 CFReleaseNull(publicKey);
112
113 secnotice("circleOps", "trusting new public key: %@", account.accountKey);
114 notify_post(kPublicKeyAvailable);
115 }
116
117 void SOSAccountSetUnTrustedUserPublicKey(SOSAccount* account, SecKeyRef publicKey) {
118 if(account.accountKeyIsTrusted && account.accountKey) {
119 secnotice("circleOps", "Moving : %@ to previousAccountKey", account.accountKey);
120 account.previousAccountKey = account.accountKey;
121 }
122
123 account.accountKey = publicKey;
124 account.accountKeyIsTrusted = false;
125
126 if(!account.previousAccountKey) {
127 account.previousAccountKey = account.accountKey;
128 }
129
130 secnotice("circleOps", "not trusting new public key: %@", account.accountKey);
131 }
132
133
134 static void SOSAccountSetPrivateCredential(SOSAccount* account, SecKeyRef private, CFDataRef password) {
135 if (!private)
136 return SOSAccountPurgePrivateCredential(account);
137
138 secnotice("circleOps", "setting new private credential");
139
140 account.accountPrivateKey = private;
141
142 if (password) {
143 account._password_tmp = [[NSData alloc] initWithData:(__bridge NSData * _Nonnull)(password)];
144 } else {
145 account._password_tmp = NULL;
146 }
147
148 bool resume_timer = false;
149 if (!account.user_private_timer) {
150 xpc_transaction_begin();
151 resume_timer = true;
152 account.user_private_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, account.queue);
153 dispatch_source_set_event_handler(account.user_private_timer, ^{
154 secnotice("keygen", "Timing out, purging private account credential");
155 SOSAccountPurgePrivateCredential(account);
156 });
157 int lockNotification;
158
159 notify_register_dispatch(kUserKeybagStateChangeNotification, &lockNotification, account.queue, ^(int token) {
160 bool locked = false;
161 CFErrorRef lockCheckError = NULL;
162
163 if (!SecAKSGetIsLocked(&locked, &lockCheckError)) {
164 secerror("Checking for locked after change failed: %@", lockCheckError);
165 }
166
167 if (locked) {
168 SOSAccountPurgePrivateCredential(account);
169 }
170 });
171 [account setLock_notification_token:lockNotification];
172 }
173
174 SOSAccountRestartPrivateCredentialTimer(account);
175
176 if (resume_timer)
177 dispatch_resume(account.user_private_timer);
178 }
179
180 void SOSAccountRestartPrivateCredentialTimer(SOSAccount* account)
181 {
182 if (account.user_private_timer) {
183 // (Re)set the timer's fire time to now + 10 minutes with a 5 second fuzz factor.
184 dispatch_time_t purgeTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * 60 * NSEC_PER_SEC));
185 dispatch_source_set_timer(account.user_private_timer, purgeTime, DISPATCH_TIME_FOREVER, (int64_t)(5 * NSEC_PER_SEC));
186 }
187 }
188
189 SecKeyRef SOSAccountGetPrivateCredential(SOSAccount* account, CFErrorRef* error)
190 {
191 if (account.accountPrivateKey == NULL) {
192 SOSCreateError(kSOSErrorPrivateKeyAbsent, CFSTR("Private Key not available - failed to prompt user recently"), NULL, error);
193 }
194 return account.accountPrivateKey;
195 }
196
197 CFDataRef SOSAccountGetCachedPassword(SOSAccount* account, CFErrorRef* error)
198 {
199 if (account._password_tmp == NULL) {
200 secnotice("circleOps", "Password cache expired");
201 }
202 return (__bridge CFDataRef)(account._password_tmp);
203 }
204 static NSString *SOSUserCredentialAccount = @"SOSUserCredential";
205 static NSString *SOSUserCredentialAccessGroup = @"com.apple.security.sos-usercredential";
206
207 __unused static void SOSAccountDeleteStashedAccountKey(SOSAccount* account)
208 {
209 NSString *dsid = (__bridge NSString *)SOSAccountGetValue(account, kSOSDSIDKey, NULL);
210 if (dsid == NULL)
211 return;
212
213 NSDictionary * attributes = @{
214 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
215 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
216 (__bridge id)kSecAttrServer : dsid,
217 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
218 };
219 (void)SecItemDelete((__bridge CFDictionaryRef)attributes);
220 }
221
222 void SOSAccountStashAccountKey(SOSAccount* account)
223 {
224 OSStatus status;
225 SecKeyRef user_private = account.accountPrivateKey;
226 NSData *data = CFBridgingRelease(SecKeyCopyExternalRepresentation(user_private, NULL));
227 if (data == NULL){
228 return;
229 }
230
231 NSDictionary *attributes = @{
232 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
233 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
234 (__bridge id)kSecAttrIsInvisible : @YES,
235 (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
236 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
237 (__bridge id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore),
238 (__bridge id)kSecValueData : data,
239 };
240
241 status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
242
243 if (status == errSecDuplicateItem) {
244 attributes = @{
245 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
246 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
247 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
248 (__bridge id)kSecUseDataProtectionKeychain : @YES,
249 };
250
251 status = SecItemUpdate((__bridge CFDictionaryRef)attributes, (__bridge CFDictionaryRef)@{
252 (__bridge id)kSecValueData : data,
253 (__bridge id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore)
254 });
255
256 if (status) {
257 secnotice("circleOps", "Failed to update user private key to keychain: %d", (int)status);
258 }
259 } else if (status != 0) {
260 secnotice("circleOps", "Failed to add user private key to keychain: %d", (int)status);
261 }
262
263 if(status == 0) {
264 secnotice("circleOps", "Stored user private key stashed local keychain");
265 }
266
267 return;
268 }
269
270 SecKeyRef SOSAccountCopyStashedUserPrivateKey(SOSAccount* account, CFErrorRef *error)
271 {
272 SecKeyRef key = NULL;
273 CFDataRef data = NULL;
274 OSStatus status;
275
276 NSDictionary *attributes = @{
277 (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword,
278 (__bridge id)kSecAttrAccount : SOSUserCredentialAccount,
279 (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup,
280 (__bridge id)kSecReturnData : @YES,
281 (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
282 };
283
284 status = SecItemCopyMatching((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&data);
285 if (status) {
286 SecError(status, error, CFSTR("Failed fetching account credential: %d"), (int)status);
287 return NULL;
288 }
289
290 NSDictionary *keyAttributes = @{
291 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
292 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
293 };
294
295
296 key = SecKeyCreateWithData(data, (__bridge CFDictionaryRef)keyAttributes, error);
297 CFReleaseNull(data);
298
299 return key;
300 }
301
302 SecKeyRef SOSAccountGetTrustedPublicCredential(SOSAccount* account, CFErrorRef* error)
303 {
304 if (account.accountKey == NULL || account.accountKeyIsTrusted == false) {
305 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available. The iCloud Password must be provided to the syncing subsystem to repair this."), NULL, error);
306 return NULL;
307 }
308 return account.accountKey;
309 }
310
311 bool SOSAccountHasPublicKey(SOSAccount* account, CFErrorRef* error)
312 {
313 return SOSAccountGetTrustedPublicCredential(account, error);
314 }
315
316 static void sosAccountSetTrustedCredentials(SOSAccount* account, CFDataRef user_password, SecKeyRef user_private, bool public_was_trusted) {
317 if(!SOSAccountFullPeerInfoVerify(account, user_private, NULL)){
318 (void) SOSAccountPeerSignatureUpdate(account, user_private, NULL);
319 }
320 SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private);
321 SOSAccountSetPrivateCredential(account, user_private, user_password);
322 SOSAccountCheckForAlwaysOnViews(account);
323 }
324
325 static SecKeyRef sosAccountCreateKeyIfPasswordIsCorrect(SOSAccount* account, CFDataRef user_password, CFErrorRef *error) {
326 SecKeyRef user_private = NULL;
327 require_quiet(account.accountKey && account.accountKeyDerivationParamters, errOut);
328 user_private = SOSUserKeygen(user_password, (__bridge CFDataRef)(account.accountKeyDerivationParamters), error);
329 require_quiet(user_private, errOut);
330
331 require_action_quiet(SOSAccountValidateAccountCredential(account, user_private, error), errOut, CFReleaseNull(user_private));
332 errOut:
333 return user_private;
334 }
335
336 static bool sosAccountValidatePasswordOrFail(SOSAccount* account, CFDataRef user_password, CFErrorRef *error) {
337 SecKeyRef privKey = sosAccountCreateKeyIfPasswordIsCorrect(account, user_password, error);
338 if(!privKey) {
339 if(account.accountKeyDerivationParamters) debugDumpUserParameters(CFSTR("sosAccountValidatePasswordOrFail"), (__bridge CFDataRef)(account.accountKeyDerivationParamters));
340 SOSCreateError(kSOSErrorWrongPassword, CFSTR("Could not create correct key with password."), NULL, error);
341 return false;
342 }
343 sosAccountSetTrustedCredentials(account, user_password, privKey, account.accountKeyIsTrusted);
344 CFReleaseNull(privKey);
345 return true;
346 }
347
348 void SOSAccountSetParameters(SOSAccount* account, CFDataRef parameters) {
349 account.accountKeyDerivationParamters = (__bridge NSData *) parameters;
350 }
351
352 bool SOSAccountValidateAccountCredential(SOSAccount* account, SecKeyRef accountPrivateKey, CFErrorRef *error)
353 {
354 bool res = false;
355 SecKeyRef publicCandidate = SecKeyCreatePublicFromPrivate(accountPrivateKey);
356
357 if(CFEqualSafe(account.accountKey, publicCandidate)) {
358 res = true;
359 } else {
360 CFErrorRef localError = NULL;
361 CFStringRef accountHpub = SOSCopyIDOfKey(account.accountKey, NULL);
362 CFStringRef candidateHpub = SOSCopyIDOfKey(publicCandidate, NULL);
363 SOSCreateErrorWithFormat(kSOSErrorWrongPassword, NULL, &localError, NULL, CFSTR("Password generated pubkey doesn't match - candidate: %@ known: %@"), candidateHpub, accountHpub);
364 secnotice("circleop", "%@", localError);
365 if (error) {
366 *error = localError;
367 localError = NULL;
368 } else {
369 CFReleaseNull(localError);
370 }
371 CFReleaseNull(accountHpub);
372 CFReleaseNull(candidateHpub);
373 }
374 CFReleaseNull(publicCandidate);
375 return res;
376 }
377
378 bool SOSAccountAssertStashedAccountCredential(SOSAccount* account, CFErrorRef *error)
379 {
380 SecKeyRef accountPrivateKey = NULL, publicCandidate = NULL;
381 bool result = false;
382
383 require_action(account.accountKey, fail, SOSCreateError(kSOSErrorWrongPassword, CFSTR("account public key missing, can't check stashed copy"), NULL, error));
384 require_action(account.accountKeyIsTrusted, fail, SOSCreateError(kSOSErrorWrongPassword, CFSTR("public key no not valid, can't check stashed copy"), NULL, error));
385
386 accountPrivateKey = SOSAccountCopyStashedUserPrivateKey(account, error);
387 require_action_quiet(accountPrivateKey, fail, secnotice("circleOps", "Looked for a stashed private key, didn't find one"));
388
389 require(SOSAccountValidateAccountCredential(account, accountPrivateKey, error), fail);
390
391 sosAccountSetTrustedCredentials(account, NULL, accountPrivateKey, true);
392
393 result = true;
394 fail:
395 CFReleaseSafe(publicCandidate);
396 CFReleaseSafe(accountPrivateKey);
397
398 return result;
399 }
400
401
402
403 bool SOSAccountAssertUserCredentials(SOSAccount* account, CFStringRef user_account, CFDataRef user_password, CFErrorRef *error)
404 {
405 bool public_was_trusted = account.accountKeyIsTrusted;
406 account.accountKeyIsTrusted = false;
407 SecKeyRef user_private = NULL;
408 CFDataRef parameters = NULL;
409
410 // if this succeeds, skip to the end. Success will update account.accountKeyIsTrusted by side-effect.
411 require_quiet(!sosAccountValidatePasswordOrFail(account, user_password, error), recordCred);
412
413 // We may or may not have parameters here.
414 // In any case we tried using them and they didn't match
415 // So forget all that and start again, assume we're the first to push anything useful.
416
417 if (CFDataGetLength(user_password) > 20) {
418 secwarning("Long password (>20 byte utf8) being used to derive account key – this may be a PET by mistake!!");
419 }
420
421 parameters = SOSUserKeyCreateGenerateParameters(error);
422 require_quiet(user_private = SOSUserKeygen(user_password, parameters, error), errOut);
423 SOSAccountSetParameters(account, parameters);
424 sosAccountSetTrustedCredentials(account, user_password, user_private, public_was_trusted);
425
426 CFErrorRef publishError = NULL;
427 if (!SOSAccountPublishCloudParameters(account, &publishError)) {
428 secerror("Failed to publish new cloud parameters: %@", publishError);
429 }
430
431 CFReleaseNull(publishError);
432 recordCred:
433 SOSAccountStashAccountKey(account);
434 SOSAccountSetValue(account, kSOSAccountName, user_account, NULL);
435 errOut:
436 CFReleaseNull(parameters);
437 CFReleaseNull(user_private);
438 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountAssertUserCredentials");
439 account.key_interests_need_updating = true;
440 return account.accountKeyIsTrusted;
441 }
442
443
444 bool SOSAccountTryUserCredentials(SOSAccount* account, CFStringRef user_account, CFDataRef user_password, CFErrorRef *error) {
445 bool success = sosAccountValidatePasswordOrFail(account, user_password, error);
446 if(success) {
447 SOSAccountStashAccountKey(account);
448 SOSAccountSetValue(account, kSOSAccountName, user_account, NULL);
449 }
450 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountTryUserCredentials");
451 account.key_interests_need_updating = true;
452 return success;
453 }
454
455 bool SOSAccountTryUserPrivateKey(SOSAccount* account, SecKeyRef user_private, CFErrorRef *error) {
456 bool retval = SOSAccountValidateAccountCredential(account, user_private, error);
457 if(!retval) {
458 secnotice("circleOps", "Failed to accept provided user_private as credential");
459 return retval;
460 }
461 sosAccountSetTrustedCredentials(account, NULL, user_private, account.accountKeyIsTrusted);
462 SOSAccountStashAccountKey(account);
463 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountTryUserPrivateKey");
464 account.key_interests_need_updating = true;
465 secnotice("circleOps", "Accepted provided user_private as credential");
466 return retval;
467 }
468
469 bool SOSAccountRetryUserCredentials(SOSAccount* account) {
470 CFDataRef cachedPassword = SOSAccountGetCachedPassword(account, NULL);
471 if (cachedPassword == NULL)
472 return false;
473 /*
474 * SOSAccountTryUserCredentials reset the cached password internally,
475 * so we must have a retain of the password over SOSAccountTryUserCredentials().
476 */
477 CFRetain(cachedPassword);
478 bool res = SOSAccountTryUserCredentials(account, NULL, cachedPassword, NULL);
479 CFRelease(cachedPassword);
480 return res;
481 }
482
483
484