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 "SOSAccountPriv.h"
27 #include "SOSPeerInfoCollections.h"
28 #include "SOSTransport.h"
30 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
33 // MARK: User Credential management
36 void SOSAccountSetPreviousPublic(SOSAccountRef account
) {
37 CFReleaseNull(account
->previous_public
);
38 account
->previous_public
= account
->user_public
;
39 CFRetain(account
->previous_public
);
42 static void SOSAccountRemoveInvalidApplications(SOSAccountRef account
, SOSCircleRef circle
)
44 CFMutableSetRef peersToRemove
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
45 SOSCircleForEachApplicant(circle
, ^(SOSPeerInfoRef peer
) {
46 if (!SOSPeerInfoApplicationVerify(peer
, account
->user_public
, NULL
))
47 CFSetAddValue(peersToRemove
, peer
);
50 CFSetForEach(peersToRemove
, ^(const void *value
) {
51 SOSPeerInfoRef peer
= (SOSPeerInfoRef
) value
;
53 SOSCircleWithdrawRequest(circle
, peer
, NULL
);
57 // List of things to do
58 // Update myFullPeerInfo in circle if needed
59 // Fix iCloud Identity if needed
60 // Gen sign if private key changed
62 static bool sosAccountUpgradeiCloudIdentity(SOSCircleRef circle
, SecKeyRef privKey
) {
64 SOSFullPeerInfoRef cloud_fpi
= SOSCircleCopyiCloudFullPeerInfoRef(circle
, NULL
);
65 require_quiet(cloud_fpi
!= NULL
, errOut
);
66 require_quiet(SOSFullPeerInfoUpgradeSignatures(cloud_fpi
, privKey
, NULL
), errOut
);
67 retval
= SOSCircleUpdatePeerInfo(circle
, SOSFullPeerInfoGetPeerInfo(cloud_fpi
));
72 static void SOSAccountGenerationSignatureUpdateWith(SOSAccountRef account
, SecKeyRef privKey
) {
73 if (account
->trusted_circle
&& account
->my_identity
) {
74 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle
) {
75 SOSPeerInfoRef myPI
= SOSAccountGetMyPeerInfo(account
);
76 bool iAmPeer
= SOSCircleHasPeer(account
->trusted_circle
, myPI
, NULL
);
77 bool change
= SOSCircleUpdatePeerInfo(circle
, myPI
);
78 if(iAmPeer
&& !SOSCircleVerify(circle
, account
->user_public
, NULL
)) {
79 change
|= sosAccountUpgradeiCloudIdentity(circle
, privKey
);
80 SOSAccountRemoveInvalidApplications(account
, circle
);
81 change
|= SOSCircleGenerationSign(circle
, privKey
, account
->my_identity
, NULL
);
82 account
->departure_code
= kSOSNeverLeftCircle
;
84 SOSFullPeerInfoRef icfpi
= SOSCircleCopyiCloudFullPeerInfoRef(circle
, NULL
);
86 SOSAccountRemoveIncompleteiCloudIdentities(account
, circle
, privKey
, NULL
);
87 change
|= SOSAccountAddiCloudIdentity(account
, circle
, privKey
, NULL
);
92 secnotice("updatingGenSignature", "we changed the circle? %@", change
? CFSTR("YES") : CFSTR("NO"));
98 bool SOSAccountGenerationSignatureUpdate(SOSAccountRef account
, CFErrorRef
*error
) {
100 SecKeyRef priv_key
= SOSAccountGetPrivateCredential(account
, error
);
101 require_quiet(priv_key
, bail
);
103 SOSAccountGenerationSignatureUpdateWith(account
, priv_key
);
110 /* this one is meant to be local - not published over KVS. */
111 static bool SOSAccountPeerSignatureUpdate(SOSAccountRef account
, SecKeyRef privKey
, CFErrorRef
*error
) {
112 return account
->my_identity
&& SOSFullPeerInfoUpgradeSignatures(account
->my_identity
, privKey
, error
);
116 void SOSAccountPurgePrivateCredential(SOSAccountRef account
)
118 CFReleaseNull(account
->_user_private
);
119 CFReleaseNull(account
->_password_tmp
);
120 if (account
->user_private_timer
) {
121 dispatch_source_cancel(account
->user_private_timer
);
122 dispatch_release(account
->user_private_timer
);
123 account
->user_private_timer
= NULL
;
124 xpc_transaction_end();
126 if (account
->lock_notification_token
!= NOTIFY_TOKEN_INVALID
) {
127 notify_cancel(account
->lock_notification_token
);
128 account
->lock_notification_token
= NOTIFY_TOKEN_INVALID
;
133 static void SOSAccountSetTrustedUserPublicKey(SOSAccountRef account
, bool public_was_trusted
, SecKeyRef privKey
)
135 if (!privKey
) return;
136 SecKeyRef publicKey
= SecKeyCreatePublicFromPrivate(privKey
);
138 if (account
->user_public
&& account
->user_public_trusted
&& CFEqual(publicKey
, account
->user_public
)) {
139 CFReleaseNull(publicKey
);
143 if(public_was_trusted
&& account
->user_public
) {
144 CFReleaseNull(account
->previous_public
);
145 account
->previous_public
= account
->user_public
;
146 CFRetain(account
->previous_public
);
149 CFReleaseNull(account
->user_public
);
150 account
->user_public
= publicKey
;
151 account
->user_public_trusted
= true;
153 if(!account
->previous_public
) {
154 account
->previous_public
= account
->user_public
;
155 CFRetain(account
->previous_public
);
158 secnotice("keygen", "trusting new public key: %@", account
->user_public
);
161 void SOSAccountSetUnTrustedUserPublicKey(SOSAccountRef account
, SecKeyRef publicKey
) {
162 if(account
->user_public_trusted
&& account
->user_public
) {
163 secnotice("keygen", "Moving : %@ to previous_public", account
->user_public
);
164 CFRetainAssign(account
->previous_public
, account
->user_public
);
167 CFReleaseNull(account
->user_public
);
168 account
->user_public
= publicKey
;
169 account
->user_public_trusted
= false;
171 if(!account
->previous_public
) {
172 CFRetainAssign(account
->previous_public
, account
->user_public
);
175 secnotice("keygen", "not trusting new public key: %@", account
->user_public
);
176 notify_post(kPublicKeyNotAvailable
);
180 static void SOSAccountSetPrivateCredential(SOSAccountRef account
, SecKeyRef
private, CFDataRef password
) {
182 return SOSAccountPurgePrivateCredential(account
);
185 CFReleaseSafe(account
->_user_private
);
186 account
->_user_private
= private;
187 CFReleaseSafe(account
->_password_tmp
);
188 account
->_password_tmp
= CFDataCreateCopy(kCFAllocatorDefault
, password
);
190 bool resume_timer
= false;
191 if (!account
->user_private_timer
) {
192 xpc_transaction_begin();
194 account
->user_private_timer
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER
, 0, 0, account
->queue
);
195 dispatch_source_set_event_handler(account
->user_private_timer
, ^{
196 SOSAccountPurgePrivateCredential(account
);
199 notify_register_dispatch(kUserKeybagStateChangeNotification
, &account
->lock_notification_token
, account
->queue
, ^(int token
) {
201 CFErrorRef lockCheckError
= NULL
;
203 if (!SecAKSGetIsLocked(&locked
, &lockCheckError
)) {
204 secerror("Checking for locked after change failed: %@", lockCheckError
);
208 SOSAccountPurgePrivateCredential(account
);
213 // (Re)set the timer's fire time to now + 120 seconds with a 5 second fuzz factor.
214 dispatch_time_t purgeTime
= dispatch_time(DISPATCH_TIME_NOW
, (int64_t)(10 * 60 * NSEC_PER_SEC
));
215 dispatch_source_set_timer(account
->user_private_timer
, purgeTime
, DISPATCH_TIME_FOREVER
, (int64_t)(5 * NSEC_PER_SEC
));
217 dispatch_resume(account
->user_private_timer
);
220 SecKeyRef
SOSAccountGetPrivateCredential(SOSAccountRef account
, CFErrorRef
* error
)
222 if (account
->_user_private
== NULL
) {
223 SOSCreateError(kSOSErrorPrivateKeyAbsent
, CFSTR("Private Key not available - failed to prompt user recently"), NULL
, error
);
225 return account
->_user_private
;
228 CFDataRef
SOSAccountGetCachedPassword(SOSAccountRef account
, CFErrorRef
* error
)
230 if (account
->_password_tmp
== NULL
) {
231 secnotice("keygen", "Password cache expired");
233 return account
->_password_tmp
;
236 SecKeyRef
SOSAccountGetTrustedPublicCredential(SOSAccountRef account
, CFErrorRef
* error
)
238 if (account
->user_public
== NULL
|| account
->user_public_trusted
== false) {
239 SOSCreateError(kSOSErrorPublicKeyAbsent
, CFSTR("Public Key not available - failed to register before call"), NULL
, error
);
242 return account
->user_public
;
247 bool SOSAccountHasPublicKey(SOSAccountRef account
, CFErrorRef
* error
)
249 return SOSAccountGetTrustedPublicCredential(account
, error
);
252 static void sosAccountSetTrustedCredentials(SOSAccountRef account
, CFDataRef user_password
, SecKeyRef user_private
, bool public_was_trusted
) {
253 if(!SOSAccountFullPeerInfoVerify(account
, user_private
, NULL
)) {
254 (void) SOSAccountPeerSignatureUpdate(account
, user_private
, NULL
);
256 SOSAccountSetTrustedUserPublicKey(account
, public_was_trusted
, user_private
);
257 SOSAccountSetPrivateCredential(account
, user_private
, user_password
);
258 SOSAccountCheckForAlwaysOnViews(account
);
261 static SecKeyRef
sosAccountCreateKeyIfPasswordIsCorrect(SOSAccountRef account
, CFDataRef user_password
, CFErrorRef
*error
) {
262 SecKeyRef user_private
= NULL
;
263 require_quiet(account
->user_public
&& account
->user_key_parameters
, errOut
);
264 user_private
= SOSUserKeygen(user_password
, account
->user_key_parameters
, error
);
265 require_quiet(user_private
, errOut
);
266 SecKeyRef public_candidate
= SecKeyCreatePublicFromPrivate(user_private
);
267 if(!CFEqualSafe(account
->user_public
, public_candidate
)) {
268 secnotice("keygen", "Public keys don't match: expected: %@, calculated: %@", account
->user_public
, public_candidate
);
269 CFReleaseNull(user_private
);
271 CFReleaseSafe(public_candidate
);
276 static bool sosAccountValidatePasswordOrFail(SOSAccountRef account
, CFDataRef user_password
, CFErrorRef
*error
) {
277 SecKeyRef privKey
= sosAccountCreateKeyIfPasswordIsCorrect(account
, user_password
, error
);
279 if(account
->user_key_parameters
) debugDumpUserParameters(CFSTR("sosAccountValidatePasswordOrFail"), account
->user_key_parameters
);
280 SOSCreateError(kSOSErrorWrongPassword
, CFSTR("Could not create correct key with password."), NULL
, error
);
283 sosAccountSetTrustedCredentials(account
, user_password
, privKey
, account
->user_public_trusted
);
284 CFReleaseNull(privKey
);
288 void SOSAccountSetParameters(SOSAccountRef account
, CFDataRef parameters
) {
289 CFRetainAssign(account
->user_key_parameters
, parameters
);
292 bool SOSAccountAssertUserCredentials(SOSAccountRef account
, CFStringRef user_account __unused
, CFDataRef user_password
, CFErrorRef
*error
)
294 bool public_was_trusted
= account
->user_public_trusted
;
295 account
->user_public_trusted
= false;
296 SecKeyRef user_private
= NULL
;
297 CFDataRef parameters
= NULL
;
299 // if this succeeds, skip to the end. Success will update account->user_public_trusted by side-effect.
300 require_quiet(!sosAccountValidatePasswordOrFail(account
, user_password
, error
), errOut
);
302 // We may or may not have parameters here.
303 // In any case we tried using them and they didn't match
304 // So forget all that and start again, assume we're the first to push anything useful.
306 if (CFDataGetLength(user_password
) > 20) {
307 secwarning("Long password (>20 byte utf8) being used to derive account key – this may be a PET by mistake!!");
310 parameters
= SOSUserKeyCreateGenerateParameters(error
);
311 require_quiet(user_private
= SOSUserKeygen(user_password
, parameters
, error
), errOut
);
312 SOSAccountSetParameters(account
, parameters
);
313 sosAccountSetTrustedCredentials(account
, user_password
, user_private
, public_was_trusted
);
315 CFErrorRef publishError
= NULL
;
316 if (!SOSAccountPublishCloudParameters(account
, &publishError
)) {
317 secerror("Failed to publish new cloud parameters: %@", publishError
);
320 CFReleaseSafe(publishError
);
323 CFReleaseSafe(parameters
);
324 CFReleaseSafe(user_private
);
325 account
->key_interests_need_updating
= true;
326 return account
->user_public_trusted
;
330 bool SOSAccountTryUserCredentials(SOSAccountRef account
, CFStringRef user_account __unused
, CFDataRef user_password
, CFErrorRef
*error
) {
331 bool success
= sosAccountValidatePasswordOrFail(account
, user_password
, error
);
332 account
->key_interests_need_updating
= true;
336 bool SOSAccountRetryUserCredentials(SOSAccountRef account
) {
337 CFDataRef cachedPassword
= SOSAccountGetCachedPassword(account
, NULL
);
338 if (cachedPassword
== NULL
)
341 * SOSAccountTryUserCredentials reset the cached password internally,
342 * so we must have a retain of the password over SOSAccountTryUserCredentials().
344 CFRetain(cachedPassword
);
345 bool res
= SOSAccountTryUserCredentials(account
, NULL
, cachedPassword
, NULL
);
346 CFRelease(cachedPassword
);