1// 2// SOSAccountUpdate.c 3// sec 4// 5 6#include "SOSAccountPriv.h" 7#include <SecureObjectSync/SOSTransportCircle.h> 8#include <SecureObjectSync/SOSTransport.h> 9#include <SecureObjectSync/SOSPeerInfoCollections.h> 10#include <CKBridge/SOSCloudKeychainClient.h> 11 12static void DifferenceAndCall(CFSetRef old_members, CFSetRef new_members, void (^updatedCircle)(CFSetRef additions, CFSetRef removals)) 13{ 14 CFMutableSetRef additions = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, new_members); 15 CFMutableSetRef removals = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, old_members); 16 17 18 CFSetForEach(old_members, ^(const void * value) { 19 CFSetRemoveValue(additions, value); 20 }); 21 22 CFSetForEach(new_members, ^(const void * value) { 23 CFSetRemoveValue(removals, value); 24 }); 25 26 updatedCircle(additions, removals); 27 28 CFReleaseSafe(additions); 29 CFReleaseSafe(removals); 30} 31 32static void SOSAccountNotifyEngines(SOSAccountRef account, SOSCircleRef new_circle, 33 CFSetRef added_peers, CFSetRef removed_peers, 34 CFSetRef added_applicants, CFSetRef removed_applicants) 35{ 36 SOSPeerInfoRef myPi = SOSAccountGetMyPeerInCircle(account, new_circle, NULL); 37 CFStringRef myPi_id = NULL; 38 CFMutableArrayRef trusted_peer_ids = NULL; 39 CFMutableArrayRef untrusted_peer_ids = NULL; 40 41 if (myPi && SOSCircleHasPeer(new_circle, myPi, NULL)) { 42 myPi_id = SOSPeerInfoGetPeerID(myPi); 43 trusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); 44 untrusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); 45 SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) { 46 CFMutableArrayRef arrayToAddTo = SOSPeerInfoApplicationVerify(peer, account->user_public, NULL) ? trusted_peer_ids : untrusted_peer_ids; 47 CFArrayAppendValue(arrayToAddTo, SOSPeerInfoGetPeerID(peer)); 48 }); 49 } 50 51 CFArrayRef dsNames = account->factory->copy_names(account->factory); 52 CFStringRef dsName = NULL; 53 CFArrayForEachC(dsNames, dsName) { 54 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, dsName, NULL); 55 if (engine) 56 SOSEngineCircleChanged(engine, myPi_id, trusted_peer_ids, untrusted_peer_ids); 57 } 58 CFReleaseSafe(dsNames); 59 CFReleaseNull(trusted_peer_ids); 60 CFReleaseNull(untrusted_peer_ids); 61} 62 63static void SOSAccountNotifyOfChange(SOSAccountRef account, SOSCircleRef oldCircle, SOSCircleRef newCircle) 64{ 65 CFMutableSetRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault); 66 CFMutableSetRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault); 67 68 CFMutableSetRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault); 69 CFMutableSetRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault); 70 71 DifferenceAndCall(old_members, new_members, ^(CFSetRef added_members, CFSetRef removed_members) { 72 DifferenceAndCall(old_applicants, new_applicants, ^(CFSetRef added_applicants, CFSetRef removed_applicants) { 73 SOSAccountNotifyEngines(account, newCircle, added_members, removed_members, added_applicants, removed_applicants); 74 CFArrayForEach(account->change_blocks, ^(const void * notificationBlock) { 75 ((SOSAccountCircleMembershipChangeBlock) notificationBlock)(newCircle, added_members, removed_members, added_applicants, removed_applicants); 76 }); 77 }); 78 }); 79 80 CFReleaseNull(old_applicants); 81 CFReleaseNull(new_applicants); 82 83 CFReleaseNull(old_members); 84 CFReleaseNull(new_members); 85} 86 87void SOSAccountRecordRetiredPeerInCircleNamed(SOSAccountRef account, CFStringRef circleName, SOSPeerInfoRef retiree) 88{ 89 // Replace Peer with RetiredPeer, if were a peer. 90 SOSAccountModifyCircle(account, circleName, NULL, ^(SOSCircleRef circle) { 91 SOSPeerInfoRef me = SOSAccountGetMyPeerInCircleNamed(account, circleName, NULL); 92 if(!me || !SOSCircleHasActivePeer(circle, me, NULL)) return (bool) false; 93 94 bool updated = SOSCircleUpdatePeerInfo(circle, retiree); 95 if (updated) { 96 CFErrorRef cleanupError = NULL; 97 if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retiree, &cleanupError)) 98 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError); 99 CFReleaseSafe(cleanupError); 100 } 101 return updated; 102 }); 103} 104 105static SOSCircleRef SOSAccountCreateCircleFrom(CFStringRef circleName, CFTypeRef value, CFErrorRef *error) { 106 if (value && !isData(value) && !isNull(value)) { 107 secnotice("circleCreat", "Value provided not appropriate for a circle"); 108 CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(value)); 109 SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL, 110 CFSTR("Expected data or NULL got %@"), description); 111 CFReleaseSafe(description); 112 return NULL; 113 } 114 115 SOSCircleRef circle = NULL; 116 if (!value || isNull(value)) { 117 secnotice("circleCreat", "No circle found in data: %@", value); 118 circle = NULL; 119 } else { 120 circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error); 121 if (circle) { 122 CFStringRef name = SOSCircleGetName(circle); 123 if (!CFEqualSafe(name, circleName)) { 124 secnotice("circleCreat", "Expected circle named %@, got %@", circleName, name); 125 SOSCreateErrorWithFormat(kSOSErrorNameMismatch, NULL, error, NULL, 126 CFSTR("Expected circle named %@, got %@"), circleName, name); 127 CFReleaseNull(circle); 128 } 129 } else { 130 secnotice("circleCreat", "SOSCircleCreateFromData returned NULL."); 131 } 132 } 133 return circle; 134} 135 136bool SOSAccountHandleCircleMessage(SOSAccountRef account, 137 CFStringRef circleName, CFDataRef encodedCircleMessage, CFErrorRef *error) { 138 bool success = false; 139 CFErrorRef localError = NULL; 140 SOSCircleRef circle = SOSAccountCreateCircleFrom(circleName, encodedCircleMessage, &localError); 141 if (circle) { 142 success = SOSAccountUpdateCircleFromRemote(account, circle, &localError); 143 CFReleaseSafe(circle); 144 } else { 145 secerror("NULL circle found, ignoring ..."); 146 success = true; // don't pend this NULL thing. 147 } 148 149 if (!success) { 150 if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) { 151 secerror("Incompatible circle found, abandoning membership: %@", circleName); 152 SOSAccountDestroyCirclePeerInfoNamed(account, circleName, NULL); 153 CFDictionarySetValue(account->circles, circleName, kCFNull); 154 } 155 156 if (error) { 157 *error = localError; 158 localError = NULL; 159 } 160 161 } 162 163 CFReleaseNull(localError); 164 165 return success; 166} 167 168bool SOSAccountHandleParametersChange(SOSAccountRef account, CFDataRef parameters, CFErrorRef *error){ 169 170 SecKeyRef newKey = NULL; 171 CFDataRef newParameters = NULL; 172 bool success = false; 173 174 if(SOSAccountRetrieveCloudParameters(account, &newKey, parameters, &newParameters, error)) { 175 if (CFEqualSafe(account->user_public, newKey)) { 176 secnotice("updates", "Got same public key sent our way. Ignoring."); 177 success = true; 178 } else if (CFEqualSafe(account->previous_public, newKey)) { 179 secnotice("updates", "Got previous public key repeated. Ignoring."); 180 success = true; 181 } else { 182 CFReleaseNull(account->user_public); 183 SOSAccountPurgePrivateCredential(account); 184 CFReleaseNull(account->user_key_parameters); 185 186 account->user_public_trusted = false; 187 188 account->user_public = newKey; 189 newKey = NULL; 190 191 account->user_key_parameters = newParameters; 192 newParameters = NULL; 193 194 secnotice("updates", "Got new parameters for public key: %@", account->user_public); 195 debugDumpUserParameters(CFSTR("params"), account->user_key_parameters); 196 197 SOSUpdateKeyInterest(); 198 199 success = true; 200 } 201 } 202 203 CFReleaseNull(newKey); 204 CFReleaseNull(newParameters); 205 206 return success; 207} 208 209static inline bool SOSAccountHasLeft(SOSAccountRef account) { 210 switch(account->departure_code) { 211 case kSOSWithdrewMembership: /* Fallthrough */ 212 case kSOSMembershipRevoked: /* Fallthrough */ 213 case kSOSLeftUntrustedCircle: 214 return true; 215 case kSOSNeverAppliedToCircle: /* Fallthrough */ 216 case kSOSNeverLeftCircle: /* Fallthrough */ 217 default: 218 return false; 219 } 220} 221 222static const char *concordstring[] = { 223 "kSOSConcordanceTrusted", 224 "kSOSConcordanceGenOld", // kSOSErrorReplay 225 "kSOSConcordanceNoUserSig", // kSOSErrorBadSignature 226 "kSOSConcordanceNoUserKey", // kSOSErrorNoKey 227 "kSOSConcordanceNoPeer", // kSOSErrorPeerNotFound 228 "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature 229 "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature 230 "kSOSConcordanceNoPeerSig", 231 "kSOSConcordanceWeSigned", 232}; 233 234bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef prospective_circle, bool writeUpdate, CFErrorRef *error) 235{ 236 bool success = true; 237 bool haveOldCircle = true; 238 const char *local_remote = writeUpdate ? "local": "remote"; 239 240 secnotice("signing", "start:[%s] %@", local_remote, prospective_circle); 241 if (!account->user_public || !account->user_public_trusted) { 242 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error); 243 return false; 244 } 245 246 if (!prospective_circle) { 247 secerror("##### Can't update to a NULL circle ######"); 248 return false; // Can't update one we don't have. 249 } 250 251 CFStringRef newCircleName = SOSCircleGetName(prospective_circle); 252 SOSCircleRef oldCircle = (SOSCircleRef) CFDictionaryGetValue(account->circles, newCircleName); 253 SOSCircleRef emptyCircle = NULL; 254 255 if(oldCircle == NULL) { 256 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle); 257 secerror("##### Can't replace circle - we don't care about %@ ######", prospective_circle); 258 return false; 259 } 260 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) { 261 secdebug("badcircle", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); 262 // We don't know what is in our table, likely it was kCFNull indicating we didn't 263 // understand a circle that came by. We seem to like this one lets make our entry be empty circle 264 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL); 265 oldCircle = emptyCircle; 266 haveOldCircle = false; 267 // And we're paranoid, drop our old peer info if for some reason we didn't before. 268 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL); 269 } 270 271 SOSFullPeerInfoRef me_full = SOSAccountGetMyFullPeerInCircle(account, oldCircle, NULL); 272 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full); 273 274 SOSTransportCircleRef transport = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(prospective_circle)); 275 276 SOSAccountScanForRetired(account, prospective_circle, error); 277 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error); 278 if(!newCircle) return false; 279 280 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) { 281 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it. 282 } 283 284 typedef enum { 285 accept, 286 countersign, 287 leave, 288 revert, 289 ignore 290 } circle_action_t; 291 292 static const char *actionstring[] = { 293 "accept", "countersign", "leave", "revert", "ignore", 294 }; 295 296 circle_action_t circle_action = ignore; 297 enum DepartureReason leave_reason = kSOSNeverLeftCircle; 298 299 SecKeyRef old_circle_key = NULL; 300 if(SOSCircleVerify(oldCircle, account->user_public, NULL)) old_circle_key = account->user_public; 301 else if(account->previous_public && SOSCircleVerify(oldCircle, account->previous_public, NULL)) old_circle_key = account->previous_public; 302 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle; 303 304 SOSConcordanceStatus concstat = 305 SOSCircleConcordanceTrust(oldCircle, newCircle, 306 old_circle_key, account->user_public, 307 me, error); 308 309 CFStringRef concStr = NULL; 310 switch(concstat) { 311 case kSOSConcordanceTrusted: 312 circle_action = countersign; 313 concStr = CFSTR("Trusted"); 314 break; 315 case kSOSConcordanceGenOld: 316 circle_action = userTrustedOldCircle ? revert : ignore; 317 concStr = CFSTR("Generation Old"); 318 break; 319 case kSOSConcordanceBadUserSig: 320 case kSOSConcordanceBadPeerSig: 321 circle_action = userTrustedOldCircle ? revert : accept; 322 concStr = CFSTR("Bad Signature"); 323 break; 324 case kSOSConcordanceNoUserSig: 325 circle_action = userTrustedOldCircle ? revert : accept; 326 concStr = CFSTR("No User Signature"); 327 break; 328 case kSOSConcordanceNoPeerSig: 329 circle_action = accept; // We might like this one eventually but don't countersign. 330 concStr = CFSTR("No trusted peer signature"); 331 secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newCircle); 332 break; 333 case kSOSConcordanceNoPeer: 334 circle_action = leave; 335 leave_reason = kSOSLeftUntrustedCircle; 336 concStr = CFSTR("No trusted peer left"); 337 break; 338 case kSOSConcordanceNoUserKey: 339 secerror("##### No User Public Key Available, this shouldn't ever happen!!!"); 340 abort(); 341 break; 342 default: 343 secerror("##### Bad Error Return from ConcordanceTrust"); 344 abort(); 345 break; 346 } 347 348 secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted"); 349 350 SOSCircleRef circleToPush = NULL; 351 352 if (circle_action == leave) { 353 circle_action = ignore; 354 355 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) { 356 if (sosAccountLeaveCircle(account, newCircle, error)) { 357 account->departure_code = leave_reason; 358 circleToPush = newCircle; 359 circle_action = accept; 360 me = NULL; 361 me_full = NULL; 362 } 363 } 364 else { 365 // We are not in this circle, but we need to update account with it, since we got it from cloud 366 secnotice("updatecircle", "We are not in this circle, but we need to update account with it"); 367 circle_action = accept; 368 } 369 } 370 371 if (circle_action == countersign) { 372 if (me && SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleVerifyPeerSigned(newCircle, me, NULL)) { 373 CFErrorRef signing_error = NULL; 374 375 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) { 376 circleToPush = newCircle; 377 secnotice("signing", "Concurred with: %@", newCircle); 378 } else { 379 secerror("Failed to concurrence sign, error: %@ Old: %@ New: %@", signing_error, oldCircle, newCircle); 380 success = false; 381 } 382 CFReleaseSafe(signing_error); 383 } 384 circle_action = accept; 385 } 386 387 if (circle_action == accept) { 388 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) { 389 // Don't destroy evidence of other code determining reason for leaving. 390 if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked; 391 } 392 393 if (me 394 && SOSCircleHasActivePeer(oldCircle, me, NULL) 395 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts 396 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) { 397 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); 398 SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL); 399 me = NULL; 400 me_full = NULL; 401 } 402 403 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) { 404 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL); 405 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account->user_public, NULL)) { 406 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); 407 SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL); 408 me = NULL; 409 me_full = NULL; 410 } else { 411 SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL); 412 writeUpdate = true; 413 } 414 } 415 416 CFRetain(oldCircle); // About to replace the oldCircle 417 CFDictionarySetValue(account->circles, newCircleName, newCircle); 418 SOSAccountSetPreviousPublic(account); 419 420 secnotice("signing", "%@, Accepting circle: %@", concStr, newCircle); 421 422 if (me_full && account->user_public_trusted 423 && SOSCircleHasApplicant(oldCircle, me, NULL) 424 && SOSCircleCountPeers(newCircle) > 0 425 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) { 426 // We weren't rejected (above would have set me to NULL. 427 // We were applying and we weren't accepted. 428 // Our application is declared lost, let us reapply. 429 430 if (SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL)) 431 writeUpdate = true; 432 } 433 434 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) { 435 SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL); 436 } 437 438 SOSAccountNotifyOfChange(account, oldCircle, newCircle); 439 440 CFReleaseNull(oldCircle); 441 442 if (writeUpdate) 443 circleToPush = newCircle; 444 SOSUpdateKeyInterest(); 445 } 446 447 /* 448 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles 449 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually 450 * are a member of oldCircle - never for an empty circle. 451 */ 452 453 if (circle_action == revert) { 454 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) { 455 secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr, newCircle, oldCircle); 456 circleToPush = oldCircle; 457 } else { 458 secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr, newCircle); 459 } 460 } 461 462 463 if (circleToPush != NULL) { 464 secnotice("circleUpdate", "Pushing:[%s] %@", local_remote, circleToPush); 465 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error); 466 if (circle_data) { 467 success &= SOSTransportCirclePostCircle(transport, SOSCircleGetName(circleToPush), circle_data, error); 468 } else { 469 success = false; 470 } 471 CFReleaseNull(circle_data); 472 473 success = (success && SOSTransportCircleFlushChanges(transport, error)); 474 } 475 476 CFReleaseSafe(newCircle); 477 CFReleaseNull(emptyCircle); 478 return success; 479} 480