#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <Security/SecItem.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFString.h>
#include <SecureObjectSync/SOSCloudCircle.h>
#include <SecureObjectSync/SOSCloudCircleInternal.h>
#include <SecureObjectSync/SOSPeerInfo.h>
#include <securityd/SOSCloudCircleServer.h>
#include <CKBridge/SOSCloudKeychainClient.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/debugging.h>
#include <SecurityTool/readline.h>
#include <notify.h>
#include "SOSCommands.h"
#define printmsg(format, ...) _printcfmsg(stdout, format, __VA_ARGS__)
#define printerr(format, ...) _printcfmsg(stderr, format, __VA_ARGS__)
static void _printcfmsg(FILE *ff, CFStringRef format, ...)
{
va_list args;
va_start(args, format);
CFStringRef message = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, format, args);
va_end(args);
CFStringPerformWithCString(message, ^(const char *utf8String) { fprintf(ff, utf8String, ""); });
CFRelease(message);
}
static bool clearAllKVS(CFErrorRef *error)
{
__block bool result = false;
const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
SOSCloudKeychainClearAll(processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef cerror)
{
result = (cerror != NULL);
dispatch_semaphore_signal(waitSemaphore);
});
dispatch_semaphore_wait(waitSemaphore, finishTime);
dispatch_release(waitSemaphore);
return result;
}
static const char *getSOSCCStatusDescription(SOSCCStatus ccstatus)
{
switch (ccstatus)
{
case kSOSCCInCircle: return "In Circle";
case kSOSCCNotInCircle: return "Not in Circle";
case kSOSCCRequestPending: return "Request pending";
case kSOSCCCircleAbsent: return "Circle absent";
case kSOSCCError: return "Circle error";
default:
return "<unknown ccstatus>";
break;
}
}
static void dumpCircleInfo()
{
CFErrorRef error = NULL;
CFArrayRef applicantPeerInfos = NULL;
CFArrayRef peerInfos = NULL;
SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(&error);
printerr(CFSTR("ccstatus: %s (%d), error: %@\n"), getSOSCCStatusDescription(ccstatus), ccstatus, error);
if(ccstatus == kSOSCCError) {
printerr(CFSTR("End of Dump - unable to proceed due to ccstatus -\n\t%s\n"), getSOSCCStatusDescription(ccstatus));
return;
}
applicantPeerInfos = SOSCCCopyApplicantPeerInfo(&error);
if (applicantPeerInfos)
{
printerr(CFSTR("Applicants: %ld, error: %@\n"), (long)CFArrayGetCount(applicantPeerInfos), error);
CFArrayForEach(applicantPeerInfos, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
printerr(CFSTR("Applicant: %@ (%@)\n"), peerName, peer);
});
}
else
printerr(CFSTR("No applicants, error: %@\n"), error);
peerInfos = SOSCCCopyPeerPeerInfo(&error);
if (peerInfos)
{
printerr(CFSTR("Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerInfos), error);
CFArrayForEach(peerInfos, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
printerr(CFSTR("Peer: %@ (%@)\n"), peerName, peer);
});
}
else
printerr(CFSTR("No peers, error: %@\n"), error);
peerInfos = SOSCCCopyConcurringPeerPeerInfo(&error);
if (peerInfos)
{
printerr(CFSTR("Concurring Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerInfos), error);
CFArrayForEach(peerInfos, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
printerr(CFSTR("Concurr: %@ (%@)\n"), peerName, peer);
});
}
else
printerr(CFSTR("No concurring peers, error: %@\n"), error);
}
static bool requestToJoinCircle(CFErrorRef *error)
{
bool hadError = false;
SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(error);
switch (ccstatus)
{
case kSOSCCCircleAbsent:
hadError = !SOSCCResetToOffering(error);
break;
case kSOSCCNotInCircle:
hadError = !SOSCCRequestToJoinCircle(error);
break;
default:
printerr(CFSTR("Request to join circle with bad status: %@ (%d)\n"), SOSCCGetStatusDescription(ccstatus), ccstatus);
break;
}
return hadError;
}
static bool setPassword(char *labelAndPassword, CFErrorRef *err)
{
char *last = NULL;
char *token0 = strtok_r(labelAndPassword, ":", &last);
char *token1 = strtok_r(NULL, "", &last);
CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
char *password_token = token1 ? token1 : token0;
password_token = password_token ? password_token : "";
CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
bool returned = !SOSCCSetUserCredentials(label, password, err);
CFRelease(label);
CFRelease(password);
return returned;
}
static bool tryPassword(char *labelAndPassword, CFErrorRef *err)
{
char *last = NULL;
char *token0 = strtok_r(labelAndPassword, ":", &last);
char *token1 = strtok_r(NULL, "", &last);
CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
char *password_token = token1 ? token1 : token0;
password_token = password_token ? password_token : "";
CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
bool returned = !SOSCCTryUserCredentials(label, password, err);
CFRelease(label);
CFRelease(password);
return returned;
}
static CFTypeRef getObjectsFromCloud(CFArrayRef keysToGet, dispatch_queue_t processQueue, dispatch_group_t dgroup)
{
__block CFTypeRef object = NULL;
const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
dispatch_group_enter(dgroup);
CloudKeychainReplyBlock replyBlock =
^ (CFDictionaryRef returnedValues, CFErrorRef error)
{
secerror("SOSCloudKeychainGetObjectsFromCloud returned: %@", returnedValues);
object = returnedValues;
if (object)
CFRetain(object);
if (error)
{
secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
}
dispatch_group_leave(dgroup);
secerror("SOSCloudKeychainGetObjectsFromCloud block exit: %@", object);
dispatch_semaphore_signal(waitSemaphore);
};
if (!keysToGet)
SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
else
SOSCloudKeychainGetObjectsFromCloud(keysToGet, processQueue, replyBlock);
dispatch_semaphore_wait(waitSemaphore, finishTime);
dispatch_release(waitSemaphore);
if (object && (CFGetTypeID(object) == CFNullGetTypeID())) {
CFRelease(object);
object = NULL;
}
secerror("returned: %@", object);
return object;
}
static void displayCircles(CFTypeRef objects)
{
CFDictionaryForEach(objects, ^(const void *key, const void *value) {
if (SOSKVSKeyGetKeyType(key) == kCircleKey)
{
CFErrorRef localError = NULL;
if (isData(value))
{
SOSCircleRef circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, &localError);
printmsg(CFSTR("circle: %@ %@"), key, circle);
CFReleaseSafe(circle);
}
else
printmsg(CFSTR("non-circle: %@ %@"), key, value);
}
});
}
static bool dumpKVS(char *itemName, CFErrorRef *err)
{
CFArrayRef keysToGet = NULL;
if (itemName)
{
CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
printf("Retrieving %s from KVS\n", itemName);
keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
CFReleaseSafe(itemStr);
}
dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
dispatch_group_t work_group = dispatch_group_create();
CFTypeRef objects = getObjectsFromCloud(keysToGet, generalq, work_group);
CFReleaseSafe(keysToGet);
printmsg(CFSTR(" : %@\n"), objects);
if (objects)
displayCircles(objects);
printf("\n");
return false;
}
static bool syncAndWait(char *itemName, CFErrorRef *err)
{
CFArrayRef keysToGet = NULL;
__block CFTypeRef objects = NULL;
if (!itemName)
{
fprintf(stderr, "No item keys supplied\n");
return false;
}
CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
printf("Retrieving %s from KVS\n", itemName);
keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
CFReleaseSafe(itemStr);
dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
{
secerror("SOSCloudKeychainSynchronizeAndWait returned: %@", returnedValues);
if (error)
secerror("SOSCloudKeychainSynchronizeAndWait returned error: %@", error);
objects = returnedValues;
if (objects)
CFRetain(objects);
secerror("SOSCloudKeychainGetObjectsFromCloud block exit: %@", objects);
dispatch_semaphore_signal(waitSemaphore);
};
SOSCloudKeychainSynchronizeAndWait(keysToGet, generalq, replyBlock);
dispatch_semaphore_wait(waitSemaphore, finishTime);
dispatch_release(waitSemaphore);
CFReleaseSafe(keysToGet);
printmsg(CFSTR(" : %@\n"), objects);
if (objects)
displayCircles(objects);
printf("\n");
return false;
}
int
keychain_sync(int argc, char * const *argv)
{
int ch, result = 0;
CFErrorRef error = NULL;
bool hadError = false;
while ((ch = getopt(argc, argv, "edakrisROChP:T:DW:UX:")) != -1)
{
switch (ch)
{
case 'e':
printf("Keychain syncing is being turned ON\n");
hadError = requestToJoinCircle(&error);
break;
case 'd':
printf("Keychain syncing is being turned OFF\n");
hadError = !SOSCCRemoveThisDeviceFromCircle(&error);
break;
case 'a':
{
CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
if (applicants)
{
hadError = !SOSCCAcceptApplicants(applicants, &error);
CFRelease(applicants);
}
else
fprintf(stderr, "No applicants to accept\n");
}
break;
case 'r':
{
CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
if (applicants)
{
hadError = !SOSCCRejectApplicants(applicants, &error);
CFRelease(applicants);
}
else
fprintf(stderr, "No applicants to reject\n");
}
break;
case 'i':
dumpCircleInfo();
break;
case 'k':
notify_post("com.apple.security.cloudkeychain.forceupdate");
break;
case 's':
break;
case 'R':
hadError = !SOSCCResetToEmpty(&error);
break;
case 'O':
hadError = !SOSCCResetToOffering(&error);
break;
case 'C':
hadError = clearAllKVS(&error);
break;
case 'P':
hadError = setPassword(optarg, &error);
break;
case 'T':
hadError = tryPassword(optarg, &error);
break;
case 'X':
{
uint64_t limit = strtoul(optarg, NULL, 10);
hadError = !SOSCCBailFromCircle_BestEffort(limit, &error);
}
break;
case 'U':
hadError = !SOSCCPurgeUserCredentials(&error);
break;
case 'D':
hadError = dumpKVS(optarg, &error);
break;
case 'W':
hadError = syncAndWait(optarg, &error);
break;
case '?':
default:
return 2;
}
}
argc -= optind;
argv += optind;
if (hadError)
printerr(CFSTR("Error: %@\n"), error);
return result;
}