DeviceSimulatorMain.m   [plain text]

//
// main.m
// DeviceSimulator
//
//

#import <Foundation/Foundation.h>
#import <Foundation/NSXPCConnection_Private.h>
#import <SOSCircle/CKBridge/SOSCloudKeychainConstants.h>
#import <objc/runtime.h>
#import <utilities/debugging.h>

#import <securityd/SOSCloudCircleServer.h>
#import <Security/SecureObjectSync/SOSPeerInfo.h>
#import <Security/SecureObjectSync/SOSCloudCircleInternal.h>
#import <Security/SecureObjectSync/SOSViews.h>
#import <Security/SecureObjectSync/SOSInternal.h>

#import "DeviceSimulator.h"
#import "SOSCloudKeychainClient.h"
#import "MultiDeviceNetworkingProtocol.h"
#import "SecCFWrappers.h"
#import "spi.h"

struct SOSCloudTransport MDNTransport;

@class MDNetwork;

NSString *deviceInstance = NULL;
static NSString *deviceHomeDir = NULL;
static MDNetwork *deviceNetwork = NULL;

@interface MDNetwork : NSObject<MultiDeviceNetworkingProtocol,MultiDeviceNetworkingCallbackProtocol,NSXPCListenerDelegate>
@property NSXPCConnection *connection;
@property NSXPCListener *callbackListener;
@property dispatch_queue_t flushQueue;
@property NSMutableDictionary *pendingKeys;
@property NSSet *registeredKeys;
- (instancetype)initWithConnection:(NSXPCConnection *)connection;
@end

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprotocol"
@implementation MDNetwork

- (instancetype)initWithConnection:(NSXPCConnection *)connection
{
self = [super init];
if (self) {
self.connection = connection;
self.callbackListener = [NSXPCListener anonymousListener];
self.callbackListener.delegate = self;
[self.callbackListener resume];

__typeof(self) weakSelf = self;
self.connection.invalidationHandler = ^{
__typeof(self) strongSelf = weakSelf;
[strongSelf.callbackListener invalidate];
strongSelf.callbackListener = nil;

exit(0);
};

self.flushQueue = dispatch_queue_create("MDNetwork.flushqueue", 0);
self.pendingKeys = [NSMutableDictionary dictionary];
self.registeredKeys = [NSSet set];

[[self.connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
NSLog(@"network register callback failed with: %@", error);
//abort();
}] MDNRegisterCallback:[self.callbackListener endpoint] complete:^void(NSDictionary *values, NSError *error) {
;
}];

}
return self;
}

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MultiDeviceNetworkingCallbackProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}

- (void)MDNCItemsChanged:(NSDictionary *)values complete:(MDNComplete)complete
{
NSMutableDictionary *requestedKeys = [NSMutableDictionary dictionary];
@synchronized(self.pendingKeys) {
secnotice("MDN", "items update: %@ (already pending: %@)", values, self.pendingKeys);

[self.pendingKeys addEntriesFromDictionary:values];
for (NSString *key in self.registeredKeys) {
id data = self.pendingKeys[key];
if (data) {
requestedKeys[key] = data;
self.pendingKeys[key] = nil;
}
}
}
if (requestedKeys.count) {
dispatch_async(self.flushQueue, ^{
secnotice("MDN", "engine processing keys: %@", requestedKeys);
NSArray *handled = CFBridgingRelease(SOSCCHandleUpdateMessage((__bridge CFDictionaryRef)requestedKeys));
/*
* Ok, our dear Engine might not have handled all messages.
* So put them back unless there are new messages around that
* have overwritten the previous message.
*/
for (NSString *key in handled) {
requestedKeys[key] = NULL;
}
if (requestedKeys.count) {
@synchronized(self.pendingKeys) {
for (NSString *key in requestedKeys) {
if (self.pendingKeys[key] == nil) {
self.pendingKeys[key] = requestedKeys[key];
}
}
}
}
});
}
complete(NULL, NULL);
}

/* Oh, ObjC, you are my friend */
- (void)forwardInvocation:(NSInvocation *)invocation
{
struct objc_method_description desc = protocol_getMethodDescription(@protocol(MultiDeviceNetworkingProtocol), [invocation selector], true, true);
if (desc.name == NULL) {
[super forwardInvocation:invocation];
} else {
__block bool gogogo = true;
id object = [self.connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
gogogo = false;
NSLog(@"network failed with: %@", error);
//abort();
}];
if(gogogo) [invocation invokeWithTarget:object];
}
}
@end
#pragma clang diagnostic pop

#define HANDLE_NO_NETWORK(_replyBlock) \
if (deviceNetwork == NULL) { \
replyBlock((__bridge CFDictionaryRef)@{}, (__bridge CFErrorRef)[NSError errorWithDomain:@"MDNNetwork" code:1 userInfo:NULL]); \
return; \
}

static void
DSCloudPut(SOSCloudTransportRef transport, CFDictionaryRef valuesToPut, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
@autoreleasepool {
HANDLE_NO_NETWORK(replyBlock);
secnotice("MDN", "CloudPut: %@", valuesToPut);

[deviceNetwork MDNCloudPut:(__bridge NSDictionary *)valuesToPut complete:^(NSDictionary *returnedValues, NSError *error) {
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
});
}];
}
}

static NSString *nKeyAlwaysKeys = @"AlwaysKeys";
static NSString *nKeyFirstUnlockKeys = @"FirstUnlockKeys";
static NSString *nKeyUnlockedKeys = @"UnlockedKeys";
static NSString *nMessageKeyParameter = @"KeyParameter";
static NSString *nMessageCircle = @"Circle";
static NSString *nMessageMessage = @"Message";

static void
DSCloudUpdateKeys(SOSCloudTransportRef transport, CFDictionaryRef cfkeys, CFStringRef accountUUID, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
/*
* Currently doesn't deal with lock state, just smash (HULK!) them all together
*/
@autoreleasepool {
NSDictionary *keys = (__bridge NSDictionary *)cfkeys;
NSMutableSet *newSet = [NSMutableSet set];

@synchronized(deviceNetwork.pendingKeys) {
for (NSString *type in @[ nMessageKeyParameter, nMessageCircle, nMessageMessage]) {
NSDictionary *typeDict = keys[type];

for (NSString *lockType in @[ nKeyAlwaysKeys, nKeyFirstUnlockKeys, nKeyUnlockedKeys]) {
NSArray *lockArray = typeDict[lockType];
if (lockArray) {
[newSet unionSet:[NSMutableSet setWithArray:lockArray]];
}
}
}
deviceNetwork.registeredKeys = newSet;
}
/* update engine with stuff */
[deviceNetwork MDNCItemsChanged:@{} complete:^(NSDictionary *returnedValues, NSError *error) {
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
}];
}
}

static void
DSCloudGetDeviceID(SOSCloudTransportRef transport, CloudKeychainReplyBlock replyBlock)
{
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
}

// Debug calls
static void
DSCloudGet(SOSCloudTransportRef transport, CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
}

static void
DSCloudGetAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
}

static void
DSCloudsynchronize(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
}

static void
DSCloudsynchronizeAndWait(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
@autoreleasepool {
HANDLE_NO_NETWORK(replyBlock);

[deviceNetwork MDNCloudsynchronizeAndWait:@{} complete:^(NSDictionary *returnedValues, NSError *error) {
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
});
}];
}
}

static void
DSCloudRemoveObjectForKey(SOSCloudTransportRef transport, CFStringRef keyToRemove, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
if (keyToRemove == NULL) {
dispatch_async(processQueue, ^{
replyBlock(NULL, NULL);
});
return;
}

@autoreleasepool {
HANDLE_NO_NETWORK(replyBlock);
[deviceNetwork MDNCloudRemoveKeys:@[(__bridge NSString *)keyToRemove] complete:^(NSDictionary *returnedValues, NSError *error) {
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
});
}];
}
}

static void DSCloudremoveKeys(SOSCloudTransportRef transport, CFArrayRef keys, CFStringRef accountUUID, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
@autoreleasepool {
HANDLE_NO_NETWORK(replyBlock);
[deviceNetwork MDNCloudRemoveKeys:(__bridge NSArray *)keys complete:^(NSDictionary *returnedValues, NSError *error) {
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
});
}];
}
}

static void
DSCloudclearAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
@autoreleasepool {
HANDLE_NO_NETWORK(replyBlock);
[deviceNetwork MDNCloudRemoveKeys:NULL complete:^(NSDictionary *returnedValues, NSError *error) {
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
});
}];
}
}

static bool
DSCloudhasPendingKey(SOSCloudTransportRef transport, CFStringRef keyName, CFErrorRef* error)
{
bool status = false;
@synchronized(deviceNetwork.pendingKeys) {
status = deviceNetwork.pendingKeys[(__bridge NSString *)keyName] != nil;
}
return status;
}

static void
DSCloudrequestSyncWithPeers(SOSCloudTransportRef transport, CFArrayRef /* CFStringRef */ peers, CFArrayRef /* CFStringRef */ backupPeers, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
CFSetRef sPeers = CFSetCreateCopyOfArrayForCFTypes(peers);
CFSetRef sBackupPeers = CFSetCreateCopyOfArrayForCFTypes(backupPeers);

if (sPeers == NULL || sBackupPeers == NULL) {
CFReleaseNull(sPeers);
CFReleaseNull(sBackupPeers);
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFErrorRef error = NULL;
CFSetRef result = SOSCCProcessSyncWithPeers_Server(sPeers, sBackupPeers, &error);

CFRelease(sPeers);
CFRelease(sBackupPeers);
CFReleaseNull(result);
CFReleaseNull(error);
});
}
if (replyBlock) {
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
});
}
}

static bool
DSCloudhasPeerSyncPending(SOSCloudTransportRef transport, CFStringRef peerID, CFErrorRef* error)
{
return false;
}

static void
DSCloudrequestEnsurePeerRegistration(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFErrorRef eprError = NULL;
if (!SOSCCProcessEnsurePeerRegistration_Server(&eprError)) {
secnotice("coder", "SOSCCProcessEnsurePeerRegistration failed with: %@", eprError);
}
CFReleaseNull(eprError);
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
});
}

static void DSCloudrequestPerfCounters(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
}

static void DSCloudflush(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
@autoreleasepool {
HANDLE_NO_NETWORK(replyBlock);

[deviceNetwork MDNCloudFlush:^(NSDictionary *returnedValues, NSError *error) {
dispatch_async(deviceNetwork.flushQueue, ^{
dispatch_async(processQueue, ^{
replyBlock((__bridge CFDictionaryRef)returnedValues, (__bridge CFErrorRef)error);
});
});
}];
}
}

static void DSCloudcounters(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
if (replyBlock)
replyBlock((__bridge CFDictionaryRef)@{}, NULL);
}

@interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
@end

@implementation ServiceDelegate

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(DeviceSimulatorProtocol)];

DeviceSimulator *exportedObject = [DeviceSimulator new];
exportedObject.conn = newConnection;
newConnection.exportedObject = exportedObject;

[newConnection resume];

return YES;
}

@end

void
boot_securityd(NSXPCListenerEndpoint *network)
{
secLogDisable();
securityd_init((__bridge CFURLRef)[NSURL URLWithString:deviceHomeDir]);

NSXPCConnection *connection = [[NSXPCConnection alloc] initWithListenerEndpoint:network];
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MultiDeviceNetworkingProtocol)];
[connection resume];

deviceNetwork = [[MDNetwork alloc] initWithConnection:connection];

}

/*
* Make sure each of th peers don't trample on each's others state
*/

@interface SOSCachedNotification (override)
@end

@implementation SOSCachedNotification (override)
+ (NSString *)swizzled_notificationName:(const char *)notificationName
{
return [NSString stringWithFormat:@"%@-%@",
[SOSCachedNotification swizzled_notificationName:notificationName], deviceInstance];
}

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method orignal = class_getClassMethod(self, @selector(notificationName:));
Method swizzled = class_getClassMethod(self, @selector(swizzled_notificationName:));
method_exchangeImplementations(orignal, swizzled);
});
}
@end

int main(int argc, const char *argv[])
{
struct sigaction action;
memset(&action, 0, sizeof(action));

deviceInstance = [[NSXPCListener _UUID] UUIDString];

NSURL *tempPath = [[NSFileManager defaultManager] temporaryDirectory];
deviceHomeDir = [[tempPath path] stringByAppendingPathComponent:deviceInstance];

[[NSFileManager defaultManager] createDirectoryAtPath:deviceHomeDir
withIntermediateDirectories:NO
attributes:NULL
error:NULL];

MDNTransport.put = DSCloudPut;
MDNTransport.updateKeys = DSCloudUpdateKeys;
MDNTransport.getDeviceID = DSCloudGetDeviceID;
MDNTransport.get = DSCloudGet;
MDNTransport.getAll = DSCloudGetAll;
MDNTransport.synchronize = DSCloudsynchronize;
MDNTransport.synchronizeAndWait = DSCloudsynchronizeAndWait;
MDNTransport.clearAll = DSCloudclearAll;
MDNTransport.removeObjectForKey = DSCloudRemoveObjectForKey;
MDNTransport.hasPendingKey = DSCloudhasPendingKey;
MDNTransport.requestSyncWithPeers = DSCloudrequestSyncWithPeers;
MDNTransport.hasPeerSyncPending = DSCloudhasPeerSyncPending;
MDNTransport.requestEnsurePeerRegistration = DSCloudrequestEnsurePeerRegistration;
MDNTransport.requestPerfCounters = DSCloudrequestPerfCounters;
MDNTransport.flush = DSCloudflush;
MDNTransport.itemsChangedBlock = CFBridgingRetain(^CFArrayRef(CFDictionaryRef values) {
// default change block doesn't handle messages, keep em
return CFBridgingRetain(@[]);
});
MDNTransport.removeKeys = DSCloudremoveKeys;
MDNTransport.counters = DSCloudcounters;

SOSCloudTransportSetDefaultTransport(&MDNTransport);

// Create the delegate for the service.
ServiceDelegate *delegate = [ServiceDelegate new];
signal(SIGPIPE, SIG_IGN);

// Set up the one NSXPCListener for this service. It will handle all incoming connections.
NSXPCListener *listener = [NSXPCListener serviceListener];
listener.delegate = delegate;

// Resuming the serviceListener starts this service. This method does not return.
[listener resume];
return 0;
}