//
// Security
//
#import <Foundation/Foundation.h>
#import <Foundation/NSXPCConnection_Private.h>
#import <Security/Security.h>
#import <Security/SecItemPriv.h>
#import <xpc/xpc.h>
#import <err.h>
#import "keychain/ckks/CKKS.h"
#import "keychain/ckks/CKKSControl.h"
#include "lib/SecArgParse.h"
static void nsprintf(NSString *fmt, ...) NS_FORMAT_FUNCTION(1, 2);
static void print_result(NSDictionary *dict, bool json_flag);
static void print_dict(NSDictionary *dict, int ind);
static void print_array(NSArray *array, int ind);
static void print_entry(id k, id v, int ind);
static void nsprintf(NSString *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
NSString *str = [[NSString alloc] initWithFormat:fmt arguments:ap];
va_end(ap);
puts([str UTF8String]);
#if !__has_feature(objc_arc)
[str release];
#endif
}
static void print_result(NSDictionary *dict, bool json_flag)
{
if (json_flag) {
NSError *err;
NSData *json = [NSJSONSerialization dataWithJSONObject:dict
options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
error:&err];
if (!json) {
NSLog(@"error: } else {
printf(" }
} else {
print_dict(dict, 0);
}
}
static void print_dict(NSDictionary *dict, int ind)
{
NSArray *sortedKeys = [[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
for (id k in sortedKeys) {
id v = dict[k];
print_entry(k, v, ind);
}
}
static void print_array(NSArray *array, int ind)
{
[array enumerateObjectsUsingBlock:^(id v, NSUInteger i, BOOL *stop __unused) {
print_entry(@(i), v, ind);
}];
}
static void print_entry(id k, id v, int ind)
{
if ([v isKindOfClass:[NSDictionary class]]) {
if (ind == 0) {
nsprintf(@"\n nsprintf(@" } else if (ind == 1) {
nsprintf(@"\n nsprintf(@" } else {
nsprintf(@" }
print_dict(v, ind + 1);
} else if ([v isKindOfClass:[NSArray class]]) {
nsprintf(@" print_array(v, ind + 1);
} else {
nsprintf(@" }
}
@interface CKKSControlCLI : NSObject
@property CKKSControl* control;
@end
@implementation CKKSControlCLI
- (instancetype) initWithCKKSControl:(CKKSControl*)control {
if ((self = [super init])) {
_control = control;
}
return self;
}
- (NSDictionary<NSString *, id> *)fetchPerformanceCounters
{
NSMutableDictionary *perfDict = [[NSMutableDictionary alloc] init];
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcPerformanceCounters:^(NSDictionary<NSString *,NSNumber *> * counters, NSError * error) {
if(error) {
perfDict[@"error"] = [error description];
}
[counters enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSNumber * obj, BOOL *stop) {
perfDict[key] = obj;
}];
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
perfDict[@"error"] = @"timed out waiting for response";
}
#endif
return perfDict;
}
- (void)resetLocal: (NSString*)view {
#if OCTAGON
printf("Beginning local reset for dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcResetLocal:view
reply:^(NSError *error) {
if(error == NULL) {
printf("reset complete.\n");
} else {
nsprintf(@"reset error: }
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
#endif // OCTAGON
}
- (void)resetCloudKit: (NSString*)view {
#if OCTAGON
printf("Beginning CloudKit reset for dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcResetCloudKit:view reply:^(NSError* error){
if(error == NULL) {
printf("CloudKit Reset complete.\n");
} else {
nsprintf(@"Reset error: }
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
#endif // OCTAON
}
- (void)resync: (NSString*)view {
#if OCTAGON
printf("Beginning resync for dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcResync:view reply:^(NSError* error){
if(error == NULL) {
printf("resync success.\n");
} else {
nsprintf(@"resync errored: }
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
#endif // OCTAGON
}
- (void)getAnalyticsSysdiagnose
{
printf("Getting analytics sysdiagnose....\n");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcGetAnalyticsSysdiagnoseWithReply:^(NSString* sysdiagnose, NSError* error) {
if (sysdiagnose && !error) {
nsprintf(@"Analytics sysdiagnose:\n\n }
else {
nsprintf(@"error retrieving sysdiagnose: }
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
}
- (void)getAnalyticsJSON
{
printf("Getting analytics json....\n");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcGetAnalyticsJSONWithReply:^(NSData* json, NSError* error) {
if (json && !error) {
nsprintf(@"Analytics JSON:\n\n }
else {
nsprintf(@"error retrieving JSON: }
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
}
- (void)forceAnalyticsUpload
{
printf("Uploading....\n");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcForceUploadAnalyticsWithReply:^(BOOL success, NSError* error) {
if (success) {
nsprintf(@"successfully uploaded analytics data");
}
else {
nsprintf(@"error uploading analytics: }
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
}
- (NSDictionary<NSString *, id> *)fetchStatus: (NSString*) view {
NSMutableDictionary *status = [[NSMutableDictionary alloc] init];
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcStatus: view reply: ^(NSArray<NSDictionary*>* result, NSError* error) {
if(error) {
status[@"error"] = [error description];
}
if(result.count <= 1u) {
printf("No CKKS views are active.\n");
}
for(NSDictionary* viewStatus in result) {
status[viewStatus[@"view"]] = viewStatus;
}
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5)) != 0) {
status[@"error"] = @"timed out";
}
#endif // OCTAGON
return status;
}
- (void)printHumanReadableStatus: (NSString*) view {
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcStatus: view reply: ^(NSArray<NSDictionary*>* result, NSError* error) {
if(error) {
printf("ERROR FETCHING STATUS: }
#define pop(d, key) ({ id x = d[key]; d[key] = nil; x; })
// First result is always global state
// Ideally, this would come in another parameter, but we can't change the protocol until
// <rdar://problem/33583242> CKKS: remove PCS's use of CKKSControlProtocol
NSMutableDictionary* global = [result[0] mutableCopy];
if(global) {
NSString* selfPeers = pop(global, @"selfPeers");
NSString* selfPeersError = pop(global, @"selfPeersError");
NSArray* trustedPeers = pop(global, @"trustedPeers");
NSString* trustedPeersError = pop(global, @"trustedPeersError");
printf("================================================================================\n\n");
printf("Global state:\n\n");
printf("Current self: if(![selfPeersError isEqual: [NSNull null]]) {
printf("Self Peers Error: }
printf("Trusted peers: if(![trustedPeersError isEqual: [NSNull null]]) {
printf("Trusted Peers Error: }
printf("\n");
}
NSArray* remainingViews = result.count > 1 ? [result subarrayWithRange:NSMakeRange(1, result.count-1)] : @[];
if(remainingViews.count == 0u) {
printf("No CKKS views are active.\n");
}
for(NSDictionary* viewStatus in remainingViews) {
NSMutableDictionary* status = [viewStatus mutableCopy];
NSString* viewName = pop(status,@"view");
NSString* accountStatus = pop(status,@"ckaccountstatus");
NSString* lockStateTracker = pop(status,@"lockstatetracker");
NSString* accountTracker = pop(status,@"accounttracker");
NSString* fetcher = pop(status,@"fetcher");
NSString* setup = pop(status,@"setup");
NSString* zoneCreated = pop(status,@"zoneCreated");
NSString* zoneCreatedError = pop(status,@"zoneCreatedError");
NSString* zoneSubscribed = pop(status,@"zoneSubscribed");
NSString* zoneSubscribedError = pop(status,@"zoneSubscribedError");
NSString* zoneInitializeScheduler = pop(status,@"zoneInitializeScheduler");
NSString* keystate = pop(status,@"keystate");
NSString* keyStateError = pop(status,@"keyStateError");
NSString* statusError = pop(status,@"statusError");
NSString* currentTLK = pop(status,@"currentTLK");
NSString* currentClassA = pop(status,@"currentClassA");
NSString* currentClassC = pop(status,@"currentClassC");
NSString* currentManifestGeneration = pop(status,@"currentManifestGen");
NSDictionary* oqe = pop(status,@"oqe");
NSDictionary* iqe = pop(status,@"iqe");
NSDictionary* keys = pop(status,@"keys");
NSDictionary* ckmirror = pop(status,@"ckmirror");
NSArray* devicestates = pop(status, @"devicestates");
NSArray* tlkshares = pop(status, @"tlkshares");
NSString* zoneSetupOperation = pop(status,@"zoneSetupOperation");
NSString* viewSetupOperation = pop(status,@"viewSetupOperation");
NSString* keyStateOperation = pop(status,@"keyStateOperation");
NSString* lastIncomingQueueOperation = pop(status,@"lastIncomingQueueOperation");
NSString* lastNewTLKOperation = pop(status,@"lastNewTLKOperation");
NSString* lastOutgoingQueueOperation = pop(status,@"lastOutgoingQueueOperation");
NSString* lastRecordZoneChangesOperation = pop(status,@"lastRecordZoneChangesOperation");
NSString* lastProcessReceivedKeysOperation = pop(status,@"lastProcessReceivedKeysOperation");
NSString* lastReencryptOutgoingItemsOperation = pop(status,@"lastReencryptOutgoingItemsOperation");
NSString* lastScanLocalItemsOperation = pop(status,@"lastScanLocalItemsOperation");
printf("================================================================================\n\n");
printf("View:
if(![statusError isEqual: [NSNull null]]) {
printf("ERROR FETCHING STATUS: }
printf("CloudKit account: printf("Account tracker: printf("Ran setup operation:
if(!([zoneCreated isEqualToString:@"yes"] && [zoneSubscribed isEqualToString:@"yes"])) {
printf("CK Zone Created: printf("CK Zone Created error:
printf("CK Zone Subscribed: printf("CK Zone Subscription error: printf("CK Zone initialize retry: printf("\n");
}
printf("Key state: if(![keyStateError isEqual: [NSNull null]]) {
printf("Key State Error: }
printf("Lock state:
printf("Current TLK: printf("Current ClassA: printf("Current ClassC:
printf("TLK shares:
printf("Outgoing Queue counts: printf("Incoming Queue counts: printf("Key counts: printf("latest manifest generation:
printf("Item counts (by key): printf("Peer states:
printf("zone change fetcher: printf("zoneSetupOperation: printf("viewSetupOperation: printf("keyStateOperation: printf("lastIncomingQueueOperation: printf("lastNewTLKOperation: printf("lastOutgoingQueueOperation: printf("lastRecordZoneChangesOperation: printf("lastProcessReceivedKeysOperation: printf("lastReencryptOutgoingItemsOperation: printf("lastScanLocalItemsOperation:
if(status.allKeys.count > 0u) {
printf("\nExtra information: }
printf("\n");
}
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
#endif // OCTAGON
}
- (void)fetch: (NSString*) view {
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcFetchAndProcessChanges:view reply:^(NSError* error) {
if(error) {
printf("Error fetching: } else {
printf("Complete.\n");
}
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
#endif // OCTAGON
}
- (void)push: (NSString*) view {
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control rpcPushOutgoingChanges:view reply:^(NSError* error) {
if(error) {
printf("Error pushing: } else {
printf("Complete.\n");
}
dispatch_semaphore_signal(sema);
}];
if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
printf("\n\nError: timed out waiting for response\n");
}
#endif // OCTAGON
}
@end
static int perfCounters = false;
static int status = false;
static int resync = false;
static int reset = false;
static int resetCloudKit = false;
static int fetch = false;
static int push = false;
static int json = false;
static int getAnalyticsSysdiagnose = false;
static int getAnalyticsJSON = false;
static int uploadAnalytics = false;
static char* viewArg = NULL;
int main(int argc, char **argv)
{
static struct argument options[] = {
{ .shortname='p', .longname="perfcounters", .flag=&perfCounters, .flagval=true, .description="Print CKKS performance counters"},
{ .shortname='j', .longname="json", .flag=&json, .flagval=true, .description="Output in JSON format"},
{ .shortname='v', .longname="view", .argument=&viewArg, .description="Operate on a single view"},
{ .command="status", .flag=&status, .flagval=true, .description="Report status on CKKS views"},
{ .command="fetch", .flag=&fetch, .flagval=true, .description="Fetch all new changes in CloudKit and attempt to process them"},
{ .command="push", .flag=&push, .flagval=true, .description="Push all pending local changes to CloudKit"},
{ .command="resync", .flag=&resync, .flagval=true, .description="Resync all data with what's in CloudKit"},
{ .command="reset", .flag=&reset, .flagval=true, .description="All local data will be wiped, and data refetched from CloudKit"},
{ .command="reset-cloudkit", .flag=&resetCloudKit, .flagval=true, .description="All data in CloudKit will be removed and replaced with what's local"},
{ .command="get-analytics-sysdiagnose", .flag=&getAnalyticsSysdiagnose, .flagval=true, .description="Retrieve the current sysdiagnose dump for CKKS analytics"},
{ .command="get-analytics", .flag=&getAnalyticsJSON, .flagval=true, .description="Retrieve the current JSON blob that would be uploaded to the logging server if an upload occurred now"},
{ .command="upload-analytics", .flag=&uploadAnalytics, .flagval=true, .description="Force an upload of analytics data to cloud server"},
{}
};
static struct arguments args = {
.programname="ckksctl",
.description="Control and report on CKKS",
.arguments = options,
};
if(!options_parse(argc, argv, &args)) {
printf("\n");
print_usage(&args);
return -1;
}
@autoreleasepool {
NSError* error = nil;
CKKSControl* rpc = [CKKSControl controlObject:&error];
if(error || !rpc) {
errx(1, "no CKKSControl failed: }
CKKSControlCLI* ctl = [[CKKSControlCLI alloc] initWithCKKSControl:rpc];
NSString* view = viewArg ? [NSString stringWithCString: viewArg encoding: NSUTF8StringEncoding] : nil;
if(status) {
// Complicated logic, but you can choose any combination of (json, perfcounters) that you like.
NSMutableDictionary *statusDict = [[NSMutableDictionary alloc] init];
if(perfCounters) {
statusDict[@"performance"] = [ctl fetchPerformanceCounters];
}
if (json) {
statusDict[@"status"] = [ctl fetchStatus:view];
}
if(json || perfCounters) {
print_result(statusDict, true);
printf("\n");
}
if(!json) {
[ctl printHumanReadableStatus:view];
}
} else if(perfCounters) {
NSMutableDictionary *statusDict = [[NSMutableDictionary alloc] init];
statusDict[@"performance"] = [ctl fetchPerformanceCounters];
print_result(statusDict, false);
} else if(fetch) {
[ctl fetch:view];
} else if(push) {
[ctl push:view];
} else if(reset) {
[ctl resetLocal:view];
} else if(resetCloudKit) {
[ctl resetCloudKit:view];
} else if(resync) {
[ctl resync:view];
} else if(getAnalyticsSysdiagnose) {
[ctl getAnalyticsSysdiagnose];
} else if(getAnalyticsJSON) {
[ctl getAnalyticsJSON];
} else if(uploadAnalytics) {
[ctl forceAnalyticsUpload];
} else {
print_usage(&args);
return -1;
}
}
return 0;
}