2 * Created by Michael Brouwer on 6/22/12.
3 * Copyright 2012 Apple Inc. All Rights Reserved.
7 * SOSPeer.c - Implementation of a secure object syncing peer
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>
25 #include <utilities/SecDb.h>
27 #include <securityd/SOSCloudCircleServer.h>
29 #include <CoreFoundation/CoreFoundation.h>
33 #include <AssertMacros.h>
38 static CFStringRef sErrorDomain
= CFSTR("com.apple.security.sos.peer.error");
40 static CFMutableDictionaryRef sPersistenceCache
= NULL
;
41 static CFStringRef peerFile
= CFSTR("PeerManifestCache.plist");
43 static CFMutableDictionaryRef
SOSPeerGetPersistenceCache(CFStringRef my_id
)
45 static dispatch_once_t onceToken
;
46 dispatch_once(&onceToken
, ^{
47 CFErrorRef localError
= NULL
;
48 CFMutableDictionaryRef peerDict
= NULL
;
49 CFDataRef dictAsData
= SOSItemGet(kSOSPeerDataLabel
, &localError
);
52 der_decode_dictionary(kCFAllocatorDefault
, kCFPropertyListMutableContainers
, (CFDictionaryRef
*)&peerDict
, &localError
,
53 CFDataGetBytePtr(dictAsData
),
54 CFDataGetBytePtr(dictAsData
) + CFDataGetLength(dictAsData
));
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
);
64 if (CFDictionaryGetValue(peerDict
, my_id
) != NULL
) {
65 CFMutableDictionaryRef mySubDictionary
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
67 CFDictionaryForEach(peerDict
, ^(const void *key
, const void *value
) {
68 if (!isDictionary(value
)) {
69 CFDictionaryAddValue(mySubDictionary
, key
, value
);
73 CFDictionaryForEach(mySubDictionary
, ^(const void *key
, const void *value
) {
74 CFDictionaryRemoveValue(peerDict
, key
);
77 CFDictionaryAddValue(peerDict
, my_id
, mySubDictionary
);
79 sPersistenceCache
= peerDict
;
82 return sPersistenceCache
;
85 static void SOSPeerFlushPersistenceCache()
87 if (!sPersistenceCache
)
90 CFErrorRef localError
= NULL
;
91 CFIndex size
= der_sizeof_dictionary(sPersistenceCache
, &localError
);
92 CFMutableDataRef dataToStore
= CFDataCreateMutableWithScratch(kCFAllocatorDefault
, size
);
95 secerror("Error calculating size of persistence cache: %@", localError
);
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
);
108 if (!SOSItemUpdateOrAdd(kSOSPeerDataLabel
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, dataToStore
, &localError
)) {
109 secerror("Peer cache item save failed: %@", localError
);
114 CFReleaseNull(localError
);
115 CFReleaseNull(dataToStore
);
118 void SOSPeerPurge(SOSPeerRef peer
) {
119 // TODO: Do we use this or some other end-around for PurgeAll?
122 void SOSPeerPurgeAllFor(CFStringRef my_id
)
127 CFMutableDictionaryRef persistenceCache
= SOSPeerGetPersistenceCache(my_id
);
129 CFMutableDictionaryRef myPeerIDs
= (CFMutableDictionaryRef
) CFDictionaryGetValue(persistenceCache
, my_id
);
132 CFRetainSafe(myPeerIDs
);
134 CFDictionaryRemoveValue(myPeerIDs
, my_id
);
136 if (isDictionary(myPeerIDs
)) {
137 CFDictionaryForEach(myPeerIDs
, ^(const void *key
, const void *value
) {
138 // TODO: Inflate each and purge its keys.
142 CFReleaseNull(myPeerIDs
);
146 static bool SOSPeerFindDataFor(CFTypeRef
*peerData
, CFStringRef my_id
, CFStringRef peer_id
, CFErrorRef
*error
)
148 CFDictionaryRef table
= (CFDictionaryRef
) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id
), my_id
);
150 *peerData
= isDictionary(table
) ? CFDictionaryGetValue(table
, peer_id
) : NULL
;
155 static bool SOSPeerCopyPersistedManifest(SOSManifestRef
* manifest
, CFStringRef my_id
, CFStringRef peer_id
, CFErrorRef
*error
)
157 CFTypeRef persistedObject
= NULL
;
159 require(SOSPeerFindDataFor(&persistedObject
, my_id
, peer_id
, error
), fail
);
161 CFDataRef persistedData
= NULL
;
163 if (isData(persistedObject
))
164 persistedData
= (CFDataRef
)persistedObject
;
165 else if (isArray(persistedObject
) && (CFArrayGetCount((CFArrayRef
) persistedObject
) == 2))
166 persistedData
= CFArrayGetValueAtIndex((CFArrayRef
) persistedObject
, 1);
168 if (isData(persistedData
)) {
169 SOSManifestRef createdManifest
= SOSManifestCreateWithData(persistedData
, error
);
171 require(createdManifest
, fail
);
173 *manifest
= createdManifest
;
183 static bool SOSPeerCopyCoderData(CFDataRef
*data
, CFStringRef my_id
, CFStringRef peer_id
, CFErrorRef
*error
)
185 CFTypeRef persistedObject
= NULL
;
187 require(SOSPeerFindDataFor(&persistedObject
, my_id
, peer_id
, error
), fail
);
189 CFDataRef persistedData
= NULL
;
191 if (isArray(persistedObject
))
192 persistedData
= CFArrayGetValueAtIndex((CFArrayRef
) persistedObject
, 0);
194 if (isData(persistedData
)) {
195 CFRetainSafe(persistedData
);
196 *data
= persistedData
;
206 static void SOSPeerPersistData(CFStringRef my_id
, CFStringRef peer_id
, SOSManifestRef manifest
, CFDataRef coderData
)
208 CFMutableArrayRef data_array
= CFArrayCreateMutableForCFTypes(0);
210 CFArrayAppendValue(data_array
, coderData
);
212 CFDataRef nullData
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
213 CFArrayAppendValue(data_array
, nullData
);
214 CFReleaseNull(nullData
);
218 CFArrayAppendValue(data_array
, SOSManifestGetData(manifest
));
221 CFMutableDictionaryRef mySubDict
= (CFMutableDictionaryRef
) CFDictionaryGetValue(SOSPeerGetPersistenceCache(my_id
), my_id
);
223 if (mySubDict
== NULL
) {
224 mySubDict
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
225 CFDictionaryAddValue(SOSPeerGetPersistenceCache(my_id
), my_id
, mySubDict
);
228 CFDictionarySetValue(mySubDict
, peer_id
, data_array
);
230 CFReleaseNull(data_array
);
232 SOSPeerFlushPersistenceCache();
235 struct __OpaqueSOSPeer
{
236 SOSPeerSendBlock send_block
;
240 SOSManifestRef manifest
;
241 CFDataRef manifest_digest
;
242 SOSCoderRef coder
; // Currently will be used for OTR stuff.
245 static 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
);
252 p
->version
= version
;
255 CFRetainSafe(myPeerID
);
257 require(SOSPeerCopyPersistedManifest(&p
->manifest
, p
->my_id
, p
->peer_id
, error
), fail
);
262 CFReleaseSafe(p
->peer_id
);
263 CFReleaseSafe(p
->my_id
);
269 SOSPeerRef
SOSPeerCreate(SOSFullPeerInfoRef myPeerInfo
, SOSPeerInfoRef peerInfo
,
270 CFErrorRef
*error
, SOSPeerSendBlock sendBlock
) {
272 if (myPeerInfo
== NULL
) {
273 SOSCreateError(kSOSErrorUnsupported
, CFSTR("Can't create peer without my peer info!"), NULL
, error
);
276 if (peerInfo
== NULL
) {
277 SOSCreateError(kSOSErrorUnsupported
, CFSTR("Can't create peer without their peer info!"), NULL
, error
);
281 SOSPeerRef result
= NULL
;
282 SOSPeerRef p
= SOSPeerCreate_Internal(SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(myPeerInfo
)),
283 SOSPeerInfoGetPeerID(peerInfo
),
284 SOSPeerInfoGetVersion(peerInfo
),
289 CFDataRef coderData
= NULL
;
290 CFErrorRef coderError
= NULL
;
292 if (SOSPeerCopyCoderData(&coderData
, p
->my_id
, p
->peer_id
, &coderError
)
293 && coderData
&& CFDataGetLength(coderData
) != 0) {
294 p
->coder
= SOSCoderCreateFromData(coderData
, &coderError
);
298 secnotice("peer", "Old coder for me: %@ to peer: %@", p
->my_id
, p
->peer_id
);
300 secnotice("peer", "New coder for me: %@ to peer: %@ [Got error: %@]", p
->my_id
, p
->peer_id
, coderError
);
302 p
->coder
= SOSCoderCreate(peerInfo
, myPeerInfo
, error
);
310 CFReleaseNull(coderData
);
311 CFReleaseNull(coderError
);
321 SOSPeerRef
SOSPeerCreateSimple(CFStringRef peer_id
, CFIndex version
, CFErrorRef
*error
,
322 SOSPeerSendBlock sendBlock
) {
323 return SOSPeerCreate_Internal(CFSTR("FakeTestID"), peer_id
, version
, error
, sendBlock
);
326 void SOSPeerDispose(SOSPeerRef peer
) {
327 CFErrorRef error
= NULL
;
328 CFDataRef coderData
= NULL
;
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
);
334 CFReleaseNull(error
);
338 coderData
= CFDataCreate(NULL
, NULL
, 0);
341 SOSPeerPersistData(peer
->my_id
, peer
->peer_id
, peer
->manifest
, coderData
);
343 CFReleaseNull(coderData
);
344 CFReleaseSafe(peer
->peer_id
);
345 CFReleaseSafe(peer
->my_id
);
347 SOSManifestDispose(peer
->manifest
);
348 CFReleaseSafe(peer
->manifest_digest
);
350 SOSCoderDispose(peer
->coder
);
355 SOSPeerCoderStatus
SOSPeerHandleMessage(SOSPeerRef peer
, SOSEngineRef engine
, CFDataRef codedMessage
, CFErrorRef
*error
) {
356 CFMutableDataRef message
= NULL
;
357 SOSPeerCoderStatus coderStatus
= kSOSPeerCoderDataReturned
;
360 coderStatus
= SOSCoderUnwrap(peer
->coder
, peer
->send_block
, codedMessage
, &message
, peer
->peer_id
, error
);
362 message
= CFDataCreateMutableCopy(kCFAllocatorDefault
, 0, codedMessage
);
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
;
373 case kSOSPeerCoderNegotiating
: // Sent message already in Unwrap.
374 secnotice("peer", "Negotiating with %@: Got: %@", peer
->peer_id
, codedMessage
);
376 case kSOSPeerCoderNegotiationCompleted
:
377 if (SOSEngineSyncWithPeer(engine
, peer
, true, error
)) {
378 secnotice("peer", "Negotiating with %@ completed: %@" , peer
->peer_id
, codedMessage
);
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
;
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
);
393 case kSOSPeerCoderStaleEvent
: // We received an event we have already processed in the past.
394 secnotice("peer", "StaleEvent from %@: Got: %@", peer
->peer_id
, codedMessage
);
401 CFReleaseNull(message
);
406 SOSPeerCoderStatus
SOSPeerStartSync(SOSPeerRef peer
, SOSEngineRef engine
, CFErrorRef
*error
) {
407 SOSPeerCoderStatus coderStatus
= kSOSPeerCoderDataReturned
;
410 coderStatus
= SOSCoderStart(peer
->coder
, peer
->send_block
, peer
->peer_id
, error
);
413 switch(coderStatus
) {
414 case kSOSPeerCoderDataReturned
: // fallthrough
415 case kSOSPeerCoderNegotiationCompleted
: // fallthrough
416 coderStatus
= (SOSEngineSyncWithPeer(engine
, peer
, false, error
)) ? coderStatus
: kSOSPeerCoderFailure
;
418 case kSOSPeerCoderNegotiating
: // Sent message already in Unwrap.
419 secnotice("peer", "Started sync with %@", peer
->peer_id
);
421 case kSOSPeerCoderFailure
: // Probably restart coder
430 bool SOSPeerSendMessage(SOSPeerRef peer
, CFDataRef message
, CFErrorRef
*error
) {
431 CFMutableDataRef codedMessage
= NULL
;
432 CFStringRef description
= SOSMessageCopyDescription(message
);
434 SOSPeerCoderStatus coderStatus
= kSOSPeerCoderDataReturned
;
437 coderStatus
= SOSCoderWrap(peer
->coder
, message
, &codedMessage
, peer
->peer_id
, error
);
439 codedMessage
= CFDataCreateMutableCopy(kCFAllocatorDefault
, 0, message
);
442 switch(coderStatus
) {
443 case kSOSPeerCoderDataReturned
:
444 secnotice("peer", "%@ message: %@", peer
->peer_id
, description
);
445 peer
->send_block(codedMessage
, error
);
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
);
451 default: // includes kSOSPeerCoderFailure
452 secerror("%@ coder failure - message not sent %@", peer
->peer_id
, error
? *error
: NULL
);
456 CFReleaseSafe(description
);
460 bool SOSPeerCanSendMessage(SOSPeerRef peer
) {
461 return (!peer
->coder
|| SOSCoderCanWrap(peer
->coder
));
464 CFIndex
SOSPeerGetVersion(SOSPeerRef peer
) {
465 return peer
->version
;
468 CFStringRef
SOSPeerGetID(SOSPeerRef peer
) {
469 return peer
->peer_id
;
472 bool SOSPeersEqual(SOSPeerRef peerA
, SOSPeerRef peerB
)
474 // Use mainly to see if peerB is actually this device (peerA)
475 return CFStringCompare(SOSPeerGetID(peerA
), SOSPeerGetID(peerB
), 0) == kCFCompareEqualTo
;
478 bool SOSPeerSetManifest(SOSPeerRef peer
, SOSManifestRef manifest
, CFErrorRef
*error __unused
) {
479 CFRetainSafe(manifest
);
480 CFReleaseSafe(peer
->manifest
);
481 peer
->manifest
= manifest
;
483 CFReleaseNull(peer
->manifest_digest
);
487 SOSManifestRef
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
);
493 CFRetain(peer
->manifest
);
494 return peer
->manifest
;
497 CFDataRef
SOSPeerCopyManifestDigest(SOSPeerRef peer
, CFErrorRef
*error
) {
498 if (peer
->manifest_digest
) {
499 CFRetain(peer
->manifest_digest
);
501 if (peer
->manifest
) {
502 CFMutableDataRef data
= CFDataCreateMutable(NULL
, CC_SHA1_DIGEST_LENGTH
);
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
);
509 SecCFCreateError(kSOSPeerDigestFailure
, sErrorDomain
, CFSTR("failed to create digest"), NULL
, error
);
512 SecCFCreateError(kSOSPeerHasNoManifest
, sErrorDomain
, CFSTR("peer has no manifest, can't create digest"), NULL
, error
);
516 return peer
->manifest_digest
;