--- /dev/null
+/*
+ * Created by Michael Brouwer on 6/22/12.
+ * Copyright 2012 Apple Inc. All Rights Reserved.
+ */
+
+/*
+ * SOSPeer.c - Implementation of a secure object syncing peer
+ */
+#include <SecureObjectSync/SOSPeer.h>
+#include <SecureObjectSync/SOSEngine.h>
+#include <SecureObjectSync/SOSFullPeerInfo.h>
+#include <SecureObjectSync/SOSPeerInfo.h>
+#include <SecureObjectSync/SOSCoder.h>
+#include <SecureObjectSync/SOSInternal.h>
+#include <utilities/SecCFRelease.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <CommonCrypto/CommonDigestSPI.h>
+#include <utilities/SecCFError.h>
+#include <utilities/SecCFWrappers.h>
+#include <utilities/debugging.h>
+#include <utilities/SecFileLocations.h>
+#include <utilities/der_plist.h>
+#include <utilities/der_plist_internal.h>
+
+#include <utilities/SecDb.h>
+
+#include <securityd/SOSCloudCircleServer.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <stdlib.h>
+
+#include <AssertMacros.h>
+
+//
+//
+//
+static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.peer.error");
+
+static CFMutableDictionaryRef sPersistenceCache = NULL;
+static CFStringRef peerFile = CFSTR("PeerManifestCache.plist");
+
+static CFMutableDictionaryRef SOSPeerGetPersistenceCache(CFStringRef my_id)
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ CFErrorRef localError = NULL;
+ CFMutableDictionaryRef peerDict = NULL;
+ CFDataRef dictAsData = SOSItemGet(kSOSPeerDataLabel, &localError);
+
+ if (dictAsData) {
+ der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef*)&peerDict, &localError,
+ CFDataGetBytePtr(dictAsData),
+ CFDataGetBytePtr(dictAsData) + CFDataGetLength(dictAsData));
+ }
+
+ if (!isDictionary(peerDict)) {
+ CFReleaseNull(peerDict);
+ secnotice("peer", "Error finding persisted peer data %@, using empty", localError);
+ peerDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+ CFReleaseNull(localError);
+ }
+
+ if (CFDictionaryGetValue(peerDict, my_id) != NULL) {
+ CFMutableDictionaryRef mySubDictionary = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ CFDictionaryForEach(peerDict, ^(const void *key, const void *value) {
+ if (!isDictionary(value)) {
+ CFDictionaryAddValue(mySubDictionary, key, value);
+ };
+ });
+
+ CFDictionaryForEach(mySubDictionary, ^(const void *key, const void *value) {
+ CFDictionaryRemoveValue(peerDict, key);
+ });
+
+ CFDictionaryAddValue(peerDict, my_id, mySubDictionary);
+ }
+ sPersistenceCache = peerDict;
+ });
+
+ return sPersistenceCache;
+}
+
+static void SOSPeerFlushPersistenceCache()
+{
+ if (!sPersistenceCache)
+ return;
+
+ CFErrorRef localError = NULL;
+ CFIndex size = der_sizeof_dictionary(sPersistenceCache, &localError);
+ CFMutableDataRef dataToStore = CFDataCreateMutableWithScratch(kCFAllocatorDefault, size);
+
+ if (size == 0) {
+ secerror("Error calculating size of persistence cache: %@", localError);
+ goto fail;
+ }
+
+ uint8_t *der = NULL;
+ if (CFDataGetBytePtr(dataToStore) != (der = der_encode_dictionary(sPersistenceCache, &localError,
+ CFDataGetBytePtr(dataToStore),
+ CFDataGetMutableBytePtr(dataToStore) + CFDataGetLength(dataToStore)))) {
+ secerror("Error flattening peer cache: %@", localError);
+ secerror("ERROR flattening peer cache (%@): size=%zd %@ (%p %p)", sPersistenceCache, size, dataToStore, CFDataGetBytePtr(dataToStore), der);
+ goto fail;
+}
+
+ if (!SOSItemUpdateOrAdd(kSOSPeerDataLabel, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, dataToStore, &localError)) {
+ secerror("Peer cache item save failed: %@", localError);
+ goto fail;
+ }
+
+fail:
+ CFReleaseNull(localError);
+ CFReleaseNull(dataToStore);
+}
+
+void SOSPeerPurge(SOSPeerRef peer) {
+ // TODO: Do we use this or some other end-around for PurgeAll?
+}
+
+void SOSPeerPurgeAllFor(CFStringRef my_id)
+{
+ if (!my_id)
+ return;
+
+ CFMutableDictionaryRef persistenceCache = SOSPeerGetPersistenceCache(my_id);
+
+ CFMutableDictionaryRef myPeerIDs = (CFMutableDictionaryRef) CFDictionaryGetValue(persistenceCache, my_id);
+ if (myPeerIDs)
+ {
+ CFRetainSafe(myPeerIDs);
+
+ CFDictionaryRemoveValue(myPeerIDs, my_id);
+
+ if (isDictionary(myPeerIDs)) {
+ CFDictionaryForEach(myPeerIDs, ^(const void *key, const void *value) {
+ // TODO: Inflate each and purge its keys.
+ });
+ }
+
+ CFReleaseNull(myPeerIDs);
+ }
+}
+
+static bool SOSPeerFindDataFor(CFTypeRef *peerData, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error)
+{
+ CFDictionaryRef table = (CFDictionaryRef) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id), my_id);
+
+ *peerData = isDictionary(table) ? CFDictionaryGetValue(table, peer_id) : NULL;
+
+ return true;
+}
+
+static bool SOSPeerCopyPersistedManifest(SOSManifestRef* manifest, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error)
+{
+ CFTypeRef persistedObject = NULL;
+
+ require(SOSPeerFindDataFor(&persistedObject, my_id, peer_id, error), fail);
+
+ CFDataRef persistedData = NULL;
+
+ if (isData(persistedObject))
+ persistedData = (CFDataRef)persistedObject;
+ else if (isArray(persistedObject) && (CFArrayGetCount((CFArrayRef) persistedObject) == 2))
+ persistedData = CFArrayGetValueAtIndex((CFArrayRef) persistedObject, 1);
+
+ if (isData(persistedData)) {
+ SOSManifestRef createdManifest = SOSManifestCreateWithData(persistedData, error);
+
+ require(createdManifest, fail);
+
+ *manifest = createdManifest;
+}
+
+ return true;
+
+fail:
+ return false;
+}
+
+
+static bool SOSPeerCopyCoderData(CFDataRef *data, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error)
+{
+ CFTypeRef persistedObject = NULL;
+
+ require(SOSPeerFindDataFor(&persistedObject, my_id, peer_id, error), fail);
+
+ CFDataRef persistedData = NULL;
+
+ if (isArray(persistedObject))
+ persistedData = CFArrayGetValueAtIndex((CFArrayRef) persistedObject, 0);
+
+ if (isData(persistedData)) {
+ CFRetainSafe(persistedData);
+ *data = persistedData;
+ }
+
+ return true;
+
+fail:
+ return false;
+}
+
+
+static void SOSPeerPersistData(CFStringRef my_id, CFStringRef peer_id, SOSManifestRef manifest, CFDataRef coderData)
+{
+ CFMutableArrayRef data_array = CFArrayCreateMutableForCFTypes(0);
+ if (coderData) {
+ CFArrayAppendValue(data_array, coderData);
+ } else {
+ CFDataRef nullData = CFDataCreate(kCFAllocatorDefault, NULL, 0);
+ CFArrayAppendValue(data_array, nullData);
+ CFReleaseNull(nullData);
+ }
+
+ if (manifest) {
+ CFArrayAppendValue(data_array, SOSManifestGetData(manifest));
+ }
+
+ CFMutableDictionaryRef mySubDict = (CFMutableDictionaryRef) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id), my_id);
+
+ if (mySubDict == NULL) {
+ mySubDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+ CFDictionaryAddValue(SOSPeerGetPersistenceCache(my_id), my_id, mySubDict);
+ }
+
+ CFDictionarySetValue(mySubDict, peer_id, data_array);
+
+ CFReleaseNull(data_array);
+
+ SOSPeerFlushPersistenceCache();
+}
+
+struct __OpaqueSOSPeer {
+ SOSPeerSendBlock send_block;
+ CFStringRef my_id;
+ CFStringRef peer_id;
+ CFIndex version;
+ SOSManifestRef manifest;
+ CFDataRef manifest_digest;
+ SOSCoderRef coder; // Currently will be used for OTR stuff.
+};
+
+static SOSPeerRef SOSPeerCreate_Internal(CFStringRef myPeerID, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error,
+ SOSPeerSendBlock sendBlock) {
+ SOSPeerRef p = calloc(1, sizeof(struct __OpaqueSOSPeer));
+ p->send_block = sendBlock;
+ p->peer_id = theirPeerID;
+ CFRetainSafe(p->peer_id);
+
+ p->version = version;
+
+ p->my_id = myPeerID;
+ CFRetainSafe(myPeerID);
+
+ require(SOSPeerCopyPersistedManifest(&p->manifest, p->my_id, p->peer_id, error), fail);
+
+ return p;
+
+fail:
+ CFReleaseSafe(p->peer_id);
+ CFReleaseSafe(p->my_id);
+ free(p);
+ return NULL;
+}
+
+
+SOSPeerRef SOSPeerCreate(SOSFullPeerInfoRef myPeerInfo, SOSPeerInfoRef peerInfo,
+ CFErrorRef *error, SOSPeerSendBlock sendBlock) {
+
+ if (myPeerInfo == NULL) {
+ SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without my peer info!"), NULL, error);
+ return NULL;
+ }
+ if (peerInfo == NULL) {
+ SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without their peer info!"), NULL, error);
+ return NULL;
+ }
+
+ SOSPeerRef result = NULL;
+ SOSPeerRef p = SOSPeerCreate_Internal(SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(myPeerInfo)),
+ SOSPeerInfoGetPeerID(peerInfo),
+ SOSPeerInfoGetVersion(peerInfo),
+ error, sendBlock);
+
+ require(p, fail);
+
+ CFDataRef coderData = NULL;
+ CFErrorRef coderError = NULL;
+
+ if (SOSPeerCopyCoderData(&coderData, p->my_id, p->peer_id, &coderError)
+ && coderData && CFDataGetLength(coderData) != 0) {
+ p->coder = SOSCoderCreateFromData(coderData, &coderError);
+ }
+
+ if (p->coder) {
+ secnotice("peer", "Old coder for me: %@ to peer: %@", p->my_id, p->peer_id);
+ } else {
+ secnotice("peer", "New coder for me: %@ to peer: %@ [Got error: %@]", p->my_id, p->peer_id, coderError);
+
+ p->coder = SOSCoderCreate(peerInfo, myPeerInfo, error);
+
+ if (!p->coder) {
+ SOSPeerDispose(p);
+ p = NULL;
+ }
+ }
+
+ CFReleaseNull(coderData);
+ CFReleaseNull(coderError);
+
+ result = p;
+ p = NULL;
+
+fail:
+ CFReleaseNull(p);
+ return result;
+}
+
+SOSPeerRef SOSPeerCreateSimple(CFStringRef peer_id, CFIndex version, CFErrorRef *error,
+ SOSPeerSendBlock sendBlock) {
+ return SOSPeerCreate_Internal(CFSTR("FakeTestID"), peer_id, version, error, sendBlock);
+}
+
+void SOSPeerDispose(SOSPeerRef peer) {
+ CFErrorRef error = NULL;
+ CFDataRef coderData = NULL;
+ if (peer->coder) {
+ coderData = SOSCoderCopyDER(peer->coder, &error);
+ if (coderData == NULL) {
+ secerror("Coder data failed to export (%@), zapping data for me: %@ to peer: %@", error, peer->my_id, peer->peer_id);
+ }
+ CFReleaseNull(error);
+ }
+
+ if (!coderData) {
+ coderData = CFDataCreate(NULL, NULL, 0);
+ }
+
+ SOSPeerPersistData(peer->my_id, peer->peer_id, peer->manifest, coderData);
+
+ CFReleaseNull(coderData);
+ CFReleaseSafe(peer->peer_id);
+ CFReleaseSafe(peer->my_id);
+ if (peer->manifest)
+ SOSManifestDispose(peer->manifest);
+ CFReleaseSafe(peer->manifest_digest);
+ if (peer->coder)
+ SOSCoderDispose(peer->coder);
+
+ free(peer);
+}
+
+SOSPeerCoderStatus SOSPeerHandleMessage(SOSPeerRef peer, SOSEngineRef engine, CFDataRef codedMessage, CFErrorRef *error) {
+ CFMutableDataRef message = NULL;
+ SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned;
+
+ if (peer->coder) {
+ coderStatus = SOSCoderUnwrap(peer->coder, peer->send_block, codedMessage, &message, peer->peer_id, error);
+ } else {
+ message = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, codedMessage);
+ }
+
+ switch(coderStatus) {
+ case kSOSPeerCoderDataReturned: {
+ CFStringRef description = SOSMessageCopyDescription(message);
+ secnotice("peer", "Got message from %@: %@", peer->peer_id, description);
+ CFReleaseSafe(description);
+ coderStatus = (SOSEngineHandleMessage(engine, peer, message, error)) ? coderStatus: kSOSPeerCoderFailure;
+ break;
+ }
+ case kSOSPeerCoderNegotiating: // Sent message already in Unwrap.
+ secnotice("peer", "Negotiating with %@: Got: %@", peer->peer_id, codedMessage);
+ break;
+ case kSOSPeerCoderNegotiationCompleted:
+ if (SOSEngineSyncWithPeer(engine, peer, true, error)) {
+ secnotice("peer", "Negotiating with %@ completed: %@" , peer->peer_id, codedMessage);
+ } else {
+ secerror("Negotiating with %@ completed syncWithPeer: %@ calling syncWithAllPeers" , peer->peer_id, error ? *error : NULL);
+ // Clearing the manifest forces SOSEngineSyncWithPeer(engine, peer, false, error) to send a message no matter what.
+ // This is needed because that's what gets called by SOSPeerStartSync, which is what SOSCCSyncWithAllPeers triggers.
+ SOSPeerSetManifest(peer, NULL, NULL);
+ SOSCCSyncWithAllPeers();
+ coderStatus = kSOSPeerCoderFailure;
+ }
+ break;
+ case kSOSPeerCoderFailure: // Probably restart coder
+ secnotice("peer", "Failed handling message from %@: Got: %@", peer->peer_id, codedMessage);
+ SOSCoderReset(peer->coder);
+ coderStatus = SOSCoderStart(peer->coder, peer->send_block, peer->peer_id, error);
+ break;
+ case kSOSPeerCoderStaleEvent: // We received an event we have already processed in the past.
+ secnotice("peer", "StaleEvent from %@: Got: %@", peer->peer_id, codedMessage);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ CFReleaseNull(message);
+
+ return coderStatus;
+}
+
+SOSPeerCoderStatus SOSPeerStartSync(SOSPeerRef peer, SOSEngineRef engine, CFErrorRef *error) {
+ SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned;
+
+ if (peer->coder) {
+ coderStatus = SOSCoderStart(peer->coder, peer->send_block, peer->peer_id, error);
+ }
+
+ switch(coderStatus) {
+ case kSOSPeerCoderDataReturned: // fallthrough
+ case kSOSPeerCoderNegotiationCompleted: // fallthrough
+ coderStatus = (SOSEngineSyncWithPeer(engine, peer, false, error)) ? coderStatus: kSOSPeerCoderFailure;
+ break;
+ case kSOSPeerCoderNegotiating: // Sent message already in Unwrap.
+ secnotice("peer", "Started sync with %@", peer->peer_id);
+ break;
+ case kSOSPeerCoderFailure: // Probably restart coder
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return coderStatus;
+}
+
+bool SOSPeerSendMessage(SOSPeerRef peer, CFDataRef message, CFErrorRef *error) {
+ CFMutableDataRef codedMessage = NULL;
+ CFStringRef description = SOSMessageCopyDescription(message);
+
+ SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned;
+
+ if (peer->coder) {
+ coderStatus = SOSCoderWrap(peer->coder, message, &codedMessage, peer->peer_id, error);
+ } else {
+ codedMessage = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, message);
+ }
+ bool ok = true;
+ switch(coderStatus) {
+ case kSOSPeerCoderDataReturned:
+ secnotice("peer", "%@ message: %@", peer->peer_id, description);
+ peer->send_block(codedMessage, error);
+ break;
+ case kSOSPeerCoderNegotiating:
+ secnotice("peer", "%@ coder Negotiating - message not sent", peer->peer_id);
+ ok = SOSCreateErrorWithFormat(kSOSCCError, NULL, error, NULL, CFSTR("%@ failed to send message peer still negotiating"), peer->peer_id);
+ break;
+ default: // includes kSOSPeerCoderFailure
+ secerror("%@ coder failure - message not sent %@", peer->peer_id, error ? *error : NULL);
+ ok = false;
+ break;
+ }
+ CFReleaseSafe(description);
+ return ok;
+}
+
+bool SOSPeerCanSendMessage(SOSPeerRef peer) {
+ return (!peer->coder || SOSCoderCanWrap(peer->coder));
+}
+
+CFIndex SOSPeerGetVersion(SOSPeerRef peer) {
+ return peer->version;
+}
+
+CFStringRef SOSPeerGetID(SOSPeerRef peer) {
+ return peer->peer_id;
+}
+
+bool SOSPeersEqual(SOSPeerRef peerA, SOSPeerRef peerB)
+{
+ // Use mainly to see if peerB is actually this device (peerA)
+ return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo;
+}
+
+bool SOSPeerSetManifest(SOSPeerRef peer, SOSManifestRef manifest, CFErrorRef *error __unused) {
+ CFRetainSafe(manifest);
+ CFReleaseSafe(peer->manifest);
+ peer->manifest = manifest;
+
+ CFReleaseNull(peer->manifest_digest);
+ return true;
+}
+
+SOSManifestRef SOSPeerCopyManifest(SOSPeerRef peer, CFErrorRef *error __unused) {
+ if (!peer->manifest) {
+ SecCFCreateError(kSOSPeerHasNoManifest, sErrorDomain, CFSTR("failed to find peer manifest - not yet implemented"), NULL, error);
+ return NULL;
+ }
+
+ CFRetain(peer->manifest);
+ return peer->manifest;
+}
+
+CFDataRef SOSPeerCopyManifestDigest(SOSPeerRef peer, CFErrorRef *error) {
+ if (peer->manifest_digest) {
+ CFRetain(peer->manifest_digest);
+ } else {
+ if (peer->manifest) {
+ CFMutableDataRef data = CFDataCreateMutable(NULL, CC_SHA1_DIGEST_LENGTH);
+ if (data) {
+ CFDataSetLength(data, CC_SHA1_DIGEST_LENGTH);
+ CCDigest(kCCDigestSHA1, SOSManifestGetBytePtr(peer->manifest), (CC_LONG)SOSManifestGetSize(peer->manifest), CFDataGetMutableBytePtr(data));
+ peer->manifest_digest = data;
+ CFRetain(peer->manifest_digest);
+ } else {
+ SecCFCreateError(kSOSPeerDigestFailure, sErrorDomain, CFSTR("failed to create digest"), NULL, error);
+ }
+ } else {
+ SecCFCreateError(kSOSPeerHasNoManifest, sErrorDomain, CFSTR("peer has no manifest, can't create digest"), NULL, error);
+ }
+ }
+
+ return peer->manifest_digest;
+}