#include <Security/SecureObjectSync/SOSInternal.h>
#include <Security/SecureObjectSync/SOSKVSKeys.h>
#include <Security/SecureObjectSync/SOSAccountPriv.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
#include <Security/SecureObjectSync/SOSTransportCircleKVS.h>
#include <Security/SecureObjectSync/SOSTransportMessageKVS.h>
#include <Security/SecureObjectSync/SOSTransportMessage.h>
#include <Security/SecureObjectSync/SOSRing.h>
#include <SOSCloudKeychainClient.h>
#include <utilities/debugging.h>
#include <utilities/SecCFWrappers.h>
#include <CoreFoundation/CFBase.h>
CFStringRef kKeyParameter = CFSTR("KeyParameter");
CFStringRef kCircle = CFSTR("Circle");
CFStringRef kMessage = CFSTR("Message");
CFStringRef kAlwaysKeys = CFSTR("AlwaysKeys");
CFStringRef kFirstUnlocked = CFSTR("FirstUnlockKeys");
CFStringRef kUnlocked = CFSTR("UnlockedKeys");
extern CFStringRef kSOSAccountDebugScope;
#define DATE_LENGTH 18
CFStringRef SOSInterestListCopyDescription(CFArrayRef interests)
{
CFMutableStringRef description = CFStringCreateMutable(kCFAllocatorDefault, 0);
CFStringAppendFormat(description, NULL, CFSTR("<Interest: "));
if (interests) {
CFArrayForEach(interests, ^(const void* string) {
if (isString(string))
CFStringAppendFormat(description, NULL, CFSTR(" '%@'"), string);
});
}
CFStringAppend(description, CFSTR(">"));
return description;
}
//
// MARK: Key Interest Processing
//
CFGiblisGetSingleton(CFMutableArrayRef, SOSGetTransportMessages, sTransportMessages, ^{
*sTransportMessages = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
});
CFGiblisGetSingleton(CFMutableArrayRef, SOSGetTransportKeyParameters, sTransportKeyParameters, ^{
*sTransportKeyParameters = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
});
CFGiblisGetSingleton(CFMutableArrayRef, SOSGetTransportCircles, sTransportCircles, ^{
*sTransportCircles = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
});
void SOSRegisterTransportMessage(SOSMessage* additional) {
if(additional != nil)
CFArrayAppendValue(SOSGetTransportMessages(), (__bridge CFTypeRef)(additional));
}
void SOSUnregisterTransportMessage(SOSMessage* removal) {
CFArrayRemoveAllValue(SOSGetTransportMessages(), (__bridge CFTypeRef)(removal));
}
void SOSUnregisterAllTransportMessages() {
CFArrayRemoveAllValues(SOSGetTransportMessages());
}
void SOSRegisterTransportCircle(SOSCircleStorageTransport* additional) {
if(additional != nil)
CFArrayAppendValue(SOSGetTransportCircles(), (__bridge CFTypeRef)(additional));
}
void SOSUnregisterTransportCircle(SOSCircleStorageTransport* removal) {
CFArrayRemoveAllValue(SOSGetTransportCircles(), (__bridge CFTypeRef)removal);
}
void SOSUnregisterAllTransportCircles() {
CFArrayRemoveAllValues(SOSGetTransportCircles());
}
void SOSRegisterTransportKeyParameter(CKKeyParameter* additional) {
if(additional != nil)
CFArrayAppendValue(SOSGetTransportKeyParameters(), (__bridge CFTypeRef)(additional));
}
void SOSUnregisterTransportKeyParameter(CKKeyParameter* removal) {
CFArrayRemoveAllValue(SOSGetTransportKeyParameters(), (__bridge CFTypeRef)(removal));
}
void SOSUnregisterAllTransportKeyParameters() {
CFArrayRemoveAllValues(SOSGetTransportKeyParameters());
}
//
// Should we be dispatching back to our queue to handle later
//
void SOSUpdateKeyInterest(SOSAccount* account)
{
CFMutableArrayRef alwaysKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef afterFirstUnlockKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef whenUnlockedKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef keyDict = CFDictionaryCreateMutableForCFTypes (kCFAllocatorDefault);
NSMutableArray *temp = (__bridge NSMutableArray *)(SOSGetTransportKeyParameters());
[temp enumerateObjectsUsingBlock:^(CKKeyParameter *value, NSUInteger idx, BOOL * _Nonnull stop) {
CKKeyParameter* tKP = (CKKeyParameter*) value;
if ([tKP SOSTransportKeyParameterGetAccount:tKP] == account && [tKP SOSTransportKeyParameterGetTransportType:tKP err:NULL] == kKVS) {
CKKeyParameter* tkvs = (CKKeyParameter*) value;
CFErrorRef localError = NULL;
if (![tkvs SOSTransportKeyParameterKVSAppendKeyInterests:tkvs ak:alwaysKeys firstUnLock:afterFirstUnlockKeys unlocked:whenUnlockedKeys err:&localError]) {
secerror("Error getting key parameters interests }
CFReleaseNull(localError);
}
}];
CFMutableDictionaryRef keyParamsDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFDictionarySetValue(keyParamsDict, kAlwaysKeys, alwaysKeys);
CFDictionarySetValue(keyParamsDict, kFirstUnlocked, afterFirstUnlockKeys);
CFDictionarySetValue(keyParamsDict, kUnlocked, whenUnlockedKeys);
CFDictionarySetValue(keyDict, kKeyParameter, keyParamsDict);
CFReleaseNull(alwaysKeys);
CFReleaseNull(afterFirstUnlockKeys);
CFReleaseNull(whenUnlockedKeys);
alwaysKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
afterFirstUnlockKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
whenUnlockedKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFArrayForEach(SOSGetTransportCircles(), ^(const void *value) {
SOSKVSCircleStorageTransport *transport = (__bridge SOSKVSCircleStorageTransport*)value;
if ( [[transport getAccount] isEqual: account] ) {
SOSKVSCircleStorageTransport* tkvs = (__bridge SOSKVSCircleStorageTransport*) value;
CFErrorRef localError = NULL;
if(! [tkvs kvsAppendKeyInterest:alwaysKeys firstUnlock:afterFirstUnlockKeys unlocked:whenUnlockedKeys err:&localError]){
secerror("Error getting circle interests }
if(![tkvs kvsAppendRingKeyInterest:alwaysKeys firstUnlock:afterFirstUnlockKeys unlocked:whenUnlockedKeys err:&localError]){
secerror("Error getting ring interests }
if(![tkvs kvsAppendDebugKeyInterest:alwaysKeys firstUnlock:afterFirstUnlockKeys unlocked:whenUnlockedKeys err:&localError]) {
secerror("Error getting debug key interests }
CFReleaseNull(localError);
}
});
CFMutableDictionaryRef circleDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFDictionarySetValue(circleDict, kAlwaysKeys, alwaysKeys);
CFDictionarySetValue(circleDict, kFirstUnlocked, afterFirstUnlockKeys);
CFDictionarySetValue(circleDict, kUnlocked, whenUnlockedKeys);
CFDictionarySetValue(keyDict, kCircle, circleDict);
CFReleaseNull(alwaysKeys);
CFReleaseNull(afterFirstUnlockKeys);
CFReleaseNull(whenUnlockedKeys);
alwaysKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
afterFirstUnlockKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
whenUnlockedKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFArrayForEach(SOSGetTransportMessages(), ^(const void *value) {
SOSMessage* transport = (__bridge SOSMessage*)value;
if ([transport SOSTransportMessageGetAccount] == account && [transport SOSTransportMessageGetTransportType] == kKVS) {
CFErrorRef localError = NULL;
SOSMessageKVS* tks = (__bridge SOSMessageKVS*)value;
if(![tks SOSTransportMessageKVSAppendKeyInterest:tks ak:alwaysKeys firstUnlock:afterFirstUnlockKeys unlocked:whenUnlockedKeys err:&localError]){
secerror("Error getting message interests }
CFReleaseNull(localError);
}
});
CFMutableDictionaryRef messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFDictionarySetValue(messageDict, kAlwaysKeys, alwaysKeys);
CFDictionarySetValue(messageDict, kFirstUnlocked, afterFirstUnlockKeys);
CFDictionarySetValue(messageDict, kUnlocked, whenUnlockedKeys);
CFDictionarySetValue(keyDict, kMessage, messageDict);
//
// Log what we are about to do.
//
NSUInteger itemCount = 0;
for (NSString *subsystem in @[(__bridge id)kMessage, (__bridge id)kCircle, (__bridge id)kKeyParameter]) {
secnotice("key-interests", "Updating interests: for (NSString *lockState in @[(__bridge id)kAlwaysKeys, (__bridge id)kFirstUnlocked, (__bridge id)kUnlocked]) {
NSArray *items = ((__bridge NSDictionary *)keyDict)[subsystem][lockState];
itemCount += items.count;
for (NSString *item in items) {
secnotice("key-interests", " key-intrest: }
}
}
secnotice("key-interests", "Updating interests done:
CFStringRef uuid = SOSAccountCopyUUID(account);
SOSCloudKeychainUpdateKeys(keyDict, uuid, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error) {
if (error) {
secerror("Error updating keys: }
});
CFReleaseNull(uuid);
CFReleaseNull(alwaysKeys);
CFReleaseNull(afterFirstUnlockKeys);
CFReleaseNull(whenUnlockedKeys);
CFReleaseNull(keyParamsDict);
CFReleaseNull(circleDict);
CFReleaseNull(messageDict);
CFReleaseNull(keyDict);
}
static void showWhatWasHandled(CFDictionaryRef updates, CFMutableArrayRef handledKeys) {
CFMutableStringRef updateStr = CFStringCreateMutable(kCFAllocatorDefault, 0);
CFMutableStringRef handledKeysStr = CFStringCreateMutable(kCFAllocatorDefault, 0);
CFDictionaryForEach(updates, ^(const void *key, const void *value) {
if (isString(key)) {
CFStringAppendFormat(updateStr, NULL, CFSTR(" }
});
CFArrayForEach(handledKeys, ^(const void *value) {
if (isString(value)) {
CFStringAppendFormat(handledKeysStr, NULL, CFSTR(" }
});
secinfo("updates", "Updates [ secinfo("updates", "Handled [
CFReleaseSafe(updateStr);
CFReleaseSafe(handledKeysStr);
}
#define KVS_STATE_INTERVAL 50
CF_RETURNS_RETAINED
CFMutableArrayRef SOSTransportDispatchMessages(SOSAccountTransaction* txn, CFDictionaryRef updates, CFErrorRef *error){
__block SOSAccount* account = txn.account;
CFMutableArrayRef handledKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFStringRef dsid = NULL;
if(CFDictionaryGetValueIfPresent(updates, kSOSKVSAccountChangedKey, (const void**)&dsid)){
secnotice("accountChange", "SOSTransportDispatchMessages received kSOSKVSAccountChangedKey");
// While changing accounts we may modify the key params array. To avoid stepping on ourselves we
// copy the list for iteration. Now modifying the transport outside of the list iteration.
CFMutableArrayRef transportsToUse = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFArrayForEach(SOSGetTransportKeyParameters(), ^(const void *value) {
CKKeyParameter* transport = (__bridge CKKeyParameter*) value;
if(CFEqualSafe((__bridge CFTypeRef)([transport SOSTransportKeyParameterGetAccount:transport]), (__bridge CFTypeRef)(account))){
CFArrayAppendValue(transportsToUse, (__bridge const void *)(transport));
}
});
CFArrayForEach(transportsToUse, ^(const void *value) {
CKKeyParameter* tempTransport = (__bridge CKKeyParameter*) value;
CFStringRef accountDSID = (CFStringRef)SOSAccountGetValue(account, kSOSDSIDKey, error);
if(accountDSID == NULL){
[tempTransport SOSTransportKeyParameterHandleNewAccount:tempTransport acct:account];
SOSAccountSetValue(account, kSOSDSIDKey, dsid, error);
secdebug("dsid", "Assigning new DSID: } else if(accountDSID != NULL && CFStringCompare(accountDSID, dsid, 0) != 0 ) {
[tempTransport SOSTransportKeyParameterHandleNewAccount:tempTransport acct:account];
SOSAccountSetValue(account, kSOSDSIDKey, dsid, error);
secdebug("dsid", "Assigning new DSID: } else {
secdebug("dsid", "DSIDs are the same!");
}
});
CFReleaseNull(transportsToUse);
CFArrayAppendValue(handledKeys, kSOSKVSAccountChangedKey);
}
// Iterate through keys in updates. Perform circle change update.
// Then instantiate circles and engines and peers for all peers that
// are receiving a message in updates.
CFMutableDictionaryRef circle_peer_messages_table = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef circle_circle_messages_table = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef circle_retirement_messages_table = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef ring_update_message_table = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef debug_info_message_table = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
__block CFMutableDictionaryRef config_message_table = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
__block CFDataRef newParameters = NULL;
__block bool initial_sync = false;
__block bool new_account = false;
CFDictionaryForEach(updates, ^(const void *key, const void *value) {
CFStringRef circle_name = NULL;
CFStringRef ring_name = NULL;
CFStringRef peer_info_name = NULL;
CFStringRef from_name = NULL;
CFStringRef to_name = NULL;
CFStringRef backup_name = NULL;
require_quiet(isString(key), errOut);
switch (SOSKVSKeyGetKeyTypeAndParse(key, &circle_name, &peer_info_name, &ring_name, &backup_name, &from_name, &to_name)) {
case kCircleKey:
CFDictionarySetValue(circle_circle_messages_table, circle_name, value);
break;
case kInitialSyncKey:
initial_sync = true;
break;
case kParametersKey:
if (isData(value)) {
newParameters = (CFDataRef) CFRetainSafe(value);
}
break;
case kMessageKey: {
CFMutableDictionaryRef circle_messages = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(circle_peer_messages_table, circle_name);
CFDictionarySetValue(circle_messages, from_name, value);
break;
}
case kRetirementKey: {
CFMutableDictionaryRef circle_retirements = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(circle_retirement_messages_table, circle_name);
CFDictionarySetValue(circle_retirements, from_name, value);
break;
}
case kAccountChangedKey:
new_account = true;
break;
case kRingKey:
if(isString(ring_name))
CFDictionarySetValue(ring_update_message_table, ring_name, value);
break;
case kDebugInfoKey:
CFDictionarySetValue(debug_info_message_table, peer_info_name, value);
break;
case kLastCircleKey:
case kLastKeyParameterKey:
case kUnknownKey:
secnotice("updates", "Unknown key '%@', ignoring", key);
break;
}
errOut:
CFReleaseNull(circle_name);
CFReleaseNull(from_name);
CFReleaseNull(to_name);
CFReleaseNull(ring_name);
CFReleaseNull(peer_info_name);
CFReleaseNull(backup_name);
if (error && *error)
secerror("Peer message processing error for: });
if (newParameters) {
CFArrayForEach(SOSGetTransportKeyParameters(), ^(const void *value) {
CKKeyParameter* tkvs = (__bridge CKKeyParameter*) value;
CFErrorRef localError = NULL;
if([[tkvs SOSTransportKeyParameterGetAccount:tkvs] isEqual:account]){
if(![tkvs SOSTransportKeyParameterHandleKeyParameterChanges:tkvs data:newParameters err:localError])
secerror("Transport failed to handle new key parameters: }
});
CFArrayAppendValue(handledKeys, kSOSKVSKeyParametersKey);
}
CFReleaseNull(newParameters);
if(initial_sync){
CFArrayAppendValue(handledKeys, kSOSKVSInitialSyncKey);
}
if(CFDictionaryGetCount(debug_info_message_table)) {
/* check for a newly set circle debug scope */
CFTypeRef debugScope = CFDictionaryGetValue(debug_info_message_table, kSOSAccountDebugScope);
if (debugScope) {
if(isString(debugScope)){
ApplyScopeListForID(debugScope, kScopeIDCircle);
}else if(isDictionary(debugScope)){
ApplyScopeDictionaryForID(debugScope, kScopeIDCircle);
}
}
CFStringRef debugInfoKey = SOSDebugInfoKeyCreateWithTypeName(kSOSAccountDebugScope);
CFArrayAppendValue(handledKeys, debugInfoKey);
CFReleaseNull(debugInfoKey);
}
if(CFDictionaryGetCount(circle_retirement_messages_table)) {
CFArrayForEach(SOSGetTransportCircles(), ^(const void *value) {
SOSKVSCircleStorageTransport* tkvs = (__bridge SOSKVSCircleStorageTransport*) value;
if([[tkvs getAccount] isEqual:account]){
CFErrorRef localError = NULL;
CFDictionaryRef handledRetirementKeys = [tkvs handleRetirementMessages:circle_retirement_messages_table err:error];
if(handledRetirementKeys == NULL){
secerror("Transport failed to handle retirement messages: } else {
CFDictionaryForEach(handledRetirementKeys, ^(const void *key, const void *value) {
CFStringRef circle_name = (CFStringRef)key;
CFArrayRef handledPeerIDs = (CFArrayRef)value;
CFArrayForEach(handledPeerIDs, ^(const void *value) {
CFStringRef peer_id = (CFStringRef)value;
CFStringRef keyHandled = SOSRetirementKeyCreateWithCircleNameAndPeer(circle_name, peer_id);
CFArrayAppendValue(handledKeys, keyHandled);
CFReleaseNull(keyHandled);
});
});
}
CFReleaseNull(handledRetirementKeys);
CFReleaseNull(localError);
}
});
}
if(CFDictionaryGetCount(circle_peer_messages_table)) {
CFArrayForEach(SOSGetTransportMessages(), ^(const void *value) {
SOSMessage* tmsg = (__bridge SOSMessage*) value;
CFDictionaryRef circleToPeersHandled = NULL;
CFErrorRef handleMessagesError = NULL;
CFErrorRef flushError = NULL;
if(!([([tmsg SOSTransportMessageGetAccount]) isEqual:account])){
CFReleaseNull(flushError);
CFReleaseNull(circleToPeersHandled);
CFReleaseNull(handleMessagesError);
return;
}
circleToPeersHandled = [tmsg SOSTransportMessageHandlePeerMessageReturnsHandledCopy:tmsg peerMessages:circle_peer_messages_table err:&handleMessagesError];
if(!circleToPeersHandled){
secnotice("msg", "No messages handled: CFReleaseNull(flushError);
CFReleaseNull(circleToPeersHandled);
CFReleaseNull(handleMessagesError);
return;
}
CFArrayRef handledPeers = asArray(CFDictionaryGetValue(circleToPeersHandled, [tmsg SOSTransportMessageGetCircleName]), NULL);
if (handledPeers) {
CFArrayForEach(handledPeers, ^(const void *value) {
CFStringRef peerID = asString(value, NULL);
if (peerID) {
CFStringRef kvsHandledKey = SOSMessageKeyCreateFromPeerToTransport(tmsg, (__bridge CFStringRef) account.peerID, peerID);
if (kvsHandledKey) {
CFArrayAppendValue(handledKeys, kvsHandledKey);
}
CFReleaseNull(kvsHandledKey);
}
});
}
if(![tmsg SOSTransportMessageFlushChanges:tmsg err:&flushError])
secnotice("msg", "Flush failed:
CFReleaseNull(flushError);
CFReleaseNull(circleToPeersHandled);
CFReleaseNull(handleMessagesError);
});
}
if(CFDictionaryGetCount(circle_circle_messages_table)) {
CFArrayForEach(SOSGetTransportCircles(), ^(const void *value) {
SOSKVSCircleStorageTransport* tkvs = (__bridge SOSKVSCircleStorageTransport*) value;
if([[tkvs getAccount] isEqual: account]){
CFArrayRef handleCircleMessages = [tkvs handleCircleMessagesAndReturnHandledCopy:circle_circle_messages_table err:error];
CFErrorRef localError = NULL;
if(handleCircleMessages == NULL){
secerror("Transport failed to handle circle messages: } else if(CFArrayGetCount(handleCircleMessages) == 0) {
if(CFDictionaryGetCount(circle_circle_messages_table) != 0) {
secerror("Transport failed to process all circle messages: ( CFArrayGetCount(handleCircleMessages),
CFDictionaryGetCount(circle_circle_messages_table), localError);
} else {
secnotice("circle", "Transport handled no circle messages");
}
} else {
CFArrayForEach(handleCircleMessages, ^(const void *value) {
CFStringRef keyHandled = SOSCircleKeyCreateWithName((CFStringRef)value, error);
CFArrayAppendValue(handledKeys, keyHandled);
CFReleaseNull(keyHandled);
});
}
CFReleaseNull(handleCircleMessages);
CFReleaseNull(localError);
}
});
}
if(CFDictionaryGetCount(ring_update_message_table)){
CFArrayForEach(SOSGetTransportCircles(), ^(const void *value) {
SOSKVSCircleStorageTransport* tkvs = (__bridge SOSKVSCircleStorageTransport*) value;
if([[tkvs getAccount] isEqual:account]){
CFErrorRef localError = NULL;
CFMutableArrayRef handledRingMessages = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFDictionaryForEach(ring_update_message_table, ^(const void *key, const void *value) {
CFDataRef ringData = asData(value, NULL);
SOSRingRef ring = SOSRingCreateFromData(error, ringData);
if(SOSAccountUpdateRingFromRemote(account, ring, error)){
CFArrayAppendValue(handledRingMessages, key);
}
CFReleaseNull(ring);
});
if(CFArrayGetCount(handledRingMessages) == 0){
secerror("Transport failed to handle ring messages: } else {
CFArrayForEach(handledRingMessages, ^(const void *value) {
CFStringRef ring_name = (CFStringRef)value;
CFStringRef keyHandled = SOSRingKeyCreateWithRingName(ring_name);
CFArrayAppendValue(handledKeys, keyHandled);
CFReleaseNull(keyHandled);
});
}
CFReleaseNull(handledRingMessages);
CFReleaseNull(localError);
}
});
}
CFReleaseNull(circle_retirement_messages_table);
CFReleaseNull(circle_circle_messages_table);
CFReleaseNull(circle_peer_messages_table);
CFReleaseNull(debug_info_message_table);
CFReleaseNull(ring_update_message_table);
CFReleaseNull(debug_info_message_table);
CFReleaseNull(config_message_table);
showWhatWasHandled(updates, handledKeys);
return handledKeys;
}