--- /dev/null
+/*
+ * Created by Michael Brouwer on 7/17/12.
+ * Copyright 2012 Apple Inc. All Rights Reserved.
+ */
+
+/*
+ * SOSEngine.c - Implementation of a secure object syncing engine
+ */
+
+#include <SecureObjectSync/SOSEngine.h>
+#include <SecureObjectSync/SOSPeer.h>
+#include <SecureObjectSync/SOSPeerInfo.h>
+#include <corecrypto/ccder.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <utilities/SecCFError.h>
+#include <utilities/SecCFRelease.h>
+#include <utilities/SecCFWrappers.h>
+#include <utilities/der_plist.h>
+#include <utilities/der_plist_internal.h>
+#include <utilities/debugging.h>
+#include <utilities/iCloudKeychainTrace.h>
+#include <AssertMacros.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <SecItemServer.h>
+#include <SecItemPriv.h>
+
+/* DataSource helper macros and functions. */
+
+// TODO: Change to create with DER.
+#define SOSObjectCreateWithPropertyList(dataSource, plist, error) (dataSource->createWithPropertyList(dataSource, plist, error))
+
+#define SOSObjectCopyPropertyList(dataSource, object, error) (dataSource->copyPropertyList(object, error))
+#define SOSObjectCopyDigest(dataSource, object, error) (dataSource->copyDigest(object, error))
+#define SOSObjectCopyPrimaryKey(dataSource, object, error) (dataSource->copyPrimaryKey(object, error))
+#define SOSObjectCopyMergedObject(dataSource, object1, object2, error) (dataSource->copyMergedObject(object1, object2, error))
+
+#define kSOSMaxObjectPerMessage (500)
+
+static CFArrayRef SOSDataSourceCopyObjectArray(SOSDataSourceRef data_source, SOSManifestRef manifest, CFErrorRef *error) {
+ CFMutableArrayRef objects = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
+
+ // Delta sync by only sending a max of kSOSMaxObjectPerMessage objects at a time.
+ SOSManifestRef toSend = NULL;
+ if (SOSManifestGetCount(manifest) > kSOSMaxObjectPerMessage) {
+ toSend = SOSManifestCreateWithBytes(SOSManifestGetBytePtr(manifest), kSOSMaxObjectPerMessage * SOSDigestSize, error);
+ } else {
+ toSend = manifest;
+ CFRetain(toSend);
+ }
+
+ if (!data_source->foreach_object(data_source, toSend, error, ^bool (SOSObjectRef object, CFErrorRef *localError) {
+ CFDictionaryRef plist = SOSObjectCopyPropertyList(data_source, object, localError);
+ if (plist) {
+ CFArrayAppendValue(objects, plist);
+ CFRelease(plist);
+ }
+ return plist;
+ })) {
+ CFReleaseNull(objects);
+ }
+ CFRetainSafe(toSend);
+ return objects;
+}
+
+static CFDataRef SOSDataSourceCopyManifestDigest(SOSDataSourceRef ds, CFErrorRef *error) {
+ CFMutableDataRef manifestDigest = CFDataCreateMutable(0, SOSDigestSize);
+ CFDataSetLength(manifestDigest, SOSDigestSize);
+ if (!ds->get_manifest_digest(ds, CFDataGetMutableBytePtr(manifestDigest), error))
+ CFReleaseNull(manifestDigest);
+
+ return manifestDigest;
+}
+
+static SOSManifestRef SOSDataSourceCopyManifest(SOSDataSourceRef ds, CFErrorRef *error) {
+ return ds->copy_manifest(ds, error);
+}
+
+static void SOSDataSourceRelease(SOSDataSourceRef ds) {
+ ds->release(ds);
+}
+
+
+/* SOSEngine implementation. */
+
+static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.engine.error");
+
+static bool SOSEngineCreateError(CFIndex errorCode, CFStringRef descriptionString, CFErrorRef previousError, CFErrorRef *newError) {
+ SecCFCreateError(errorCode, descriptionString, sErrorDomain, previousError, newError);
+ return true;
+}
+
+struct __OpaqueSOSEngine {
+ SOSDataSourceRef dataSource;
+};
+
+SOSEngineRef SOSEngineCreate(SOSDataSourceRef dataSource, CFErrorRef *error) {
+ SOSEngineRef engine = calloc(1, sizeof(struct __OpaqueSOSEngine));
+ engine->dataSource = dataSource;
+
+ return engine;
+}
+
+void SOSEngineDispose(SOSEngineRef engine) {
+ SOSDataSourceRelease(engine->dataSource);
+ free(engine);
+}
+
+/* SOSEngine. */
+enum SOSMessageType {
+ SOSManifestInvalidMessageType = 0,
+ SOSManifestDigestMessageType = 1,
+ SOSManifestMessageType = 2,
+ SOSManifestDeltaAndObjectsMessageType = 3,
+};
+
+/* H(): SHA1 hash function.
+ M: Manifest of peer p
+ MSG: H(M).
+
+
+ SOSPeerMessage := SEQUENCE {
+ messageType INTEGER (manifestDigest, manifest, manifestDeltaAndObjects)
+ version INTEGER OPTIONAL default v0
+ content ANY defined by messageType
+ }
+
+ ManifestDigest := OCTECT STRING (length 20)
+ Manifest := OCTECT STRING (length 20 * number of entries)
+
+
+ Value := CHOICE {
+ bool Boolean
+ number INTEGER
+ string UTF8String
+ data OCTECT STRING
+ date GENERAL TIME
+ dictionary Object
+ array Array
+ }
+
+ KVPair := SEQUENCE {
+ key UTF8String
+ value Value
+ }
+
+ Array := SEQUENCE of Value
+ Dictionary := SET of KVPair
+
+ Object := SEQUENCE {
+ [0] conflict OCTECT STRING OPTIONAL
+ [1] change OCTECT STRING OPTIONAL
+ object Dictionary
+
+
+ ManifestDeltaAndObjects := SEQUENCE {
+ manfestDigest ManifestDigest
+ removals Manifest
+ additions Manifest
+ addedObjects SEQUENCE of Object
+ }
+
+ manifestDigest content = OCTECT STRING
+ manifest content := OCTECT STRING
+ manifestDeltaAndObjects := SEQUENCE {
+ manfestDigest ManifestDigest
+ }
+
+ */
+
+
+/* ManifestDigest message */
+static size_t der_sizeof_manifest_digest_message(void) {
+ return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ (ccder_sizeof_uint64(SOSManifestDigestMessageType) +
+ ccder_sizeof_raw_octet_string(SOSDigestSize)));
+}
+
+static uint8_t *der_encode_manifest_digest_message(const uint8_t digest[SOSDigestSize], const uint8_t *der, uint8_t *der_end) {
+ return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ ccder_encode_uint64(SOSManifestDigestMessageType, der,
+ ccder_encode_raw_octet_string(SOSDigestSize, digest, der, der_end)));
+}
+
+/* This message is sent to each peer that joins a circle and can also be sent
+ as a form of ACK to confirm that the local peer is in sync with the peer
+ this is beig sent to. */
+CFDataRef SOSEngineCreateManifestDigestMessage(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) {
+ /* TODO: avoid copying the digest here by inlining der_encode_manifest_digest_message(). */
+
+ uint8_t digest[SOSDigestSize];
+ if (!engine->dataSource->get_manifest_digest(engine->dataSource, &digest[0], error)) {
+ return NULL;
+ }
+
+ size_t der_size = der_sizeof_manifest_digest_message();
+ CFMutableDataRef message = CFDataCreateMutable(NULL, der_size);
+ if (message == NULL) {
+ return NULL;
+ }
+ CFDataSetLength(message, der_size);
+ uint8_t *der_end = CFDataGetMutableBytePtr(message);
+ const uint8_t *der = der_end;
+ der_end += der_size;
+
+ der_end = der_encode_manifest_digest_message(digest, der, der_end);
+ assert(der == der_end);
+
+ return message;
+}
+
+
+/* Manifest message */
+static size_t der_sizeof_manifest_message(SOSManifestRef manifest) {
+ return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ (ccder_sizeof_uint64(SOSManifestMessageType) +
+ ccder_sizeof_raw_octet_string(SOSManifestGetSize(manifest))));
+}
+
+static uint8_t *der_encode_manifest_message(SOSManifestRef manifest, const uint8_t *der, uint8_t *der_end) {
+ return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ ccder_encode_uint64(SOSManifestMessageType, der,
+ ccder_encode_raw_octet_string(SOSManifestGetSize(manifest),
+ SOSManifestGetBytePtr(manifest), der, der_end)));
+}
+
+/* This message is sent in response to a manifestDigest if our manifestDigest
+ differs from that of the received manifestDigest, or in response to a
+ manifestAndObjects message if the manifestDigest in the received message
+ doesn't match our own manifestDigest. */
+CFDataRef SOSEngineCreateManifestMessage(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) {
+ SOSManifestRef manifest = SOSDataSourceCopyManifest(engine->dataSource, error);
+ if (!manifest)
+ return NULL;
+
+ size_t der_size = der_sizeof_manifest_message(manifest);
+ CFMutableDataRef message = CFDataCreateMutable(NULL, der_size);
+ CFDataSetLength(message, der_size);
+ uint8_t *der_end = CFDataGetMutableBytePtr(message);
+ const uint8_t *der = der_end;
+ der_end += der_size;
+
+ der_end = der_encode_manifest_message(manifest, der, der_end);
+ assert(der == der_end);
+
+ return message;
+}
+
+
+/* ManifestDeltaAndObjects message */
+static size_t der_sizeof_manifest_and_objects_message(SOSManifestRef removals, SOSManifestRef additions, CFArrayRef objects, CFErrorRef *error) {
+ size_t objects_size = der_sizeof_plist(objects, error);
+ if (objects_size == 0)
+ return objects_size;
+
+ return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ (ccder_sizeof_uint64(SOSManifestDeltaAndObjectsMessageType) +
+ ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ (ccder_sizeof_raw_octet_string(SOSDigestSize) +
+ ccder_sizeof_raw_octet_string(SOSManifestGetSize(removals)) +
+ ccder_sizeof_raw_octet_string(SOSManifestGetSize(additions)) +
+ objects_size))));
+}
+
+static uint8_t *der_encode_manifest_and_objects_message(CFDataRef digest, SOSManifestRef removals, SOSManifestRef additions, CFArrayRef objects, CFErrorRef *error, const uint8_t *der, uint8_t *der_end) {
+ assert(CFDataGetLength(digest) == SOSDigestSize);
+ return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ ccder_encode_uint64(SOSManifestDeltaAndObjectsMessageType, der,
+ ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ ccder_encode_raw_octet_string(SOSDigestSize, CFDataGetBytePtr(digest), der,
+ ccder_encode_raw_octet_string(SOSManifestGetSize(removals), SOSManifestGetBytePtr(removals), der,
+ ccder_encode_raw_octet_string(SOSManifestGetSize(additions), SOSManifestGetBytePtr(additions), der,
+ der_encode_plist(objects, error, der, der_end)))))));
+}
+
+/* This message is sent in response to a local change that needs to be
+ propagated to our peers or in response to a manifest or manifestDigest
+ message from a peer that is not in sync with us yet. */
+CFDataRef SOSEngineCreateManifestAndObjectsMessage(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) {
+ /* Assumptions:
+ peer has a manifest that corresponds to peers real manifest.
+ we send everything in our datasource that's not in peers manifest already to peer.
+ */
+ CFMutableDataRef message = NULL;
+ SOSManifestRef manifest, peerManifest, additions, removals;
+
+retry:
+ manifest = SOSDataSourceCopyManifest(engine->dataSource, error);
+ if (!manifest)
+ goto errOut4;
+
+ peerManifest = SOSPeerCopyManifest(peer, error);
+ if (!peerManifest)
+ goto errOut3;
+
+ if (!SOSManifestDiff(manifest, peerManifest, &additions, &removals, error))
+ goto errOut2;
+
+ CFErrorRef localError = NULL;
+ CFArrayRef objects = SOSDataSourceCopyObjectArray(engine->dataSource, additions, &localError);
+ if (!objects) {
+ if(SecErrorGetOSStatus(localError)==errSecDecode) {
+ secnotice("engine", "Corrupted item found: %@", localError);
+ CFReleaseNull(manifest);
+ CFReleaseNull(additions);
+ CFReleaseNull(removals);
+ CFReleaseNull(peerManifest);
+ CFReleaseNull(localError);
+ goto retry;
+ }
+ if(error && *error==NULL)
+ *error=localError;
+ else
+ CFReleaseNull(localError);
+ goto errOut1;
+ }
+
+ size_t der_size = der_sizeof_manifest_and_objects_message(removals, additions, objects, error);
+ if (der_size == 0)
+ goto errOut0;
+
+ /* TODO: avoid copying the digest here by inlining der_encode_manifest_and_objects_message(). */
+ CFDataRef peerDigest = SOSPeerCopyManifestDigest(peer, error);
+ if (!peerDigest)
+ goto errOut0;
+
+ message = CFDataCreateMutable(NULL, der_size);
+ CFDataSetLength(message, der_size);
+ uint8_t *der_end = CFDataGetMutableBytePtr(message);
+ const uint8_t *der = der_end;
+ der_end += der_size;
+
+ der_end = der_encode_manifest_and_objects_message(peerDigest, removals, additions, objects, error, der, der_end);
+ assert(der == der_end);
+ if (der_end == NULL) {
+ CFReleaseNull(message);
+ goto errOut_;
+ }
+
+ /* Record the peers new manifest assuming that peer will accept all the
+ changes we are about to send them. */
+ SOSPeerSetManifest(peer, manifest, error);
+
+errOut_:
+ CFRelease(peerDigest);
+errOut0:
+ CFRelease(objects);
+errOut1:
+ SOSManifestDispose(removals);
+ SOSManifestDispose(additions);
+errOut2:
+ SOSManifestDispose(peerManifest);
+errOut3:
+ SOSManifestDispose(manifest);
+errOut4:
+
+ return message;
+}
+
+static const uint8_t *der_decode_msg_type(enum SOSMessageType *msg_type,
+ const uint8_t *der,
+ const uint8_t *der_end,
+ CFErrorRef *error) {
+ const uint8_t *body_end;
+ der = ccder_decode_sequence_tl(&body_end, der, der_end);
+ if (!der)
+ return NULL;
+
+ if (body_end != der_end) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Trailing garbage at end of message"), NULL, error);
+ return NULL;
+ }
+
+ uint64_t msgType;
+ der = ccder_decode_uint64(&msgType, der, der_end);
+ if (msgType < 1 || msgType > SOSManifestDeltaAndObjectsMessageType) {
+ SecCFCreateErrorWithFormat(kSOSEngineInvalidMessageError, sErrorDomain,
+ NULL, error, NULL,
+ CFSTR("Bad message type: %llu"), msgType);
+ return NULL;
+ }
+ *msg_type = (enum SOSMessageType)msgType;
+ return der;
+}
+
+static const uint8_t *
+der_decode_manifest_digest(CFDataRef *digest, CFErrorRef *error,
+ const uint8_t *der, const uint8_t *der_end) {
+ require_quiet(der, errOut);
+ size_t len;
+ der = ccder_decode_tl(CCDER_OCTET_STRING, &len, der, der_end);
+ require_action_quiet(der, errOut, SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Failed to find string"), NULL, error));
+ require_action_quiet(len == SOSDigestSize, errOut, SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Invalid digest size"), NULL, error));
+
+ *digest = CFDataCreate(0, der, len);
+ require_action_quiet(*digest, errOut, SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Failed to create digest"), NULL, error));
+
+ der += len;
+ require_action_quiet(der, errOut, CFReleaseNull(*digest); SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Failed to find string"), NULL, error));
+
+ return der;
+
+errOut:
+ return NULL;
+}
+
+static const uint8_t *
+der_decode_manifest(SOSManifestRef *manifest, CFErrorRef *error,
+ const uint8_t *der, const uint8_t *der_end) {
+ if (!der)
+ goto errOut;
+ size_t len;
+ der = ccder_decode_tl(CCDER_OCTET_STRING, &len, der, der_end);
+ if (!der) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Failed to decode manifest"), NULL, error);
+ goto errOut;
+ }
+ if (len % SOSDigestSize != 0) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("manifest not a multiple of digest size"), NULL, error);
+ goto errOut;
+ }
+ *manifest = SOSManifestCreateWithBytes(der, len, error);
+ if (!*manifest)
+ goto errOut;
+
+ return der += len;
+
+errOut:
+ return NULL;
+}
+
+static const uint8_t *
+der_decode_manifest_digest_message(CFDataRef *digest, CFErrorRef *error,
+ const uint8_t *der, const uint8_t *der_end) {
+ der = der_decode_manifest_digest(digest, error, der, der_end);
+ if (der && der != der_end) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Trailing garbage after digest"), NULL, error);
+ CFReleaseNull(*digest);
+ der = NULL;
+ }
+ return der;
+}
+
+static const uint8_t *
+der_decode_manifest_message(SOSManifestRef *manifest, CFErrorRef *error,
+ const uint8_t *der, const uint8_t *der_end) {
+ der = der_decode_manifest(manifest, error, der, der_end);
+ if (der && der != der_end) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Trailing garbage after manifest"), NULL, error);
+ SOSManifestDispose(*manifest);
+ *manifest = NULL;
+ der = NULL;
+ }
+ return der;
+}
+
+static const uint8_t *
+der_decode_manifest_and_objects_message(CFDataRef *peerManifestDigest,
+ SOSManifestRef *removals,
+ SOSManifestRef *additions,
+ CFArrayRef *objects,
+ CFErrorRef *error, const uint8_t *der,
+ const uint8_t *der_end) {
+ const uint8_t *body_end;
+ der = ccder_decode_sequence_tl(&body_end, der, der_end);
+ if (!der) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Failed to decode top level sequence"), NULL, error);
+ goto errOut;
+ }
+
+ if (body_end != der_end) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Trailing garbage at end of message"), NULL, error);
+ goto errOut;
+ }
+
+ der = der_decode_manifest_digest(peerManifestDigest, error, der, der_end);
+ if (!der)
+ goto errOut;
+ der = der_decode_manifest(removals, error, der, der_end);
+ if (!der)
+ goto errOut1;
+ der = der_decode_manifest(additions, error, der, der_end);
+ if (!der)
+ goto errOut2;
+
+ CFPropertyListRef pl;
+ der = der_decode_plist(0, 0, &pl, error, der, der_end);
+ if (!der)
+ goto errOut3;
+
+ if (der != der_end) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("Trailing garbage at end of message body"), NULL, error);
+ goto errOut4;
+ }
+
+ // TODO Check that objects is in fact an array. */
+ if (CFArrayGetTypeID() != CFGetTypeID(pl)) {
+ SOSEngineCreateError(kSOSEngineInvalidMessageError, CFSTR("objects is not an array"), NULL, error);
+ goto errOut4;
+ }
+ *objects = pl;
+
+ return der;
+
+errOut4:
+ CFRelease(pl);
+errOut3:
+ CFRelease(additions);
+errOut2:
+ CFRelease(removals);
+errOut1:
+ CFRelease(peerManifestDigest);
+errOut:
+ return NULL;
+}
+
+
+#if 0
+enum SOSMessageType SOSMessageGetType(CFDataRef message) {
+ const uint8_t *der = CFDataGetBytePtr(message);
+ const uint8_t *der_end = der + CFDataGetLength(message);
+ enum SOSMessageType msg_type;
+ der_decode_msg_type(&msg_type, der, der_end, NULL);
+ if (!der) {
+ return SOSManifestInvalidMessageType;
+ }
+
+ return msg_type;
+}
+#endif
+
+/* H(): SHA1 hash function.
+ M: Manifest of peer p
+ MSG: H(M) */
+static CFDataRef SOSEngineCopyManifestDigestReply(SOSEngineRef engine,
+ SOSPeerRef peer,
+ CFDataRef digest,
+ CFErrorRef *error) {
+ CFDataRef reply = NULL;
+ CFDataRef peerDigest = SOSPeerCopyManifestDigest(peer, NULL);
+ CFDataRef manifestDigest = SOSDataSourceCopyManifestDigest(engine->dataSource, error);
+ if (manifestDigest) {
+ if (CFEqual(manifestDigest, digest)) {
+ /* Our dataSources manifest and that of the peer are equal, we are in sync. */
+ if (peerDigest && CFEqual(peerDigest, digest)) {
+ /* The last known digest we had for peer already matched the digest peer
+ sent us, so this message is redundant, consider it an ack of our last
+ message to peer. */
+ reply = CFDataCreate(kCFAllocatorDefault, NULL, 0);
+ } else {
+ /* Our peer just sent us a manifest digest that matches our own, but the digest
+ we have for the peer (if any) doesn't match that. Peer must have the same
+ manifest we do, so record that. */
+ SOSManifestRef manifest = SOSDataSourceCopyManifest(engine->dataSource, error);
+ if (manifest) {
+ bool ok = SOSPeerSetManifest(peer, manifest, error);
+ SOSManifestDispose(manifest);
+ if (ok) {
+ /* Since we got lucky and happen to have the same digest as our peer, we
+ send back an ack to ensure our peer ends up knowning our manifest as well. */
+ reply = SOSEngineCreateManifestDigestMessage(engine, peer, error);
+ }
+ }
+ }
+ } else if (peerDigest && CFEqual(peerDigest, digest)) {
+ /* We know peer's current manifest is correct (the computed digest
+ matches the passed in one) but peer and our dataSource
+ are not in sync. Send the deltas to peer. */
+ reply = SOSEngineCreateManifestAndObjectsMessage(engine, peer, error);
+ } else {
+ /* Our peer has no digest yet, or the manifestDigest peer just sent
+ us doesn't match the digest of the manifest we think peer has.
+ We need to get peer to tell us their manifest, to do so we sent
+ it ours and hope it responds with deltas. */
+ reply = SOSEngineCreateManifestMessage(engine, peer, error);
+ }
+ CFRelease(manifestDigest);
+ }
+ CFReleaseSafe(peerDigest);
+ return reply;
+}
+
+/* M: Manifest of peer p
+ MSG: M */
+static CFDataRef SOSEngineCopyManifestReply(SOSEngineRef engine, SOSPeerRef peer,
+ SOSManifestRef manifest,
+ CFErrorRef *error) {
+ CFDataRef reply = NULL;
+ // Peer just told us what his manifest was. Let's roll with it.
+ SOSPeerSetManifest(peer, manifest, error);
+ CFDataRef peerManifestDigest = SOSPeerCopyManifestDigest(peer, error);
+ if (peerManifestDigest) {
+ CFDataRef manifestDigest = SOSDataSourceCopyManifestDigest(engine->dataSource, error);
+ if (manifestDigest) {
+ if (CFEqual(peerManifestDigest, manifestDigest)) {
+ /* We're in sync, optionally send peer an ack. */
+ reply = SOSEngineCreateManifestDigestMessage(engine, peer, error);
+ } else {
+ /* Send peer the objects it is missing from our manifest. */
+ reply = SOSEngineCreateManifestAndObjectsMessage(engine, peer, error);
+ }
+ CFRelease(manifestDigest);
+ }
+ CFRelease(peerManifestDigest);
+ }
+ return reply;
+}
+
+static bool SOSEngineProccesObjects(SOSEngineRef engine,
+ SOSPeerRef peer,
+ CFDataRef digest,
+ SOSManifestRef removals,
+ SOSManifestRef additions,
+ CFArrayRef objects,
+ CFErrorRef *error) {
+ __block bool result = true;
+ CFArrayForEach(objects, ^(const void *value) {
+ SOSObjectRef ob = SOSObjectCreateWithPropertyList(engine->dataSource, value, error);
+ if (ob) {
+ SOSMergeResult mr = engine->dataSource->add(engine->dataSource, ob, error);
+ if (!mr) {
+ result = false;
+ // assertion failure, duplicate object added during transaction, that wasn't explicitly listed in removal list.
+ // treat as conflict?
+ // oa = ds->lookup(pkb);
+ // ds->choose_between(oa, ob)
+ // TODO: This is needed is we want to allow conflicts with other circles.
+ SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncFailed, 1);
+ secerror("assertion failure, add failed: %@",
+ error ? *error : (CFErrorRef)CFSTR("error is null"));
+ }
+ CFRelease(ob);
+ }
+ });
+ return result;
+}
+
+/* H(): SHA1 hash function.
+ L: Manifest of local peer.
+ M: Manifest of peer p.
+ M-L: Manifest of entries in M but not in L
+ L-M: Manifest of entries in L but not in M
+ O(M): Objects in manifest M
+ MSG: H(L) || L-M || M-L || O(M-L) */
+static CFDataRef SOSEngineCopyManifestAndObjectsReply(SOSEngineRef engine,
+ SOSPeerRef peer,
+ CFDataRef digest,
+ SOSManifestRef removals,
+ SOSManifestRef additions,
+ CFArrayRef objects,
+ CFErrorRef *error) {
+ CFDataRef reply = NULL;
+ CFMutableDataRef manifestDigest = (CFMutableDataRef)SOSDataSourceCopyManifestDigest(engine->dataSource, error);
+ if (manifestDigest) {
+ SOSManifestRef manifest = SOSDataSourceCopyManifest(engine->dataSource, error);
+
+ /* Always proccess the objects after we snapshot our manifest. */
+ if (!SOSEngineProccesObjects(engine, peer, digest, removals, additions, objects, error)) {
+ secerror("peer: %@ SOSEngineProccesObjects(): %@", SOSPeerGetID(peer), *error);
+ }
+
+ if (CFEqual(manifestDigest, digest)) {
+ SOSManifestRef peerManifest = NULL;
+ if (manifest) {
+ peerManifest = SOSManifestCreateWithPatch(manifest, removals, additions, error);
+ }
+ if (peerManifest) {
+ if (SOSPeerSetManifest(peer, peerManifest, error)) {
+ /* Now proccess the objects. */
+ if (!SOSEngineProccesObjects(engine, peer, digest, removals, additions, objects, error)) {
+ secerror("peer: %@ SOSEngineProccesObjects(): %@", SOSPeerGetID(peer), *error);
+ }
+
+ CFDataRef peerDigest = SOSPeerCopyManifestDigest(peer, error);
+ if (peerDigest) {
+ /* Depending on whether after proccess objects we still have objects that need to be sent back to peer we respond with our digestManifest or with a manifestAndObjectsMessage. */
+ if (engine->dataSource->get_manifest_digest(engine->dataSource, CFDataGetMutableBytePtr(manifestDigest), error)) {
+ if (CFEqual(manifestDigest, peerDigest)) {
+ reply = SOSEngineCreateManifestDigestMessage(engine, peer, error);
+ } else {
+ reply = SOSEngineCreateManifestAndObjectsMessage(engine, peer, error);
+ }
+ }
+ CFRelease(peerDigest);
+ }
+ }
+ CFRelease(peerManifest);
+ } else {
+ secerror("Received peer: %@ sent bad message: %@", SOSPeerGetID(peer), *error);
+ /* We failed to compute peer's digest, let's tell him ours again and hope for a retransmission. */
+ /* TODO: Perhaps this should be sent by the top level whenever an error occurs during parsing. */
+ reply = SOSEngineCreateManifestDigestMessage(engine, peer, error);
+ }
+ } else {
+ /* ds->manifestDigest != msg->manigestDigest => We received deltas
+ against a manifest we don't have respond with our current
+ manifest to get back in sync. */
+ reply = SOSEngineCreateManifestMessage(engine, peer, error);
+ }
+ CFReleaseSafe(manifest);
+ CFRelease(manifestDigest);
+ }
+ return reply;
+}
+
+/* Handle incoming message from peer p. Return false if there was an error, true otherwise. */
+bool SOSEngineHandleMessage(SOSEngineRef engine, SOSPeerRef peer,
+ CFDataRef message, CFErrorRef *error) {
+ CFDataRef reply = NULL;
+ SOSManifestRef oldPeerManifest = SOSPeerCopyManifest(peer, NULL);
+ const uint8_t *der = CFDataGetBytePtr(message);
+ const uint8_t *der_end = der + CFDataGetLength(message);
+ enum SOSMessageType msgType;
+
+ der = der_decode_msg_type(&msgType, der, der_end, error);
+ if (der) switch (msgType) {
+ case SOSManifestDigestMessageType:
+ {
+ CFDataRef digest = NULL; // Make the static analyzer happy by NULL and Release safe
+ der = der_decode_manifest_digest_message(&digest, error, der, der_end);
+ if (der) {
+ reply = SOSEngineCopyManifestDigestReply(engine, peer, digest, error);
+ }
+ CFReleaseSafe(digest);
+ break;
+ }
+ case SOSManifestMessageType:
+ {
+ SOSManifestRef manifest;
+ der = der_decode_manifest_message(&manifest, error, der, der_end);
+ if (der) {
+ reply = SOSEngineCopyManifestReply(engine, peer, manifest, error);
+ SOSManifestDispose(manifest);
+ }
+ break;
+ }
+ case SOSManifestDeltaAndObjectsMessageType:
+ {
+ CFDataRef peerManifestDigest;
+ SOSManifestRef removals;
+ SOSManifestRef additions;
+ CFArrayRef objects;
+ der = der_decode_manifest_and_objects_message(&peerManifestDigest, &removals, &additions, &objects, error, der, der_end);
+ if (der) {
+ reply = SOSEngineCopyManifestAndObjectsReply(engine, peer, peerManifestDigest, removals, additions, objects, error);
+ CFRelease(peerManifestDigest);
+ SOSManifestDispose(removals);
+ SOSManifestDispose(additions);
+ CFRelease(objects);
+ }
+ break;
+ }
+ default:
+ SecCFCreateErrorWithFormat(kSOSEngineInvalidMessageError, sErrorDomain,
+ NULL, error, NULL, CFSTR("Invalid message type %d"), msgType);
+ break;
+ }
+
+ bool ok = reply;
+ if (reply && CFDataGetLength(reply)) {
+ ok = SOSPeerSendMessage(peer, reply, error);
+ if (!ok)
+ SOSPeerSetManifest(peer, oldPeerManifest, NULL);
+ }
+ secnotice("engine", "%@", SOSPeerGetID(peer));
+ CFReleaseSafe(oldPeerManifest);
+ CFReleaseSafe(reply);
+ return ok;
+}
+
+bool SOSEngineSyncWithPeer(SOSEngineRef engine, SOSPeerRef peer, bool force,
+ CFErrorRef *error) {
+ CFDataRef reply = NULL;
+ SOSManifestRef oldPeerManifest = SOSPeerCopyManifest(peer, NULL);
+ bool ok = true;
+ require_quiet(SOSPeerCanSendMessage(peer), exit);
+ CFDataRef peerDigest = SOSPeerCopyManifestDigest(peer, NULL);
+ CFMutableDataRef manifestDigest = CFDataCreateMutable(0, SOSDigestSize);
+ CFDataSetLength(manifestDigest, SOSDigestSize);
+ if (engine->dataSource->get_manifest_digest(engine->dataSource, CFDataGetMutableBytePtr(manifestDigest), error)) {
+ if (peerDigest) {
+ if (CFEqual(peerDigest, manifestDigest)) {
+ /* We are in sync with peer already. */
+ if (force) {
+ /* If we are at the end of the OTR handshake, we have to send
+ something to our peer no matter what to break the symmmetry. */
+ reply = SOSEngineCreateManifestDigestMessage(engine, peer, error);
+ } else {
+ reply = CFDataCreate(kCFAllocatorDefault, NULL, 0);
+ }
+ } else {
+ /* We have have a digest for peer's manifest and it doesn't
+ match our current digest, so send deltas to peer. */
+ reply = SOSEngineCreateManifestAndObjectsMessage(engine, peer, error);
+ }
+ } else {
+ /* We have no digest for peer yet, send our manifest digest to peer,
+ it should respond with it's manifest so we can sync. */
+ reply = SOSEngineCreateManifestDigestMessage(engine, peer, error);
+ }
+ }
+ CFRelease(manifestDigest);
+ CFReleaseSafe(peerDigest);
+
+ ok = ok && reply;
+ if (ok && CFDataGetLength(reply)) {
+ ok = SOSPeerSendMessage(peer, reply, error);
+ if (!ok)
+ SOSPeerSetManifest(peer, oldPeerManifest, NULL);
+ }
+
+exit:
+ secnotice("engine", "%@", SOSPeerGetID(peer));
+ CFReleaseSafe(oldPeerManifest);
+ CFReleaseSafe(reply);
+ return ok;
+}
+
+#if 0
+static void appendObject(CFMutableStringRef desc, CFDictionaryRef object) {
+ __block bool needComma = false;
+ CFDictionaryForEach(object, ^(const void *key, const void *value) {
+ if (needComma)
+ CFStringAppend(desc, CFSTR(","));
+ else
+ needComma = true;
+
+ CFStringAppend(desc, key);
+ CFStringAppend(desc, CFSTR("="));
+ if (CFEqual(CFSTR("data"), key)) {
+ CFStringAppend(desc, CFSTR("<?>"));
+ } else if (isData(value)) {
+ CFStringAppendHexData(desc, value);
+ } else {
+ CFStringAppendFormat(desc, 0, CFSTR("%@"), value);
+ }
+ });
+}
+#endif
+
+static void appendObjects(CFMutableStringRef desc, CFArrayRef objects) {
+ __block bool needComma = false;
+ CFArrayForEach(objects, ^(const void *value) {
+ if (needComma)
+ CFStringAppend(desc, CFSTR(","));
+ else
+ needComma = true;
+
+ SecItemServerAppendItemDescription(desc, value);
+ });
+}
+
+CFStringRef SOSMessageCopyDescription(CFDataRef message) {
+ if (!message)
+ return CFSTR("<NULL>");
+
+ CFMutableStringRef desc = CFStringCreateMutable(0, 0);
+ const uint8_t *der = CFDataGetBytePtr(message);
+ const uint8_t *der_end = der + CFDataGetLength(message);
+ enum SOSMessageType msgType;
+
+ CFStringAppend(desc, CFSTR("<Msg"));
+ der = der_decode_msg_type(&msgType, der, der_end, 0);
+ if (der) switch (msgType) {
+ case SOSManifestDigestMessageType:
+ {
+ CFStringAppend(desc, CFSTR("ManifestDigest digest: "));
+ CFDataRef digest = NULL;
+ der = der_decode_manifest_digest_message(&digest, 0, der, der_end);
+ if (der) {
+ CFStringAppendHexData(desc, digest);
+ }
+ CFReleaseNull(digest);
+
+ break;
+ }
+ case SOSManifestMessageType:
+ {
+ CFStringAppend(desc, CFSTR("Manifest"));
+
+ SOSManifestRef manifest;
+ der = der_decode_manifest_message(&manifest, 0, der, der_end);
+ if (der) {
+ CFStringRef mfdesc = SOSManifestCopyDescription(manifest);
+ if (mfdesc) {
+ CFStringAppendFormat(desc, 0, CFSTR(" manifest: %@"), mfdesc);
+ CFRelease(mfdesc);
+ }
+ SOSManifestDispose(manifest);
+ }
+ break;
+ }
+ case SOSManifestDeltaAndObjectsMessageType:
+ {
+ CFStringAppend(desc, CFSTR("ManifestDeltaAndObjects digest:"));
+
+ CFDataRef peerManifestDigest;
+ SOSManifestRef removals;
+ SOSManifestRef additions;
+ CFArrayRef objects;
+ der = der_decode_manifest_and_objects_message(&peerManifestDigest, &removals, &additions, &objects, 0, der, der_end);
+ if (der) {
+ CFStringAppendHexData(desc, peerManifestDigest);
+ CFStringRef remdesc = SOSManifestCopyDescription(removals);
+ if (remdesc) {
+ CFStringAppendFormat(desc, 0, CFSTR(" removals: %@"), remdesc);
+ CFRelease(remdesc);
+ }
+ CFStringRef adddesc = SOSManifestCopyDescription(additions);
+ if (adddesc) {
+ CFStringAppendFormat(desc, 0, CFSTR(" additions: %@"), adddesc);
+ CFRelease(adddesc);
+ }
+ CFStringAppendFormat(desc, 0, CFSTR(" objects: "));
+ appendObjects(desc, objects);
+
+ CFRelease(peerManifestDigest);
+ SOSManifestDispose(removals);
+ SOSManifestDispose(additions);
+ CFRelease(objects);
+ }
+ break;
+ }
+ default:
+ CFStringAppendFormat(desc, 0, CFSTR("InvalidType: %d"), msgType);
+ break;
+ }
+
+ CFStringAppend(desc, CFSTR(">"));
+
+ return desc;
+}