SOSAccountSync.m   [plain text]

#include "SOSAccountPriv.h"
#include "SOSAccount.h"

#include <Security/SecureObjectSync/SOSKVSKeys.h>
#include <Security/SecureObjectSync/SOSTransportCircle.h>
#include <Security/SecureObjectSync/SOSTransportCircleKVS.h>
#include <Security/SecureObjectSync/SOSTransportMessage.h>
#include <Security/SecureObjectSync/SOSKVSKeys.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
#include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>

#import <Security/SecureObjectSync/SOSAccountTrust.h>
#import <Security/SecureObjectSync/SOSTransport.h>
#import <Security/SecureObjectSync/SOSTransportKeyParameter.h>
#import <Security/SecureObjectSync/SOSTransportMessage.h>
#import <Security/SecureObjectSync/SOSTransportMessageKVS.h>
#include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
#include <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
#include <Security/SecureObjectSync/SOSInternal.h>

#include <CoreFoundation/CoreFoundation.h>

#include <utilities/SecCFError.h>

// MARK: Engine Logging
#define LOG_ENGINE_STATE_INTERVAL 20

void SOSAccountConsiderLoggingEngineState(SOSAccountTransaction* txn) {
static int engineLogCountDown = 0;

if(engineLogCountDown <= 0) {
SOSAccount* acct = txn.account;
CFTypeRef engine = [acct.kvs_message_transport SOSTransportMessageGetEngine];

SOSEngineLogState((SOSEngineRef)engine);
engineLogCountDown = LOG_ENGINE_STATE_INTERVAL;
} else {
engineLogCountDown--;
}
}

bool SOSAccountInflateTransports(SOSAccount* account, CFStringRef circleName, CFErrorRef *error){
bool success = false;

if(account.key_transport)
SOSUnregisterTransportKeyParameter(account.key_transport);
if(account.circle_transport)
SOSUnregisterTransportCircle(account.circle_transport);
if(account.kvs_message_transport)
SOSUnregisterTransportMessage((SOSMessage*)account.kvs_message_transport);

account.key_transport = [[CKKeyParameter alloc] initWithAccount:account];
account.circle_transport = [[SOSKVSCircleStorageTransport alloc]initWithAccount:account andCircleName:(__bridge NSString *)(circleName)];

require_quiet(account.key_transport, fail);
require_quiet(account.circle_transport, fail);

account.kvs_message_transport = [[SOSMessageKVS alloc] initWithAccount:account andName:(__bridge NSString*)circleName];
require_quiet(account.kvs_message_transport, fail);

success = true;

fail:
return success;
}

//
// MARK: KVS Syncing
//

static bool SOSAccountSyncWithKVSPeers(SOSAccountTransaction* txn, CFSetRef peerIDs, CFErrorRef *error) {
SOSAccount* account = txn.account;
CFErrorRef localError = NULL;
bool result = false;

require_quiet([account isInCircle:error], xit);

result =[account.kvs_message_transport SOSTransportMessageSyncWithPeers:account.kvs_message_transport p:peerIDs err:&localError];

if (result)
SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);

xit:
if (!result) {
// Tell account to update SOSEngine with current trusted peers
if (isSOSErrorCoded(localError, kSOSErrorPeerNotFound)) {
secnotice("Account", "Arming account to update SOSEngine with current trusted peers");
account.engine_peer_state_needs_repair = true;
}
CFErrorPropagate(localError, error);
localError = NULL;
}
return result;

}

bool SOSAccountSyncWithKVSPeerWithMessage(SOSAccountTransaction* txn, CFStringRef peerid, CFDataRef message, CFErrorRef *error) {
SOSAccount* account = txn.account;
bool result = false;
CFErrorRef localError = NULL;
CFDictionaryRef encapsulatedMessage = NULL;

secnotice("KVS Transport","Syncing with KVS capable peer: %@", peerid);
secnotice("KVS Transport", "message: %@", message);

require_quiet(message, xit);
require_quiet(peerid && CFStringGetLength(peerid) <= kSOSPeerIDLengthMax, xit);

encapsulatedMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerid, message, NULL);

result = [account.kvs_message_transport SOSTransportMessageSendMessages:account.kvs_message_transport pm:encapsulatedMessage err:&localError];
secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);

SOSAccountConsiderLoggingEngineState(txn);

xit:
CFReleaseNull(encapsulatedMessage);
CFErrorPropagate(localError, error);

return result;
}

static bool SOSAccountSyncWithKVSPeer(SOSAccountTransaction* txn, CFStringRef peerID, CFErrorRef *error)
{
bool result = false;
CFErrorRef localError = NULL;

secnotice("KVS Transport","Syncing with KVS capable peer: %@", peerID);

CFMutableSetRef peerIDs = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
CFSetAddValue(peerIDs, peerID);

result = SOSAccountSyncWithKVSPeers(txn, peerIDs, &localError);
secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);

CFReleaseNull(peerIDs);
CFErrorPropagate(localError, error);

return result;
}

CFSetRef SOSAccountSyncWithPeersOverKVS(SOSAccountTransaction* txn, CFSetRef peers) {
CFMutableSetRef handled = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);

CFSetForEach(peers, ^(const void *value) {
CFStringRef peerID = asString(value, NULL);
CFErrorRef localError = NULL;
if (peerID && SOSAccountSyncWithKVSPeer(txn, peerID, &localError)) {
CFSetAddValue(handled, peerID);
secnotice("KVS Transport", "synced with peer: %@", peerID);
} else {
secnotice("KVS Transport", "failed to sync with peer: %@ error: %@", peerID, localError);
}
});

return handled;
}

CF_RETURNS_RETAINED CFMutableSetRef SOSAccountSyncWithPeers(SOSAccountTransaction* txn, CFSetRef /* CFStringRef */ peerIDs, CFErrorRef *error) {
CFMutableSetRef notMePeers = NULL;
CFMutableSetRef handledPeerIDs = NULL;
CFMutableSetRef peersForKVS = NULL;

SOSAccount* account = txn.account;

if(![account isInCircle:error])
{
handledPeerIDs = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, peerIDs);
CFReleaseNull(notMePeers);
CFReleaseNull(peersForKVS);
return handledPeerIDs;
}

handledPeerIDs = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
peersForKVS = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);

SOSPeerInfoRef myPeerInfo = account.peerInfo;
if(!myPeerInfo)
{
CFReleaseNull(notMePeers);
CFReleaseNull(peersForKVS);
return handledPeerIDs;

}
CFStringRef myPeerID = SOSPeerInfoGetPeerID(myPeerInfo);

notMePeers = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, peerIDs);
CFSetRemoveValue(notMePeers, myPeerID);

CFSetForEach(notMePeers, ^(const void *value) {
CFErrorRef localError = NULL;
CFStringRef peerID = asString(value, &localError);
SOSPeerInfoRef peerInfo = NULL;
SOSCircleRef circle = NULL;
SOSAccountTrustClassic *trust = account.trust;
circle = trust.trustedCircle;
require_quiet(peerID, skip);

peerInfo = SOSCircleCopyPeerWithID(circle, peerID, NULL);
if (peerInfo && SOSCircleHasValidSyncingPeer(circle, peerInfo, account.accountKey, NULL)){
CFSetAddValue(peersForKVS, peerID);
} else {
CFSetAddValue(handledPeerIDs, peerID);
}

skip:
CFReleaseNull(peerInfo);
if (localError) {
secnotice("sync-with-peers", "Skipped peer ID: %@ due to %@", peerID, localError);
}
CFReleaseNull(localError);
});

CFSetRef handledKVSPeerIDs = SOSAccountSyncWithPeersOverKVS(txn, peersForKVS);
CFSetUnion(handledPeerIDs, handledKVSPeerIDs);
CFReleaseNull(handledKVSPeerIDs);

SOSAccountConsiderLoggingEngineState(txn);

CFReleaseNull(notMePeers);
CFReleaseNull(peersForKVS);
return handledPeerIDs;
}

CF_RETURNS_RETAINED CFSetRef SOSAccountProcessSyncWithPeers(SOSAccountTransaction* txn, CFSetRef /* CFStringRef */ peers, CFSetRef /* CFStringRef */ backupPeers, CFErrorRef *error)
{
CFErrorRef localError = NULL;
SOSAccount* account = txn.account;

CFMutableSetRef handled = SOSAccountSyncWithPeers(txn, peers, &localError);

if (!handled) {
secnotice("account-sync", "Peer Sync failed: %@", localError);
handled = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
}
CFReleaseNull(localError);

CFTypeRef engine = [account.kvs_message_transport SOSTransportMessageGetEngine];
CFSetRef engineHandled = SOSEngineSyncWithBackupPeers((SOSEngineRef)engine, backupPeers, false, error);

if (engineHandled) {
CFSetUnion(handled, engineHandled);
} else {
secnotice("account-sync", "Engine Backup Sync failed: %@", localError);
}
CFReleaseNull(localError);
CFReleaseNull(engineHandled);

return handled;
}

CF_RETURNS_RETAINED CFSetRef SOSAccountCopyBackupPeersAndForceSync(SOSAccountTransaction* txn, CFErrorRef *error)
{
SOSEngineRef engine = (SOSEngineRef) [txn.account.kvs_message_transport SOSTransportMessageGetEngine];

NSArray* backupPeersArray = (NSArray*) CFBridgingRelease(SOSEngineCopyBackupPeerNames(engine, error));
NSSet* backupPeers = [[NSSet alloc] initWithArray: backupPeersArray];
return SOSEngineSyncWithBackupPeers(engine, (__bridge CFSetRef) backupPeers, true, error);
}

bool SOSAccountRequestSyncWithAllPeers(SOSAccountTransaction* txn, CFErrorRef *error)
{
SOSAccount* account = txn.account;
SOSAccountTrustClassic *trust = account.trust;

if (![account isInCircle:error])
return false;

NSMutableSet<NSString*>* allSyncingPeers = [NSMutableSet set];
SOSCircleRef circle = trust.trustedCircle;

SOSCircleForEachValidSyncingPeer(circle, account.accountKey, ^(SOSPeerInfoRef peer) {
[allSyncingPeers addObject: (__bridge NSString*) SOSPeerInfoGetPeerID(peer)];
});

[txn requestSyncWithPeers:allSyncingPeers];

return true;
}

//
// MARK: Syncing status functions
//
bool SOSAccountMessageFromPeerIsPending(SOSAccountTransaction* txn, SOSPeerInfoRef peer, CFErrorRef *error) {
bool success = false;
SOSAccount* account = txn.account;
require_quiet([account isInCircle:error], xit);

// This translation belongs inside KVS..way down in CKD, but for now we reach over and do it here.
CFStringRef peerMessage = SOSMessageKeyCreateFromPeerToTransport([account kvs_message_transport], (__bridge CFStringRef)(account.peerID), SOSPeerInfoGetPeerID(peer));

success = SOSCloudKeychainHasPendingKey(peerMessage, error);
CFReleaseNull(peerMessage);

xit:
return success;
}

bool SOSAccountSendToPeerIsPending(SOSAccountTransaction* txn, SOSPeerInfoRef peer, CFErrorRef *error) {
bool success = false;
SOSAccount* account = txn.account;
require_quiet([account isInCircle:error], xit);

success = SOSCCIsSyncPendingFor(SOSPeerInfoGetPeerID(peer), error);
xit:
return success;

}