X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/c38e3ce98599a410a47dc10253faa4d5830f13b2..427c49bcad63d042b29ada2ac27e3dfc4845c779:/sec/SOSCircle/SecureObjectSync/SOSEngine.c?ds=inline diff --git a/sec/SOSCircle/SecureObjectSync/SOSEngine.c b/sec/SOSCircle/SecureObjectSync/SOSEngine.c new file mode 100644 index 00000000..aab5bffb --- /dev/null +++ b/sec/SOSCircle/SecureObjectSync/SOSEngine.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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(""); + + 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("")); + + return desc; +}