//
// 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 "OT.h"
#import <utilities/debugging.h>
#import "keychain/ot/OTControl.h"
#import "keychain/ot/OTConstants.h"
#include "lib/SecArgParse.h"
#include <utilities/SecCFWrappers.h>
#include <utilities/SecInternalReleasePriv.h>
@interface OTControlCLI : NSObject
@property OTControl* control;
@end
@implementation OTControlCLI
- (instancetype) initWithOTControl:(OTControl*)control {
if ((self = [super init])) {
_control = control;
}
return self;
}
- (long)preflightBottledPeer:(NSString*)contextID dsid:(NSString*)dsid
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control preflightBottledPeer:contextID dsid:dsid reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
if(error){
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
}else if(entropy && bottleID && signingPublicKey){
printf("\nSuccessfully preflighted bottle ID: printf("\nEntropy used: printf("\nSigning Public Key: ret = 0;
}else{
printf("Failed to preflight bottle and no error was returned..");
ret = -1;
}
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");
return -1;
}
return ret;
#else
return -1;
#endif
}
- (long)launchBottledPeer:(NSString*)contextID bottleID:(NSString*)bottleID
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control launchBottledPeer:contextID bottleID:bottleID reply:^(NSError * _Nullable error) {
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
printf("\nSuccessfully launched bottleID: ret = 0;
}
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");
return -1;
}
return ret;
#else
return -1;
#endif
}
- (long)scrubBottledPeer:(NSString*)contextID bottleID:(NSString*)bottleID
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control scrubBottledPeer:contextID bottleID:bottleID reply:^(NSError * _Nullable error) {
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
printf("\nSuccessfully scrubbed bottle ID: ret = 0;
}
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");
return -1;
}
return ret;
#else
ret = -1;
return ret;
#endif
}
- (long)enroll:(NSString*)contextID dsid:(NSString*)dsid
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t semaForPreFlight = dispatch_semaphore_create(0);
dispatch_semaphore_t semaForLaunch = dispatch_semaphore_create(0);
__block NSString* bottleRecordID = nil;
__block NSError* localError = nil;
[self.control preflightBottledPeer:contextID dsid:dsid reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
if(error)
{
localError = error;
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
bottleRecordID = bottleID;
printf("\nSuccessfully preflighted bottle ID: printf("\nEntropy used: printf("\nSigning Public Key: ret = 0;
}
dispatch_semaphore_signal(semaForPreFlight);
}];
if(dispatch_semaphore_wait(semaForPreFlight, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
printf("\n\nError: timed out waiting for response\n");
return -1;
}
if(localError == nil){
[self.control launchBottledPeer:contextID bottleID:bottleRecordID reply:^(NSError * _Nullable error) {
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
printf("\nSuccessfully launched bottleID: ret = 0;
}
dispatch_semaphore_signal(semaForLaunch);
}];
if(dispatch_semaphore_wait(semaForLaunch, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
printf("\n\nError: timed out waiting for response\n");
return -1;
}
}
printf("Complete.\n");
return ret;
#else
return -1;
#endif
}
- (long)restore:(NSString*)contextID dsid:(NSString*)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control restore:contextID dsid:dsid secret:secret escrowRecordID:escrowRecordID reply:^(NSData* signingKeyData, NSData* encryptionKeyData, NSError *error) {
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
printf("Complete.\n");
ret = 0;
}
NSString* signingKeyString = [signingKeyData base64EncodedStringWithOptions:0];
NSString* encryptionKeyString = [encryptionKeyData base64EncodedStringWithOptions:0];
printf("Signing Key:\n printf("Encryption Key:\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");
return -1;
}
return ret;
#else
ret = -1;
return ret;
#endif
}
- (long) reset
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control reset:^(BOOL reset, NSError* error){
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
printf("success\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");
return -1;
}
printf("Complete.\n");
return ret;
#else
ret = -1;
return ret;
#endif
}
- (long) listOfRecords
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.control listOfRecords:^(NSArray* list, NSError* error){
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
[list enumerateObjectsUsingBlock:^(NSString* _Nonnull escrowRecordID, NSUInteger idx, BOOL * _Nonnull stop) {
printf("escrowRecordID: }];
ret = 0;
}
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");
return -1;
}
printf("Complete.\n");
return ret;
#else
ret = -1;
return ret;
#endif
}
- (long)octagonKeys
{
__block long ret = 0;
#if OCTAGON
dispatch_semaphore_t semaForGettingEncryptionKey = dispatch_semaphore_create(0);
dispatch_semaphore_t semaForGettingSigningKey = dispatch_semaphore_create(0);
[self.control encryptionKey:^(NSData *encryptionKey, NSError * error) {
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
NSString* encryptionKeyString = [encryptionKey base64EncodedStringWithOptions:0];
printf("Encryption Key:\n ret = 0;
}
dispatch_semaphore_signal(semaForGettingEncryptionKey);
}];
[self.control signingKey:^(NSData *signingKey, NSError * error) {
if(error)
{
printf("Error pushing: ret = (error.code == 0 ? -1 : error.code);
} else {
NSString* signingKeyString = [signingKey base64EncodedStringWithOptions:0];
printf("Signing Key:\n ret = 0;
}
dispatch_semaphore_signal(semaForGettingSigningKey);
}];
if(dispatch_semaphore_wait(semaForGettingEncryptionKey, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
printf("\n\nError: timed out waiting for response\n");
return -1;
}
if(dispatch_semaphore_wait(semaForGettingSigningKey, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
printf("\n\nError: timed out waiting for response\n");
return -1;
}
printf("Complete.\n");
return ret;
#else
ret = -1;
return ret;
#endif
}
@end
static int enroll = false;
static int restore = false;
static int octagonkeys = false;
static int reset = false;
static int prepbp = false;
static int launch = false;
static int scrub = false;
static int listOfRecords = false;
static char* bottleIDArg = NULL;
static char* contextNameArg = NULL;
static char* secretArg = NULL;
int main(int argc, char **argv)
{
if(!SecIsInternalRelease())
{
secnotice("octagon", "Tool not available on non internal builds");
return -1;
}
if(!SecOTIsEnabled())
{
printf("To use this tool, enable defaults write for EnableOTRestore\n defaults write (~)/Library/Preferences/com.apple.security EnableOTRestore -bool YES\n");
return -1;
}
static struct argument options[] = {
{ .shortname='s', .longname="secret", .argument=&secretArg, .description="escrow secret"},
{ .shortname='e', .longname="bottleID", .argument=&bottleIDArg, .description="bottle record id"},
{ .shortname='c', .longname="context", .argument=&contextNameArg, .description="context name"},
{ .command="restore", .flag=&restore, .flagval=true, .description="Restore fake bottled peer"},
{ .command="enroll", .flag=&enroll, .flagval=true, .description="Enroll fake bottled peer"},
{ .command="keys", .flag=&octagonkeys, .shortname='k', .flagval=true, .description="Octagon Signing + Encryption Keys"},
{ .command="reset", .flag=&reset, .flagval=true, .description="Reset Octagon Trust Zone"},
{ .command="list", .flag=&listOfRecords, .flagval=true, .description="List of current Bottled Peer Records IDs"},
{ .command="prepbp", .flag=&prepbp, .shortname='p', .flagval=true, .description="Preflights a bottled peer"},
{ .command="launch", .flag=&launch, .flagval=true, .description="Launches a bottled peer"},
{ .command="scrub", .flag=&scrub, .flagval=true, .description="Scrub bottled peer"},
{}
};
static struct arguments args = {
.programname="otctl",
.description="Control and report on Octagon Trust",
.arguments = options,
};
if(!options_parse(argc, argv, &args)) {
printf("\n");
print_usage(&args);
return -1;
}
@autoreleasepool {
NSError* error = nil;
OTControl* rpc = [OTControl controlObject:&error];
if(error || !rpc) {
errx(1, "no OTControl failed: }
OTControlCLI* ctl = [[OTControlCLI alloc] initWithOTControl:rpc];
if(enroll) {
long ret = 0;
NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
ret = [ctl enroll:context dsid:@"12345678"];
return (int)ret;
}
if(prepbp){
long ret = 0;
NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
//requires secret, context is optional
ret = [ctl preflightBottledPeer:context dsid:@"12345678"];
return (int)ret;
}
if(launch){
long ret = 0;
NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
//requires bottleID, context is optional
if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
ret = [ctl launchBottledPeer:context bottleID:bottleID];
}
else{
print_usage(&args);
return -1;
}
return (int)ret;
}
if(scrub){
long ret = 0;
NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
//requires bottle ID, context is optional
if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
ret = [ctl scrubBottledPeer:context bottleID:bottleID];
}
else{
print_usage(&args);
return -1;
}
return (int)ret;
}
if(restore) {
long ret = 0;
NSData* secretData = nil;
NSString* secretString = secretArg ? [NSString stringWithCString: secretArg encoding: NSUTF8StringEncoding] : nil;
NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
//requires secret and bottle ID, context is optional
if(secretString && [secretString length] > 0){
secretData = [[NSData alloc] initWithBase64EncodedString:secretString options:0];;
}
else{
print_usage(&args);
return -1;
}
if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
ret = [ctl restore:context dsid:@"12345678" secret:secretData escrowRecordID:bottleID];
}
else{
print_usage(&args);
return -1;
}
return (int)ret;
}
if(octagonkeys){
long ret = 0;
ret = [ctl octagonKeys];
return (int)ret;
}
if(listOfRecords){
long ret = 0;
ret = [ctl listOfRecords];
return (int)ret;
}
if(reset){
long ret = 0;
ret = [ctl reset];
return (int)ret;
}
else {
print_usage(&args);
return -1;
}
}
return 0;
}