]> git.saurik.com Git - apple/security.git/blobdiff - sec/SOSCircle/SecureObjectSync/SOSEngine.c
Security-55471.tar.gz
[apple/security.git] / sec / SOSCircle / SecureObjectSync / SOSEngine.c
diff --git a/sec/SOSCircle/SecureObjectSync/SOSEngine.c b/sec/SOSCircle/SecureObjectSync/SOSEngine.c
new file mode 100644 (file)
index 0000000..aab5bff
--- /dev/null
@@ -0,0 +1,932 @@
+/*
+ * 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;
+}