5 #import <Foundation/Foundation.h>
6 #import <Foundation/NSXPCConnection_Private.h>
7 #import <Security/Security.h>
8 #import <Security/SecItemPriv.h>
12 #import <utilities/debugging.h>
13 #import "keychain/ot/OTControl.h"
14 #import "keychain/ot/OTConstants.h"
15 #include "lib/SecArgParse.h"
16 #include <utilities/SecCFWrappers.h>
17 #include <utilities/SecInternalReleasePriv.h>
19 @interface OTControlCLI : NSObject
20 @property OTControl* control;
23 @implementation OTControlCLI
26 - (instancetype) initWithOTControl:(OTControl*)control {
27 if ((self = [super init])) {
34 - (long)preflightBottledPeer:(NSString*)contextID dsid:(NSString*)dsid
39 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
41 [self.control preflightBottledPeer:contextID dsid:dsid reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
43 printf("Error pushing: %s\n", [[error description] UTF8String]);
44 ret = (error.code == 0 ? -1 : error.code);
45 }else if(entropy && bottleID && signingPublicKey){
46 printf("\nSuccessfully preflighted bottle ID: %s\n", [bottleID UTF8String]);
47 printf("\nEntropy used: %s\n", [[entropy base64EncodedStringWithOptions:0] UTF8String]);
48 printf("\nSigning Public Key: %s\n", [[signingPublicKey base64EncodedStringWithOptions:0] UTF8String]);
51 printf("Failed to preflight bottle and no error was returned..");
55 dispatch_semaphore_signal(sema);
58 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
59 printf("\n\nError: timed out waiting for response\n");
68 - (long)launchBottledPeer:(NSString*)contextID bottleID:(NSString*)bottleID
73 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
75 [self.control launchBottledPeer:contextID bottleID:bottleID reply:^(NSError * _Nullable error) {
78 printf("Error pushing: %s\n", [[error description] UTF8String]);
79 ret = (error.code == 0 ? -1 : error.code);
81 printf("\nSuccessfully launched bottleID: %s\n", [bottleID UTF8String]);
85 dispatch_semaphore_signal(sema);
88 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
89 printf("\n\nError: timed out waiting for response\n");
98 - (long)scrubBottledPeer:(NSString*)contextID bottleID:(NSString*)bottleID
100 __block long ret = 0;
103 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
105 [self.control scrubBottledPeer:contextID bottleID:bottleID reply:^(NSError * _Nullable error) {
108 printf("Error pushing: %s\n", [[error description] UTF8String]);
109 ret = (error.code == 0 ? -1 : error.code);
111 printf("\nSuccessfully scrubbed bottle ID: %s\n", [bottleID UTF8String]);
115 dispatch_semaphore_signal(sema);
118 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
119 printf("\n\nError: timed out waiting for response\n");
130 - (long)enroll:(NSString*)contextID dsid:(NSString*)dsid
132 __block long ret = 0;
135 dispatch_semaphore_t semaForPreFlight = dispatch_semaphore_create(0);
136 dispatch_semaphore_t semaForLaunch = dispatch_semaphore_create(0);
137 __block NSString* bottleRecordID = nil;
138 __block NSError* localError = nil;
140 [self.control preflightBottledPeer:contextID dsid:dsid reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
144 printf("Error pushing: %s\n", [[error description] UTF8String]);
145 ret = (error.code == 0 ? -1 : error.code);
147 bottleRecordID = bottleID;
148 printf("\nSuccessfully preflighted bottle ID: %s\n", [bottleID UTF8String]);
149 printf("\nEntropy used: %s\n", [[entropy base64EncodedStringWithOptions:0] UTF8String]);
150 printf("\nSigning Public Key: %s\n", [[signingPublicKey base64EncodedStringWithOptions:0] UTF8String]);
154 dispatch_semaphore_signal(semaForPreFlight);
157 if(dispatch_semaphore_wait(semaForPreFlight, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
158 printf("\n\nError: timed out waiting for response\n");
162 if(localError == nil){
163 [self.control launchBottledPeer:contextID bottleID:bottleRecordID reply:^(NSError * _Nullable error) {
166 printf("Error pushing: %s\n", [[error description] UTF8String]);
167 ret = (error.code == 0 ? -1 : error.code);
169 printf("\nSuccessfully launched bottleID: %s\n", [bottleRecordID UTF8String]);
173 dispatch_semaphore_signal(semaForLaunch);
176 if(dispatch_semaphore_wait(semaForLaunch, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
177 printf("\n\nError: timed out waiting for response\n");
181 printf("Complete.\n");
189 - (long)restore:(NSString*)contextID dsid:(NSString*)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID
191 __block long ret = 0;
194 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
196 [self.control restore:contextID dsid:dsid secret:secret escrowRecordID:escrowRecordID reply:^(NSData* signingKeyData, NSData* encryptionKeyData, NSError *error) {
199 printf("Error pushing: %s\n", [[error description] UTF8String]);
200 ret = (error.code == 0 ? -1 : error.code);
203 printf("Complete.\n");
207 NSString* signingKeyString = [signingKeyData base64EncodedStringWithOptions:0];
208 NSString* encryptionKeyString = [encryptionKeyData base64EncodedStringWithOptions:0];
210 printf("Signing Key:\n %s\n", [signingKeyString UTF8String]);
211 printf("Encryption Key:\n %s\n", [encryptionKeyString UTF8String]);
212 dispatch_semaphore_signal(sema);
215 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
216 printf("\n\nError: timed out waiting for response\n");
228 __block long ret = 0;
231 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
232 [self.control reset:^(BOOL reset, NSError* error){
235 printf("Error pushing: %s\n", [[error description] UTF8String]);
236 ret = (error.code == 0 ? -1 : error.code);
241 dispatch_semaphore_signal(sema);
244 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
245 printf("\n\nError: timed out waiting for response\n");
249 printf("Complete.\n");
257 - (long) listOfRecords
259 __block long ret = 0;
262 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
263 [self.control listOfRecords:^(NSArray* list, NSError* error){
266 printf("Error pushing: %s\n", [[error description] UTF8String]);
267 ret = (error.code == 0 ? -1 : error.code);
269 [list enumerateObjectsUsingBlock:^(NSString* _Nonnull escrowRecordID, NSUInteger idx, BOOL * _Nonnull stop) {
270 printf("escrowRecordID: %s\n", [escrowRecordID UTF8String]);
275 dispatch_semaphore_signal(sema);
278 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
279 printf("\n\nError: timed out waiting for response\n");
283 printf("Complete.\n");
293 __block long ret = 0;
296 dispatch_semaphore_t semaForGettingEncryptionKey = dispatch_semaphore_create(0);
297 dispatch_semaphore_t semaForGettingSigningKey = dispatch_semaphore_create(0);
298 [self.control encryptionKey:^(NSData *encryptionKey, NSError * error) {
301 printf("Error pushing: %s\n", [[error description] UTF8String]);
302 ret = (error.code == 0 ? -1 : error.code);
304 NSString* encryptionKeyString = [encryptionKey base64EncodedStringWithOptions:0];
305 printf("Encryption Key:\n %s\n", [encryptionKeyString UTF8String]);
309 dispatch_semaphore_signal(semaForGettingEncryptionKey);
312 [self.control signingKey:^(NSData *signingKey, NSError * error) {
315 printf("Error pushing: %s\n", [[error description] UTF8String]);
316 ret = (error.code == 0 ? -1 : error.code);
318 NSString* signingKeyString = [signingKey base64EncodedStringWithOptions:0];
319 printf("Signing Key:\n %s\n", [signingKeyString UTF8String]);
323 dispatch_semaphore_signal(semaForGettingSigningKey);
327 if(dispatch_semaphore_wait(semaForGettingEncryptionKey, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
328 printf("\n\nError: timed out waiting for response\n");
331 if(dispatch_semaphore_wait(semaForGettingSigningKey, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 65)) != 0) {
332 printf("\n\nError: timed out waiting for response\n");
335 printf("Complete.\n");
345 static int enroll = false;
346 static int restore = false;
347 static int octagonkeys = false;
348 static int reset = false;
350 static int prepbp = false;
351 static int launch = false;
352 static int scrub = false;
354 static int listOfRecords = false;
355 static char* bottleIDArg = NULL;
356 static char* contextNameArg = NULL;
357 static char* secretArg = NULL;
359 int main(int argc, char **argv)
361 if(!SecIsInternalRelease())
363 secnotice("octagon", "Tool not available on non internal builds");
367 if(!SecOTIsEnabled())
369 printf("To use this tool, enable defaults write for EnableOTRestore\n defaults write (~)/Library/Preferences/com.apple.security EnableOTRestore -bool YES\n");
372 static struct argument options[] = {
373 { .shortname='s', .longname="secret", .argument=&secretArg, .description="escrow secret"},
374 { .shortname='e', .longname="bottleID", .argument=&bottleIDArg, .description="bottle record id"},
375 { .shortname='c', .longname="context", .argument=&contextNameArg, .description="context name"},
377 { .command="restore", .flag=&restore, .flagval=true, .description="Restore fake bottled peer"},
378 { .command="enroll", .flag=&enroll, .flagval=true, .description="Enroll fake bottled peer"},
379 { .command="keys", .flag=&octagonkeys, .shortname='k', .flagval=true, .description="Octagon Signing + Encryption Keys"},
380 { .command="reset", .flag=&reset, .flagval=true, .description="Reset Octagon Trust Zone"},
381 { .command="list", .flag=&listOfRecords, .flagval=true, .description="List of current Bottled Peer Records IDs"},
383 { .command="prepbp", .flag=&prepbp, .shortname='p', .flagval=true, .description="Preflights a bottled peer"},
384 { .command="launch", .flag=&launch, .flagval=true, .description="Launches a bottled peer"},
385 { .command="scrub", .flag=&scrub, .flagval=true, .description="Scrub bottled peer"},
390 static struct arguments args = {
391 .programname="otctl",
392 .description="Control and report on Octagon Trust",
393 .arguments = options,
396 if(!options_parse(argc, argv, &args)) {
403 NSError* error = nil;
405 OTControl* rpc = [OTControl controlObject:&error];
407 errx(1, "no OTControl failed: %s", [[error description] UTF8String]);
410 OTControlCLI* ctl = [[OTControlCLI alloc] initWithOTControl:rpc];
414 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
415 ret = [ctl enroll:context dsid:@"12345678"];
420 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
422 //requires secret, context is optional
423 ret = [ctl preflightBottledPeer:context dsid:@"12345678"];
429 NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
430 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
431 //requires bottleID, context is optional
432 if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
433 ret = [ctl launchBottledPeer:context bottleID:bottleID];
445 NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
446 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
448 //requires bottle ID, context is optional
449 if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
450 ret = [ctl scrubBottledPeer:context bottleID:bottleID];
461 NSData* secretData = nil;
462 NSString* secretString = secretArg ? [NSString stringWithCString: secretArg encoding: NSUTF8StringEncoding] : nil;
463 NSString* bottleID = bottleIDArg ? [NSString stringWithCString: bottleIDArg encoding: NSUTF8StringEncoding] : nil;
464 NSString* context = contextNameArg ? [NSString stringWithCString: contextNameArg encoding: NSUTF8StringEncoding] : OTDefaultContext;
466 //requires secret and bottle ID, context is optional
467 if(secretString && [secretString length] > 0){
468 secretData = [[NSData alloc] initWithBase64EncodedString:secretString options:0];;
476 if(bottleID && [bottleID length] > 0 && ![bottleID isEqualToString:@"(null)"]){
477 ret = [ctl restore:context dsid:@"12345678" secret:secretData escrowRecordID:bottleID];
487 ret = [ctl octagonKeys];
492 ret = [ctl listOfRecords];