1/* 2 * Created by Michael Brouwer on 6/22/12. 3 * Copyright 2012 Apple Inc. All Rights Reserved. 4 */ 5 6/* 7 * SOSPeer.c - Implementation of a secure object syncing peer 8 */ 9#include <SecureObjectSync/SOSPeer.h> 10#include <SecureObjectSync/SOSEngine.h> 11#include <SecureObjectSync/SOSFullPeerInfo.h> 12#include <SecureObjectSync/SOSPeerInfo.h> 13#include <SecureObjectSync/SOSCoder.h> 14#include <SecureObjectSync/SOSInternal.h> 15#include <utilities/SecCFRelease.h> 16#include <CommonCrypto/CommonDigest.h> 17#include <CommonCrypto/CommonDigestSPI.h> 18#include <utilities/SecCFError.h> 19#include <utilities/SecCFWrappers.h> 20#include <utilities/debugging.h> 21#include <utilities/SecFileLocations.h> 22#include <utilities/der_plist.h> 23#include <utilities/der_plist_internal.h> 24 25#include <utilities/SecDb.h> 26 27#include <securityd/SOSCloudCircleServer.h> 28 29#include <CoreFoundation/CoreFoundation.h> 30 31#include <stdlib.h> 32 33#include <AssertMacros.h> 34 35// 36// 37// 38static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.peer.error"); 39 40static CFMutableDictionaryRef sPersistenceCache = NULL; 41static CFStringRef peerFile = CFSTR("PeerManifestCache.plist"); 42 43static CFMutableDictionaryRef SOSPeerGetPersistenceCache(CFStringRef my_id) 44{ 45 static dispatch_once_t onceToken; 46 dispatch_once(&onceToken, ^{ 47 CFErrorRef localError = NULL; 48 CFMutableDictionaryRef peerDict = NULL; 49 CFDataRef dictAsData = SOSItemGet(kSOSPeerDataLabel, &localError); 50 51 if (dictAsData) { 52 der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef*)&peerDict, &localError, 53 CFDataGetBytePtr(dictAsData), 54 CFDataGetBytePtr(dictAsData) + CFDataGetLength(dictAsData)); 55 } 56 57 if (!isDictionary(peerDict)) { 58 CFReleaseNull(peerDict); 59 secnotice("peer", "Error finding persisted peer data %@, using empty", localError); 60 peerDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 61 CFReleaseNull(localError); 62 } 63 64 if (CFDictionaryGetValue(peerDict, my_id) != NULL) { 65 CFMutableDictionaryRef mySubDictionary = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 66 67 CFDictionaryForEach(peerDict, ^(const void *key, const void *value) { 68 if (!isDictionary(value)) { 69 CFDictionaryAddValue(mySubDictionary, key, value); 70 }; 71 }); 72 73 CFDictionaryForEach(mySubDictionary, ^(const void *key, const void *value) { 74 CFDictionaryRemoveValue(peerDict, key); 75 }); 76 77 CFDictionaryAddValue(peerDict, my_id, mySubDictionary); 78 } 79 sPersistenceCache = peerDict; 80 }); 81 82 return sPersistenceCache; 83} 84 85static void SOSPeerFlushPersistenceCache() 86{ 87 if (!sPersistenceCache) 88 return; 89 90 CFErrorRef localError = NULL; 91 CFIndex size = der_sizeof_dictionary(sPersistenceCache, &localError); 92 CFMutableDataRef dataToStore = CFDataCreateMutableWithScratch(kCFAllocatorDefault, size); 93 94 if (size == 0) { 95 secerror("Error calculating size of persistence cache: %@", localError); 96 goto fail; 97 } 98 99 uint8_t *der = NULL; 100 if (CFDataGetBytePtr(dataToStore) != (der = der_encode_dictionary(sPersistenceCache, &localError, 101 CFDataGetBytePtr(dataToStore), 102 CFDataGetMutableBytePtr(dataToStore) + CFDataGetLength(dataToStore)))) { 103 secerror("Error flattening peer cache: %@", localError); 104 secerror("ERROR flattening peer cache (%@): size=%zd %@ (%p %p)", sPersistenceCache, size, dataToStore, CFDataGetBytePtr(dataToStore), der); 105 goto fail; 106} 107 108 if (!SOSItemUpdateOrAdd(kSOSPeerDataLabel, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, dataToStore, &localError)) { 109 secerror("Peer cache item save failed: %@", localError); 110 goto fail; 111 } 112 113fail: 114 CFReleaseNull(localError); 115 CFReleaseNull(dataToStore); 116} 117 118void SOSPeerPurge(SOSPeerRef peer) { 119 // TODO: Do we use this or some other end-around for PurgeAll? 120} 121 122void SOSPeerPurgeAllFor(CFStringRef my_id) 123{ 124 if (!my_id) 125 return; 126 127 CFMutableDictionaryRef persistenceCache = SOSPeerGetPersistenceCache(my_id); 128 129 CFMutableDictionaryRef myPeerIDs = (CFMutableDictionaryRef) CFDictionaryGetValue(persistenceCache, my_id); 130 if (myPeerIDs) 131 { 132 CFRetainSafe(myPeerIDs); 133 134 CFDictionaryRemoveValue(myPeerIDs, my_id); 135 136 if (isDictionary(myPeerIDs)) { 137 CFDictionaryForEach(myPeerIDs, ^(const void *key, const void *value) { 138 // TODO: Inflate each and purge its keys. 139 }); 140 } 141 142 CFReleaseNull(myPeerIDs); 143 } 144} 145 146static bool SOSPeerFindDataFor(CFTypeRef *peerData, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error) 147{ 148 CFDictionaryRef table = (CFDictionaryRef) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id), my_id); 149 150 *peerData = isDictionary(table) ? CFDictionaryGetValue(table, peer_id) : NULL; 151 152 return true; 153} 154 155static bool SOSPeerCopyPersistedManifest(SOSManifestRef* manifest, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error) 156{ 157 CFTypeRef persistedObject = NULL; 158 159 require(SOSPeerFindDataFor(&persistedObject, my_id, peer_id, error), fail); 160 161 CFDataRef persistedData = NULL; 162 163 if (isData(persistedObject)) 164 persistedData = (CFDataRef)persistedObject; 165 else if (isArray(persistedObject) && (CFArrayGetCount((CFArrayRef) persistedObject) == 2)) 166 persistedData = CFArrayGetValueAtIndex((CFArrayRef) persistedObject, 1); 167 168 if (isData(persistedData)) { 169 SOSManifestRef createdManifest = SOSManifestCreateWithData(persistedData, error); 170 171 require(createdManifest, fail); 172 173 *manifest = createdManifest; 174} 175 176 return true; 177 178fail: 179 return false; 180} 181 182 183static bool SOSPeerCopyCoderData(CFDataRef *data, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error) 184{ 185 CFTypeRef persistedObject = NULL; 186 187 require(SOSPeerFindDataFor(&persistedObject, my_id, peer_id, error), fail); 188 189 CFDataRef persistedData = NULL; 190 191 if (isArray(persistedObject)) 192 persistedData = CFArrayGetValueAtIndex((CFArrayRef) persistedObject, 0); 193 194 if (isData(persistedData)) { 195 CFRetainSafe(persistedData); 196 *data = persistedData; 197 } 198 199 return true; 200 201fail: 202 return false; 203} 204 205 206static void SOSPeerPersistData(CFStringRef my_id, CFStringRef peer_id, SOSManifestRef manifest, CFDataRef coderData) 207{ 208 CFMutableArrayRef data_array = CFArrayCreateMutableForCFTypes(0); 209 if (coderData) { 210 CFArrayAppendValue(data_array, coderData); 211 } else { 212 CFDataRef nullData = CFDataCreate(kCFAllocatorDefault, NULL, 0); 213 CFArrayAppendValue(data_array, nullData); 214 CFReleaseNull(nullData); 215 } 216 217 if (manifest) { 218 CFArrayAppendValue(data_array, SOSManifestGetData(manifest)); 219 } 220 221 CFMutableDictionaryRef mySubDict = (CFMutableDictionaryRef) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id), my_id); 222 223 if (mySubDict == NULL) { 224 mySubDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 225 CFDictionaryAddValue(SOSPeerGetPersistenceCache(my_id), my_id, mySubDict); 226 } 227 228 CFDictionarySetValue(mySubDict, peer_id, data_array); 229 230 CFReleaseNull(data_array); 231 232 SOSPeerFlushPersistenceCache(); 233} 234 235struct __OpaqueSOSPeer { 236 SOSPeerSendBlock send_block; 237 CFStringRef my_id; 238 CFStringRef peer_id; 239 CFIndex version; 240 SOSManifestRef manifest; 241 CFDataRef manifest_digest; 242 SOSCoderRef coder; // Currently will be used for OTR stuff. 243}; 244 245static SOSPeerRef SOSPeerCreate_Internal(CFStringRef myPeerID, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error, 246 SOSPeerSendBlock sendBlock) { 247 SOSPeerRef p = calloc(1, sizeof(struct __OpaqueSOSPeer)); 248 p->send_block = sendBlock; 249 p->peer_id = theirPeerID; 250 CFRetainSafe(p->peer_id); 251 252 p->version = version; 253 254 p->my_id = myPeerID; 255 CFRetainSafe(myPeerID); 256 257 require(SOSPeerCopyPersistedManifest(&p->manifest, p->my_id, p->peer_id, error), fail); 258 259 return p; 260 261fail: 262 CFReleaseSafe(p->peer_id); 263 CFReleaseSafe(p->my_id); 264 free(p); 265 return NULL; 266} 267 268 269SOSPeerRef SOSPeerCreate(SOSFullPeerInfoRef myPeerInfo, SOSPeerInfoRef peerInfo, 270 CFErrorRef *error, SOSPeerSendBlock sendBlock) { 271 272 if (myPeerInfo == NULL) { 273 SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without my peer info!"), NULL, error); 274 return NULL; 275 } 276 if (peerInfo == NULL) { 277 SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without their peer info!"), NULL, error); 278 return NULL; 279 } 280 281 SOSPeerRef result = NULL; 282 SOSPeerRef p = SOSPeerCreate_Internal(SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(myPeerInfo)), 283 SOSPeerInfoGetPeerID(peerInfo), 284 SOSPeerInfoGetVersion(peerInfo), 285 error, sendBlock); 286 287 require(p, fail); 288 289 CFDataRef coderData = NULL; 290 CFErrorRef coderError = NULL; 291 292 if (SOSPeerCopyCoderData(&coderData, p->my_id, p->peer_id, &coderError) 293 && coderData && CFDataGetLength(coderData) != 0) { 294 p->coder = SOSCoderCreateFromData(coderData, &coderError); 295 } 296 297 if (p->coder) { 298 secnotice("peer", "Old coder for me: %@ to peer: %@", p->my_id, p->peer_id); 299 } else { 300 secnotice("peer", "New coder for me: %@ to peer: %@ [Got error: %@]", p->my_id, p->peer_id, coderError); 301 302 p->coder = SOSCoderCreate(peerInfo, myPeerInfo, error); 303 304 if (!p->coder) { 305 SOSPeerDispose(p); 306 p = NULL; 307 } 308 } 309 310 CFReleaseNull(coderData); 311 CFReleaseNull(coderError); 312 313 result = p; 314 p = NULL; 315 316fail: 317 CFReleaseNull(p); 318 return result; 319} 320 321SOSPeerRef SOSPeerCreateSimple(CFStringRef peer_id, CFIndex version, CFErrorRef *error, 322 SOSPeerSendBlock sendBlock) { 323 return SOSPeerCreate_Internal(CFSTR("FakeTestID"), peer_id, version, error, sendBlock); 324} 325 326void SOSPeerDispose(SOSPeerRef peer) { 327 CFErrorRef error = NULL; 328 CFDataRef coderData = NULL; 329 if (peer->coder) { 330 coderData = SOSCoderCopyDER(peer->coder, &error); 331 if (coderData == NULL) { 332 secerror("Coder data failed to export (%@), zapping data for me: %@ to peer: %@", error, peer->my_id, peer->peer_id); 333 } 334 CFReleaseNull(error); 335 } 336 337 if (!coderData) { 338 coderData = CFDataCreate(NULL, NULL, 0); 339 } 340 341 SOSPeerPersistData(peer->my_id, peer->peer_id, peer->manifest, coderData); 342 343 CFReleaseNull(coderData); 344 CFReleaseSafe(peer->peer_id); 345 CFReleaseSafe(peer->my_id); 346 if (peer->manifest) 347 SOSManifestDispose(peer->manifest); 348 CFReleaseSafe(peer->manifest_digest); 349 if (peer->coder) 350 SOSCoderDispose(peer->coder); 351 352 free(peer); 353} 354 355SOSPeerCoderStatus SOSPeerHandleMessage(SOSPeerRef peer, SOSEngineRef engine, CFDataRef codedMessage, CFErrorRef *error) { 356 CFMutableDataRef message = NULL; 357 SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned; 358 359 if (peer->coder) { 360 coderStatus = SOSCoderUnwrap(peer->coder, peer->send_block, codedMessage, &message, peer->peer_id, error); 361 } else { 362 message = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, codedMessage); 363 } 364 365 switch(coderStatus) { 366 case kSOSPeerCoderDataReturned: { 367 CFStringRef description = SOSMessageCopyDescription(message); 368 secnotice("peer", "Got message from %@: %@", peer->peer_id, description); 369 CFReleaseSafe(description); 370 coderStatus = (SOSEngineHandleMessage(engine, peer, message, error)) ? coderStatus: kSOSPeerCoderFailure; 371 break; 372 } 373 case kSOSPeerCoderNegotiating: // Sent message already in Unwrap. 374 secnotice("peer", "Negotiating with %@: Got: %@", peer->peer_id, codedMessage); 375 break; 376 case kSOSPeerCoderNegotiationCompleted: 377 if (SOSEngineSyncWithPeer(engine, peer, true, error)) { 378 secnotice("peer", "Negotiating with %@ completed: %@" , peer->peer_id, codedMessage); 379 } else { 380 secerror("Negotiating with %@ completed syncWithPeer: %@ calling syncWithAllPeers" , peer->peer_id, error ? *error : NULL); 381 // Clearing the manifest forces SOSEngineSyncWithPeer(engine, peer, false, error) to send a message no matter what. 382 // This is needed because that's what gets called by SOSPeerStartSync, which is what SOSCCSyncWithAllPeers triggers. 383 SOSPeerSetManifest(peer, NULL, NULL); 384 SOSCCSyncWithAllPeers(); 385 coderStatus = kSOSPeerCoderFailure; 386 } 387 break; 388 case kSOSPeerCoderFailure: // Probably restart coder 389 secnotice("peer", "Failed handling message from %@: Got: %@", peer->peer_id, codedMessage); 390 SOSCoderReset(peer->coder); 391 coderStatus = SOSCoderStart(peer->coder, peer->send_block, peer->peer_id, error); 392 break; 393 case kSOSPeerCoderStaleEvent: // We received an event we have already processed in the past. 394 secnotice("peer", "StaleEvent from %@: Got: %@", peer->peer_id, codedMessage); 395 break; 396 default: 397 assert(false); 398 break; 399 } 400 401 CFReleaseNull(message); 402 403 return coderStatus; 404} 405 406SOSPeerCoderStatus SOSPeerStartSync(SOSPeerRef peer, SOSEngineRef engine, CFErrorRef *error) { 407 SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned; 408 409 if (peer->coder) { 410 coderStatus = SOSCoderStart(peer->coder, peer->send_block, peer->peer_id, error); 411 } 412 413 switch(coderStatus) { 414 case kSOSPeerCoderDataReturned: // fallthrough 415 case kSOSPeerCoderNegotiationCompleted: // fallthrough 416 coderStatus = (SOSEngineSyncWithPeer(engine, peer, false, error)) ? coderStatus: kSOSPeerCoderFailure; 417 break; 418 case kSOSPeerCoderNegotiating: // Sent message already in Unwrap. 419 secnotice("peer", "Started sync with %@", peer->peer_id); 420 break; 421 case kSOSPeerCoderFailure: // Probably restart coder 422 break; 423 default: 424 assert(false); 425 break; 426 } 427 return coderStatus; 428} 429 430bool SOSPeerSendMessage(SOSPeerRef peer, CFDataRef message, CFErrorRef *error) { 431 CFMutableDataRef codedMessage = NULL; 432 CFStringRef description = SOSMessageCopyDescription(message); 433 434 SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned; 435 436 if (peer->coder) { 437 coderStatus = SOSCoderWrap(peer->coder, message, &codedMessage, peer->peer_id, error); 438 } else { 439 codedMessage = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, message); 440 } 441 bool ok = true; 442 switch(coderStatus) { 443 case kSOSPeerCoderDataReturned: 444 secnotice("peer", "%@ message: %@", peer->peer_id, description); 445 peer->send_block(codedMessage, error); 446 break; 447 case kSOSPeerCoderNegotiating: 448 secnotice("peer", "%@ coder Negotiating - message not sent", peer->peer_id); 449 ok = SOSCreateErrorWithFormat(kSOSCCError, NULL, error, NULL, CFSTR("%@ failed to send message peer still negotiating"), peer->peer_id); 450 break; 451 default: // includes kSOSPeerCoderFailure 452 secerror("%@ coder failure - message not sent %@", peer->peer_id, error ? *error : NULL); 453 ok = false; 454 break; 455 } 456 CFReleaseSafe(description); 457 return ok; 458} 459 460bool SOSPeerCanSendMessage(SOSPeerRef peer) { 461 return (!peer->coder || SOSCoderCanWrap(peer->coder)); 462} 463 464CFIndex SOSPeerGetVersion(SOSPeerRef peer) { 465 return peer->version; 466} 467 468CFStringRef SOSPeerGetID(SOSPeerRef peer) { 469 return peer->peer_id; 470} 471 472bool SOSPeersEqual(SOSPeerRef peerA, SOSPeerRef peerB) 473{ 474 // Use mainly to see if peerB is actually this device (peerA) 475 return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo; 476} 477 478bool SOSPeerSetManifest(SOSPeerRef peer, SOSManifestRef manifest, CFErrorRef *error __unused) { 479 CFRetainSafe(manifest); 480 CFReleaseSafe(peer->manifest); 481 peer->manifest = manifest; 482 483 CFReleaseNull(peer->manifest_digest); 484 return true; 485} 486 487SOSManifestRef SOSPeerCopyManifest(SOSPeerRef peer, CFErrorRef *error __unused) { 488 if (!peer->manifest) { 489 SecCFCreateError(kSOSPeerHasNoManifest, sErrorDomain, CFSTR("failed to find peer manifest - not yet implemented"), NULL, error); 490 return NULL; 491 } 492 493 CFRetain(peer->manifest); 494 return peer->manifest; 495} 496 497CFDataRef SOSPeerCopyManifestDigest(SOSPeerRef peer, CFErrorRef *error) { 498 if (peer->manifest_digest) { 499 CFRetain(peer->manifest_digest); 500 } else { 501 if (peer->manifest) { 502 CFMutableDataRef data = CFDataCreateMutable(NULL, CC_SHA1_DIGEST_LENGTH); 503 if (data) { 504 CFDataSetLength(data, CC_SHA1_DIGEST_LENGTH); 505 CCDigest(kCCDigestSHA1, SOSManifestGetBytePtr(peer->manifest), (CC_LONG)SOSManifestGetSize(peer->manifest), CFDataGetMutableBytePtr(data)); 506 peer->manifest_digest = data; 507 CFRetain(peer->manifest_digest); 508 } else { 509 SecCFCreateError(kSOSPeerDigestFailure, sErrorDomain, CFSTR("failed to create digest"), NULL, error); 510 } 511 } else { 512 SecCFCreateError(kSOSPeerHasNoManifest, sErrorDomain, CFSTR("peer has no manifest, can't create digest"), NULL, error); 513 } 514 } 515 516 return peer->manifest_digest; 517} 518