5 #import <Foundation/Foundation.h>
6 #import <Foundation/NSXPCConnection_Private.h>
7 #import <Security/Security.h>
8 #import <Security/SecItemPriv.h>
11 #import <utilities/debugging.h>
12 #import "keychain/ot/OTControl.h"
13 #import "keychain/ot/OTConstants.h"
14 #include "lib/SecArgParse.h"
15 #include <utilities/SecCFWrappers.h>
16 #include <utilities/SecInternalReleasePriv.h>
18 @interface OTControlCLI : NSObject
19 @property OTControl* control;
22 @implementation OTControlCLI
25 - (instancetype) initWithOTControl:(OTControl*)control {
26 if ((self = [super init])) {
33 - (long)preflightBottledPeer:(NSString*)contextID dsid:(NSString*)dsid
38 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
40 [self.control preflightBottledPeer:contextID dsid:dsid reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
42 printf("Error pushing: %s\n", [[error description] UTF8String]);
43 ret = (error.code == 0 ? -1 : error.code);
44 }else if(entropy && bottleID && signingPublicKey){
45 printf("\nSuccessfully preflighted bottle ID: %s\n", [bottleID UTF8String]);
46 printf("\nEntropy used: %s\n", [[entropy base64EncodedStringWithOptions:0] UTF8String]);
47 printf("\nSigning Public Key: %s\n", [[signingPublicKey base64EncodedStringWithOptions:0] UTF8String]);
50 printf("Failed to preflight bottle and no error was returned..");
54 dispatch_semaphore_signal(sema);
57 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
58 printf("\n\nError: timed out waiting for response\n");
67 - (long)launchBottledPeer:(NSString*)contextID bottleID:(NSString*)bottleID
72 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
74 [self.control launchBottledPeer:contextID bottleID:bottleID reply:^(NSError * _Nullable error) {
77 printf("Error pushing: %s\n", [[error description] UTF8String]);
78 ret = (error.code == 0 ? -1 : error.code);
80 printf("\nSuccessfully launched bottleID: %s\n", [bottleID UTF8String]);
84 dispatch_semaphore_signal(sema);
87 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
88 printf("\n\nError: timed out waiting for response\n");
97 - (long)scrubBottledPeer:(NSString*)contextID bottleID:(NSString*)bottleID
102 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
104 [self.control scrubBottledPeer:contextID bottleID:bottleID reply:^(NSError * _Nullable error) {
107 printf("Error pushing: %s\n", [[error description] UTF8String]);
108 ret = (error.code == 0 ? -1 : error.code);
110 printf("\nSuccessfully scrubbed bottle ID: %s\n", [bottleID UTF8String]);
114 dispatch_semaphore_signal(sema);
117 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
118 printf("\n\nError: timed out waiting for response\n");
129 - (long)enroll:(NSString*)contextID dsid:(NSString*)dsid
131 __block long ret = 0;
134 dispatch_semaphore_t semaForPreFlight = dispatch_semaphore_create(0);
135 dispatch_semaphore_t semaForLaunch = dispatch_semaphore_create(0);
136 __block NSString* bottleRecordID = nil;
137 __block NSError* localError = nil;
139 [self.control preflightBottledPeer:contextID dsid:dsid reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
143 printf("Error pushing: %s\n", [[error description] UTF8String]);
144 ret = (error.code == 0 ? -1 : error.code);
146 bottleRecordID = bottleID;
147 printf("\nSuccessfully preflighted bottle ID: %s\n", [bottleID UTF8String]);
148 printf("\nEntropy used: %s\n", [[entropy base64EncodedStringWithOptions:0] UTF8String]);
149 printf("\nSigning Public Key: %s\n", [[signingPublicKey base64EncodedStringWithOptions:0] UTF8String]);
153 dispatch_semaphore_signal(semaForPreFlight);
156 if(dispatch_semaphore_wait(semaForPreFlight, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
157 printf("\n\nError: timed out waiting for response\n");
161 if(localError == nil){
162 [self.control launchBottledPeer:contextID bottleID:bottleRecordID reply:^(NSError * _Nullable error) {
165 printf("Error pushing: %s\n", [[error description] UTF8String]);
166 ret = (error.code == 0 ? -1 : error.code);
168 printf("\nSuccessfully launched bottleID: %s\n", [bottleRecordID UTF8String]);
172 dispatch_semaphore_signal(semaForLaunch);
175 if(dispatch_semaphore_wait(semaForLaunch, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
176 printf("\n\nError: timed out waiting for response\n");
180 printf("Complete.\n");
188 - (long)restore:(NSString*)contextID dsid:(NSString*)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID
190 __block long ret = 0;
193 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
195 [self.control restore:contextID dsid:dsid secret:secret escrowRecordID:escrowRecordID reply:^(NSData* signingKeyData, NSData* encryptionKeyData, NSError *error) {
198 printf("Error pushing: %s\n", [[error description] UTF8String]);
199 ret = (error.code == 0 ? -1 : error.code);
202 printf("Complete.\n");
206 NSString* signingKeyString = [signingKeyData base64EncodedStringWithOptions:0];
207 NSString* encryptionKeyString = [encryptionKeyData base64EncodedStringWithOptions:0];
209 printf("Signing Key:\n %s\n", [signingKeyString UTF8String]);
210 printf("Encryption Key:\n %s\n", [encryptionKeyString UTF8String]);
211 dispatch_semaphore_signal(sema);
214 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
215 printf("\n\nError: timed out waiting for response\n");
227 __block long ret = 0;
230 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
231 [self.control reset:^(BOOL reset, NSError* error){
234 printf("Error pushing: %s\n", [[error description] UTF8String]);
235 ret = (error.code == 0 ? -1 : error.code);
240 dispatch_semaphore_signal(sema);
243 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
244 printf("\n\nError: timed out waiting for response\n");
248 printf("Complete.\n");
256 - (long) listOfRecords
258 __block long ret = 0;
261 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
262 [self.control listOfRecords:^(NSArray* list, NSError* error){
265 printf("Error pushing: %s\n", [[error description] UTF8String]);
266 ret = (error.code == 0 ? -1 : error.code);
268 [list enumerateObjectsUsingBlock:^(NSString* _Nonnull escrowRecordID, NSUInteger idx, BOOL * _Nonnull stop) {
269 printf("escrowRecordID: %s\n", [escrowRecordID UTF8String]);
274 dispatch_semaphore_signal(sema);
277 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
278 printf("\n\nError: timed out waiting for response\n");
282 printf("Complete.\n");
292 __block long ret = 0;
295 dispatch_semaphore_t semaForGettingEncryptionKey = dispatch_semaphore_create(0);
296 dispatch_semaphore_t semaForGettingSigningKey = dispatch_semaphore_create(0);
297 [self.control encryptionKey:^(NSData *encryptionKey, NSError * error) {
300 printf("Error pushing: %s\n", [[error description] UTF8String]);
301 ret = (error.code == 0 ? -1 : error.code);
303 NSString* encryptionKeyString = [encryptionKey base64EncodedStringWithOptions:0];
304 printf("Encryption Key:\n %s\n", [encryptionKeyString UTF8String]);
308 dispatch_semaphore_signal(semaForGettingEncryptionKey);
311 [self.control signingKey:^(NSData *signingKey, NSError * error) {
314 printf("Error pushing: %s\n", [[error description] UTF8String]);
315 ret = (error.code == 0 ? -1 : error.code);
317 NSString* signingKeyString = [signingKey base64EncodedStringWithOptions:0];
318 printf("Signing Key:\n %s\n", [signingKeyString UTF8String]);
322 dispatch_semaphore_signal(semaForGettingSigningKey);
326 if(dispatch_semaphore_wait(semaForGettingEncryptionKey, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
327 printf("\n\nError: timed out waiting for response\n");
330 if(dispatch_semaphore_wait(semaForGettingSigningKey, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
331 printf("\n\nError: timed out waiting for response\n");
334 printf("Complete.\n");
344 static bool SecOTIsEnabled(void) {
346 bool userDefaultsShouldBottledPeer = true;
347 CFBooleanRef enabled = (CFBooleanRef)CFPreferencesCopyValue(CFSTR("EnableOTRestore"),
348 CFSTR("com.apple.security"),
349 kCFPreferencesAnyUser, kCFPreferencesAnyHost);
350 if(enabled && CFGetTypeID(enabled) == CFBooleanGetTypeID()){
351 if(enabled == kCFBooleanFalse){
352 secnotice("octagon", "Octagon Restore Disabled");
353 userDefaultsShouldBottledPeer = false;
355 if(enabled == kCFBooleanTrue){
356 secnotice("octagon", "Octagon Restore Enabled");
357 userDefaultsShouldBottledPeer = true;
361 CFReleaseNull(enabled);
362 return userDefaultsShouldBottledPeer;
365 static int enroll = false;
366 static int restore = false;
367 static int octagonkeys = false;
368 static int reset = false;
370 static int prepbp = false;
371 static int launch = false;
372 static int scrub = false;
374 static int listOfRecords = false;
375 static char* bottleIDArg = NULL;
376 static char* contextNameArg = NULL;
377 static char* secretArg = NULL;
379 int main(int argc, char **argv)
381 if(!SecIsInternalRelease())
383 secnotice("octagon", "Tool not available on non internal builds");
387 if(!SecOTIsEnabled())
389 printf("To use this tool, enable defaults write for EnableOTRestore\n defaults write (~)/Library/Preferences/com.apple.security EnableOTRestore -bool YES\n");
392 static struct argument options[] = {
393 { .shortname='s', .longname="secret", .argument=&secretArg, .description="escrow secret"},
394 { .shortname='e', .longname="bottleID", .argument=&bottleIDArg, .description="bottle record id"},
395 { .shortname='c', .longname="context", .argument=&contextNameArg, .description="context name"},
397 { .command="restore", .flag=&restore, .flagval=true, .description="Restore fake bottled peer"},
398 { .command="enroll", .flag=&enroll, .flagval=true, .description="Enroll fake bottled peer"},
399 { .command="keys", .flag=&octagonkeys, .shortname='k', .flagval=true, .description="Octagon Signing + Encryption Keys"},
400 { .command="reset", .flag=&reset, .flagval=true, .description="Reset Octagon Trust Zone"},
401 { .command="list", .flag=&listOfRecords, .flagval=true, .description="List of current Bottled Peer Records IDs"},
403 { .command="prepbp", .flag=&prepbp, .shortname='p', .flagval=true, .description="Preflights a bottled peer"},
404 { .command="launch", .flag=&launch, .flagval=true, .description="Launches a bottled peer"},
405 { .command="scrub", .flag=&scrub, .flagval=true, .description="Scrub bottled peer"},
410 static struct arguments args = {
411 .programname="otctl",
412 .description="Control and report on Octagon Trust",
413 .arguments = options,
416 if(!options_parse(argc, argv, &args)) {
423 NSError* error = nil;
425 OTControl* rpc = [OTControl controlObject:&error];
427 errx(1, "no OTControl failed: %s", [[error description] UTF8String]);
430 OTControlCLI* ctl = [[OTControlCLI alloc] initWithOTControl:rpc];
434 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
435 ret = [ctl enroll:context dsid:@"12345678"];
440 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
442 //requires secret, context is optional
443 ret = [ctl preflightBottledPeer:context dsid:@"12345678"];
449 NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
450 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
451 //requires bottleID, context is optional
452 if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
453 ret = [ctl launchBottledPeer:context bottleID:bottleID];
465 NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
466 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
468 //requires bottle ID, context is optional
469 if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
470 ret = [ctl scrubBottledPeer:context bottleID:bottleID];
481 NSData* secretData = nil;
482 NSString* secretString = secretArg ? [NSString stringWithCString: secretArg encoding: NSUTF8StringEncoding] : nil;
483 NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
484 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
486 //requires secret and bottle ID, context is optional
487 if(secretString && [secretString length] > 0){
488 secretData = [[NSData alloc] initWithBase64EncodedString:secretString options:0];;
496 if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
497 ret = [ctl restore:context dsid:@"12345678" secret:secretData escrowRecordID:bottleID];
507 ret = [ctl octagonKeys];
512 ret = [ctl listOfRecords];