#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) {
}
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) {
});
}
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; };
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: 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);
SOSPeerSetManifest(peer, NULL, NULL);
SOSCCSyncWithAllPeers();
coderStatus = kSOSPeerCoderFailure;
}
break;
case kSOSPeerCoderFailure: 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: 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: case kSOSPeerCoderNegotiationCompleted: coderStatus = (SOSEngineSyncWithPeer(engine, peer, false, error)) ? coderStatus: kSOSPeerCoderFailure;
break;
case kSOSPeerCoderNegotiating: secnotice("peer", "Started sync with %@", peer->peer_id);
break;
case kSOSPeerCoderFailure: 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: 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)
{
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;
}