1/*
2 * Created by Michael Brouwer on 6/22/12.
3 * Copyright 2012 Apple Inc. All Rights Reserved.
4 */
5
6/*
7 * SOSPeer.c -  Implementation of a secure object syncing peer
8 */
9#include <SecureObjectSync/SOSPeer.h>
10#include <SecureObjectSync/SOSEngine.h>
11#include <SecureObjectSync/SOSFullPeerInfo.h>
12#include <SecureObjectSync/SOSPeerInfo.h>
13#include <SecureObjectSync/SOSCoder.h>
14#include <SecureObjectSync/SOSInternal.h>
15#include <utilities/SecCFRelease.h>
16#include <CommonCrypto/CommonDigest.h>
17#include <CommonCrypto/CommonDigestSPI.h>
18#include <utilities/SecCFError.h>
19#include <utilities/SecCFWrappers.h>
20#include <utilities/debugging.h>
21#include <utilities/SecFileLocations.h>
22#include <utilities/der_plist.h>
23#include <utilities/der_plist_internal.h>
24
25#include <utilities/SecDb.h>
26
27#include <securityd/SOSCloudCircleServer.h>
28
29#include <CoreFoundation/CoreFoundation.h>
30
31#include <stdlib.h>
32
33#include <AssertMacros.h>
34
35//
36//
37//
38static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.peer.error");
39
40static CFMutableDictionaryRef sPersistenceCache = NULL;
41static CFStringRef peerFile = CFSTR("PeerManifestCache.plist");
42
43static CFMutableDictionaryRef SOSPeerGetPersistenceCache(CFStringRef my_id)
44{
45    static dispatch_once_t onceToken;
46    dispatch_once(&onceToken, ^{
47        CFErrorRef localError = NULL;
48        CFMutableDictionaryRef peerDict = NULL;
49        CFDataRef dictAsData = SOSItemGet(kSOSPeerDataLabel, &localError);
50
51        if (dictAsData) {
52            der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef*)&peerDict, &localError,
53                                  CFDataGetBytePtr(dictAsData),
54                                  CFDataGetBytePtr(dictAsData) + CFDataGetLength(dictAsData));
55        }
56
57        if (!isDictionary(peerDict)) {
58            CFReleaseNull(peerDict);
59            secnotice("peer", "Error finding persisted peer data %@, using empty", localError);
60            peerDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
61            CFReleaseNull(localError);
62        }
63
64        if (CFDictionaryGetValue(peerDict, my_id) != NULL) {
65            CFMutableDictionaryRef mySubDictionary = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
66
67            CFDictionaryForEach(peerDict, ^(const void *key, const void *value) {
68                if (!isDictionary(value)) {
69                    CFDictionaryAddValue(mySubDictionary, key, value);
70                };
71            });
72
73            CFDictionaryForEach(mySubDictionary, ^(const void *key, const void *value) {
74                CFDictionaryRemoveValue(peerDict, key);
75            });
76
77            CFDictionaryAddValue(peerDict, my_id, mySubDictionary);
78        }
79        sPersistenceCache = peerDict;
80    });
81
82    return sPersistenceCache;
83}
84
85static void SOSPeerFlushPersistenceCache()
86{
87    if (!sPersistenceCache)
88        return;
89
90    CFErrorRef localError = NULL;
91    CFIndex size = der_sizeof_dictionary(sPersistenceCache, &localError);
92    CFMutableDataRef dataToStore = CFDataCreateMutableWithScratch(kCFAllocatorDefault, size);
93
94    if (size == 0) {
95        secerror("Error calculating size of persistence cache: %@", localError);
96        goto fail;
97    }
98
99    uint8_t *der = NULL;
100    if (CFDataGetBytePtr(dataToStore) != (der = der_encode_dictionary(sPersistenceCache, &localError,
101					                                                  CFDataGetBytePtr(dataToStore),
102                                                                      CFDataGetMutableBytePtr(dataToStore) + CFDataGetLength(dataToStore)))) {
103        secerror("Error flattening peer cache: %@", localError);
104        secerror("ERROR flattening peer cache (%@): size=%zd %@ (%p %p)", sPersistenceCache, size, dataToStore, CFDataGetBytePtr(dataToStore), der);
105        goto fail;
106}
107
108    if (!SOSItemUpdateOrAdd(kSOSPeerDataLabel, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, dataToStore, &localError)) {
109        secerror("Peer cache item save failed: %@", localError);
110        goto fail;
111    }
112
113fail:
114    CFReleaseNull(localError);
115    CFReleaseNull(dataToStore);
116}
117
118void SOSPeerPurge(SOSPeerRef peer) {
119    // TODO: Do we use this or some other end-around for PurgeAll?
120}
121
122void SOSPeerPurgeAllFor(CFStringRef my_id)
123{
124    if (!my_id)
125        return;
126
127    CFMutableDictionaryRef persistenceCache = SOSPeerGetPersistenceCache(my_id);
128
129    CFMutableDictionaryRef myPeerIDs = (CFMutableDictionaryRef) CFDictionaryGetValue(persistenceCache, my_id);
130    if (myPeerIDs)
131    {
132        CFRetainSafe(myPeerIDs);
133
134        CFDictionaryRemoveValue(myPeerIDs, my_id);
135
136        if (isDictionary(myPeerIDs)) {
137            CFDictionaryForEach(myPeerIDs, ^(const void *key, const void *value) {
138                // TODO: Inflate each and purge its keys.
139            });
140        }
141
142        CFReleaseNull(myPeerIDs);
143    }
144}
145
146static bool SOSPeerFindDataFor(CFTypeRef *peerData, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error)
147{
148    CFDictionaryRef table = (CFDictionaryRef) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id), my_id);
149
150    *peerData = isDictionary(table) ? CFDictionaryGetValue(table, peer_id) : NULL;
151
152    return true;
153}
154
155static bool SOSPeerCopyPersistedManifest(SOSManifestRef* manifest, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error)
156{
157    CFTypeRef persistedObject = NULL;
158
159    require(SOSPeerFindDataFor(&persistedObject, my_id, peer_id, error), fail);
160
161    CFDataRef persistedData = NULL;
162
163    if (isData(persistedObject))
164        persistedData = (CFDataRef)persistedObject;
165    else if (isArray(persistedObject) && (CFArrayGetCount((CFArrayRef) persistedObject) == 2))
166        persistedData = CFArrayGetValueAtIndex((CFArrayRef) persistedObject, 1);
167
168    if (isData(persistedData)) {
169        SOSManifestRef createdManifest = SOSManifestCreateWithData(persistedData, error);
170
171        require(createdManifest, fail);
172
173        *manifest = createdManifest;
174}
175
176    return true;
177
178fail:
179    return false;
180}
181
182
183static bool SOSPeerCopyCoderData(CFDataRef *data, CFStringRef my_id, CFStringRef peer_id, CFErrorRef *error)
184{
185    CFTypeRef persistedObject = NULL;
186
187    require(SOSPeerFindDataFor(&persistedObject, my_id, peer_id, error), fail);
188
189    CFDataRef persistedData = NULL;
190
191    if (isArray(persistedObject))
192        persistedData = CFArrayGetValueAtIndex((CFArrayRef) persistedObject, 0);
193
194    if (isData(persistedData)) {
195        CFRetainSafe(persistedData);
196        *data = persistedData;
197    }
198
199    return true;
200
201fail:
202    return false;
203}
204
205
206static void SOSPeerPersistData(CFStringRef my_id, CFStringRef peer_id, SOSManifestRef manifest, CFDataRef coderData)
207{
208    CFMutableArrayRef data_array = CFArrayCreateMutableForCFTypes(0);
209	if (coderData) {
210    CFArrayAppendValue(data_array, coderData);
211    } else {
212        CFDataRef nullData = CFDataCreate(kCFAllocatorDefault, NULL, 0);
213        CFArrayAppendValue(data_array, nullData);
214        CFReleaseNull(nullData);
215	}
216
217    if (manifest) {
218        CFArrayAppendValue(data_array, SOSManifestGetData(manifest));
219    }
220
221    CFMutableDictionaryRef mySubDict = (CFMutableDictionaryRef) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id), my_id);
222
223    if (mySubDict == NULL) {
224        mySubDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
225        CFDictionaryAddValue(SOSPeerGetPersistenceCache(my_id), my_id, mySubDict);
226    }
227
228    CFDictionarySetValue(mySubDict, peer_id, data_array);
229
230    CFReleaseNull(data_array);
231
232    SOSPeerFlushPersistenceCache();
233}
234
235struct __OpaqueSOSPeer {
236    SOSPeerSendBlock send_block;
237    CFStringRef my_id;
238    CFStringRef peer_id;
239    CFIndex version;
240    SOSManifestRef manifest;
241    CFDataRef manifest_digest;
242    SOSCoderRef coder; // Currently will be used for OTR stuff.
243};
244
245static SOSPeerRef SOSPeerCreate_Internal(CFStringRef myPeerID, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error,
246                                         SOSPeerSendBlock sendBlock) {
247    SOSPeerRef p = calloc(1, sizeof(struct __OpaqueSOSPeer));
248    p->send_block = sendBlock;
249    p->peer_id = theirPeerID;
250    CFRetainSafe(p->peer_id);
251
252    p->version = version;
253
254    p->my_id = myPeerID;
255    CFRetainSafe(myPeerID);
256
257    require(SOSPeerCopyPersistedManifest(&p->manifest, p->my_id, p->peer_id, error), fail);
258
259    return p;
260
261fail:
262    CFReleaseSafe(p->peer_id);
263    CFReleaseSafe(p->my_id);
264    free(p);
265    return NULL;
266}
267
268
269SOSPeerRef SOSPeerCreate(SOSFullPeerInfoRef myPeerInfo, SOSPeerInfoRef peerInfo,
270                         CFErrorRef *error, SOSPeerSendBlock sendBlock) {
271
272    if (myPeerInfo == NULL) {
273        SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without my peer info!"), NULL, error);
274        return NULL;
275    }
276    if (peerInfo == NULL) {
277        SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without their peer info!"), NULL, error);
278        return NULL;
279    }
280
281    SOSPeerRef result = NULL;
282    SOSPeerRef p = SOSPeerCreate_Internal(SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(myPeerInfo)),
283                                          SOSPeerInfoGetPeerID(peerInfo),
284                                          SOSPeerInfoGetVersion(peerInfo),
285                                          error, sendBlock);
286
287    require(p, fail);
288
289    CFDataRef coderData = NULL;
290    CFErrorRef coderError = NULL;
291
292    if (SOSPeerCopyCoderData(&coderData, p->my_id, p->peer_id, &coderError)
293        && coderData && CFDataGetLength(coderData) != 0) {
294        p->coder = SOSCoderCreateFromData(coderData, &coderError);
295    }
296
297    if (p->coder) {
298        secnotice("peer", "Old coder for me: %@ to peer: %@", p->my_id, p->peer_id);
299    } else {
300        secnotice("peer", "New coder for me: %@ to peer: %@ [Got error: %@]", p->my_id, p->peer_id, coderError);
301
302        p->coder = SOSCoderCreate(peerInfo, myPeerInfo, error);
303
304        if (!p->coder) {
305            SOSPeerDispose(p);
306            p = NULL;
307        }
308    }
309
310    CFReleaseNull(coderData);
311    CFReleaseNull(coderError);
312
313    result = p;
314    p = NULL;
315
316fail:
317    CFReleaseNull(p);
318    return result;
319}
320
321SOSPeerRef SOSPeerCreateSimple(CFStringRef peer_id, CFIndex version, CFErrorRef *error,
322                               SOSPeerSendBlock sendBlock) {
323    return SOSPeerCreate_Internal(CFSTR("FakeTestID"), peer_id, version, error, sendBlock);
324}
325
326void SOSPeerDispose(SOSPeerRef peer) {
327        CFErrorRef error = NULL;
328    CFDataRef coderData = NULL;
329    if (peer->coder) {
330        coderData = SOSCoderCopyDER(peer->coder, &error);
331        if (coderData == NULL) {
332			secerror("Coder data failed to export (%@), zapping data for me: %@ to peer: %@", error, peer->my_id, peer->peer_id);
333		}
334		CFReleaseNull(error);
335    }
336
337        if (!coderData) {
338            coderData = CFDataCreate(NULL, NULL, 0);
339        }
340
341        SOSPeerPersistData(peer->my_id, peer->peer_id, peer->manifest, coderData);
342
343        CFReleaseNull(coderData);
344    CFReleaseSafe(peer->peer_id);
345    CFReleaseSafe(peer->my_id);
346    if (peer->manifest)
347        SOSManifestDispose(peer->manifest);
348    CFReleaseSafe(peer->manifest_digest);
349    if (peer->coder)
350        SOSCoderDispose(peer->coder);
351
352    free(peer);
353}
354
355SOSPeerCoderStatus SOSPeerHandleMessage(SOSPeerRef peer, SOSEngineRef engine, CFDataRef codedMessage, CFErrorRef *error) {
356    CFMutableDataRef message = NULL;
357    SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned;
358
359    if (peer->coder) {
360        coderStatus = SOSCoderUnwrap(peer->coder, peer->send_block, codedMessage, &message, peer->peer_id, error);
361    } else {
362        message = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, codedMessage);
363    }
364
365    switch(coderStatus) {
366        case kSOSPeerCoderDataReturned: {
367            CFStringRef description = SOSMessageCopyDescription(message);
368            secnotice("peer", "Got message from %@: %@", peer->peer_id, description);
369            CFReleaseSafe(description);
370            coderStatus = (SOSEngineHandleMessage(engine, peer, message, error)) ? coderStatus: kSOSPeerCoderFailure;
371            break;
372        }
373        case kSOSPeerCoderNegotiating:  // Sent message already in Unwrap.
374            secnotice("peer", "Negotiating with %@: Got: %@", peer->peer_id, codedMessage);
375            break;
376        case kSOSPeerCoderNegotiationCompleted:
377            if (SOSEngineSyncWithPeer(engine, peer, true, error)) {
378                secnotice("peer", "Negotiating with %@ completed: %@" , peer->peer_id, codedMessage);
379            } else {
380                secerror("Negotiating with %@ completed syncWithPeer: %@ calling syncWithAllPeers" , peer->peer_id, error ? *error : NULL);
381                // Clearing the manifest forces SOSEngineSyncWithPeer(engine, peer, false, error) to send a message no matter what.
382                // This is needed because that's what gets called by SOSPeerStartSync, which is what SOSCCSyncWithAllPeers triggers.
383                SOSPeerSetManifest(peer, NULL, NULL);
384                SOSCCSyncWithAllPeers();
385                coderStatus = kSOSPeerCoderFailure;
386            }
387            break;
388        case kSOSPeerCoderFailure:      // Probably restart coder
389            secnotice("peer", "Failed handling message from %@: Got: %@", peer->peer_id, codedMessage);
390            SOSCoderReset(peer->coder);
391            coderStatus = SOSCoderStart(peer->coder, peer->send_block, peer->peer_id, error);
392            break;
393        case kSOSPeerCoderStaleEvent:   // We received an event we have already processed in the past.
394            secnotice("peer", "StaleEvent from %@: Got: %@", peer->peer_id, codedMessage);
395            break;
396        default:
397            assert(false);
398            break;
399    }
400
401    CFReleaseNull(message);
402
403    return coderStatus;
404}
405
406SOSPeerCoderStatus SOSPeerStartSync(SOSPeerRef peer, SOSEngineRef engine, CFErrorRef *error) {
407    SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned;
408
409    if (peer->coder) {
410        coderStatus = SOSCoderStart(peer->coder, peer->send_block, peer->peer_id, error);
411    }
412
413    switch(coderStatus) {
414        case kSOSPeerCoderDataReturned:         // fallthrough
415        case kSOSPeerCoderNegotiationCompleted: // fallthrough
416            coderStatus = (SOSEngineSyncWithPeer(engine, peer, false, error)) ? coderStatus: kSOSPeerCoderFailure;
417            break;
418        case kSOSPeerCoderNegotiating: // Sent message already in Unwrap.
419            secnotice("peer", "Started sync with %@", peer->peer_id);
420            break;
421        case kSOSPeerCoderFailure: // Probably restart coder
422            break;
423        default:
424            assert(false);
425            break;
426    }
427    return coderStatus;
428}
429
430bool SOSPeerSendMessage(SOSPeerRef peer, CFDataRef message, CFErrorRef *error) {
431    CFMutableDataRef codedMessage = NULL;
432    CFStringRef description = SOSMessageCopyDescription(message);
433
434    SOSPeerCoderStatus coderStatus = kSOSPeerCoderDataReturned;
435
436    if (peer->coder) {
437        coderStatus = SOSCoderWrap(peer->coder, message, &codedMessage, peer->peer_id, error);
438    } else {
439        codedMessage = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, message);
440    }
441    bool ok = true;
442    switch(coderStatus) {
443        case kSOSPeerCoderDataReturned:
444            secnotice("peer", "%@ message: %@", peer->peer_id, description);
445            peer->send_block(codedMessage, error);
446            break;
447        case kSOSPeerCoderNegotiating:
448            secnotice("peer", "%@ coder Negotiating - message not sent", peer->peer_id);
449            ok = SOSCreateErrorWithFormat(kSOSCCError, NULL, error, NULL, CFSTR("%@ failed to send message peer still negotiating"), peer->peer_id);
450            break;
451        default: // includes kSOSPeerCoderFailure
452            secerror("%@ coder failure - message not sent %@", peer->peer_id, error ? *error : NULL);
453            ok = false;
454            break;
455    }
456    CFReleaseSafe(description);
457    return ok;
458}
459
460bool SOSPeerCanSendMessage(SOSPeerRef peer) {
461    return (!peer->coder || SOSCoderCanWrap(peer->coder));
462}
463
464CFIndex SOSPeerGetVersion(SOSPeerRef peer) {
465    return peer->version;
466}
467
468CFStringRef SOSPeerGetID(SOSPeerRef peer) {
469    return peer->peer_id;
470}
471
472bool SOSPeersEqual(SOSPeerRef peerA, SOSPeerRef peerB)
473{
474    // Use mainly to see if peerB is actually this device (peerA)
475    return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo;
476}
477
478bool SOSPeerSetManifest(SOSPeerRef peer, SOSManifestRef manifest, CFErrorRef *error __unused) {
479    CFRetainSafe(manifest);
480    CFReleaseSafe(peer->manifest);
481    peer->manifest = manifest;
482
483    CFReleaseNull(peer->manifest_digest);
484    return true;
485}
486
487SOSManifestRef SOSPeerCopyManifest(SOSPeerRef peer, CFErrorRef *error __unused) {
488    if (!peer->manifest) {
489        SecCFCreateError(kSOSPeerHasNoManifest, sErrorDomain, CFSTR("failed to find peer manifest - not yet implemented"), NULL, error);
490        return NULL;
491    }
492
493    CFRetain(peer->manifest);
494    return peer->manifest;
495}
496
497CFDataRef SOSPeerCopyManifestDigest(SOSPeerRef peer, CFErrorRef *error) {
498    if (peer->manifest_digest) {
499        CFRetain(peer->manifest_digest);
500    } else {
501        if (peer->manifest) {
502            CFMutableDataRef data = CFDataCreateMutable(NULL, CC_SHA1_DIGEST_LENGTH);
503            if (data) {
504                CFDataSetLength(data, CC_SHA1_DIGEST_LENGTH);
505                CCDigest(kCCDigestSHA1, SOSManifestGetBytePtr(peer->manifest), (CC_LONG)SOSManifestGetSize(peer->manifest), CFDataGetMutableBytePtr(data));
506                peer->manifest_digest = data;
507                CFRetain(peer->manifest_digest);
508            } else {
509                SecCFCreateError(kSOSPeerDigestFailure, sErrorDomain, CFSTR("failed to create digest"), NULL, error);
510            }
511        } else {
512            SecCFCreateError(kSOSPeerHasNoManifest, sErrorDomain, CFSTR("peer has no manifest, can't create digest"), NULL, error);
513        }
514    }
515
516    return peer->manifest_digest;
517}
518