]> git.saurik.com Git - apple/security.git/blob - keychain/otctl/OTControlCLI.m
Security-59306.101.1.tar.gz
[apple/security.git] / keychain / otctl / OTControlCLI.m
1
2
3 #import <Foundation/Foundation.h>
4 #import <Foundation/NSXPCConnection_Private.h>
5 #import <Security/SecItemPriv.h>
6 #import <Security/Security.h>
7 #import <xpc/xpc.h>
8
9 #include "utilities/SecCFWrappers.h"
10 #include "utilities/SecInternalReleasePriv.h"
11 #import "utilities/debugging.h"
12
13 #import "keychain/ot/OTClique.h"
14 #import "keychain/ot/OT.h"
15 #import "keychain/ot/OTConstants.h"
16 #import "keychain/ot/OTControl.h"
17 #import "keychain/otctl/OTControlCLI.h"
18
19
20 #import <AuthKit/AKAppleIDAuthenticationController.h>
21 #import <AuthKit/AKAppleIDAuthenticationContext.h>
22 #import <AuthKit/AKAppleIDAuthenticationContext_Private.h>
23
24 static NSString* fetch_pet(NSString* appleID, NSString* dsid)
25 {
26 if(!appleID && !dsid) {
27 NSLog(@"Must provide either an AppleID or a DSID to fetch a PET");
28 exit(1);
29 }
30
31 AKAppleIDAuthenticationContext* authContext = [[AKAppleIDAuthenticationContext alloc] init];
32 authContext.username = appleID;
33
34 authContext.authenticationType = AKAppleIDAuthenticationTypeSilent;
35 authContext.isUsernameEditable = NO;
36
37 __block NSString* pet = nil;
38
39 dispatch_semaphore_t s = dispatch_semaphore_create(0);
40
41 AKAppleIDAuthenticationController *authenticationController = [[AKAppleIDAuthenticationController alloc] init];
42 [authenticationController authenticateWithContext:authContext
43 completion:^(AKAuthenticationResults authenticationResults, NSError *error) {
44 if(error) {
45 NSLog(@"error fetching PET: %@", error);
46 exit(1);
47 }
48
49 pet = authenticationResults[AKAuthenticationPasswordKey];
50 dispatch_semaphore_signal(s);
51 }];
52 dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
53
54 return pet;
55 }
56
57 // Mutual recursion to set up an object for jsonification
58 static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict);
59
60 static id cleanObjectForJSON(id obj) {
61 if(!obj) {
62 return nil;
63 }
64 if([obj isKindOfClass:[NSError class]]) {
65 NSError* obje = (NSError*) obj;
66 NSMutableDictionary* newErrorDict = [@{@"code": @(obje.code), @"domain": obje.domain} mutableCopy];
67 newErrorDict[@"userInfo"] = cleanDictionaryForJSON(obje.userInfo);
68 return newErrorDict;
69 } else if([NSJSONSerialization isValidJSONObject:obj]) {
70 return obj;
71
72 } else if ([obj isKindOfClass: [NSNumber class]]) {
73 return obj;
74
75 } else if([obj isKindOfClass: [NSData class]]) {
76 NSData* dataObj = (NSData*)obj;
77 return [dataObj base64EncodedStringWithOptions:0];
78
79 } else if ([obj isKindOfClass: [NSDictionary class]]) {
80 return cleanDictionaryForJSON((NSDictionary*) obj);
81
82 } else if ([obj isKindOfClass: [NSArray class]]) {
83 NSArray* arrayObj = (NSArray*)obj;
84 NSMutableArray* cleanArray = [NSMutableArray arrayWithCapacity:arrayObj.count];
85
86 for(id x in arrayObj) {
87 [cleanArray addObject: cleanObjectForJSON(x)];
88 }
89 return cleanArray;
90
91 } else {
92 return [obj description];
93 }
94 }
95
96 static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict) {
97 if(!dict) {
98 return nil;
99 }
100 NSMutableDictionary* mutDict = [dict mutableCopy];
101 for(id key in mutDict.allKeys) {
102 id obj = mutDict[key];
103 mutDict[key] = cleanObjectForJSON(obj);
104 }
105 return mutDict;
106 }
107
108 static void print_json(NSDictionary* dict)
109 {
110 NSError* err;
111
112 NSData* json = [NSJSONSerialization dataWithJSONObject:cleanDictionaryForJSON(dict)
113 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
114 error:&err];
115 if(!json) {
116 NSLog(@"error during JSONification: %@", err.localizedDescription);
117 } else {
118 printf("%s\n", [[[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding] UTF8String]);
119 }
120 }
121
122
123 @implementation OTControlCLI
124
125 - (instancetype)initWithOTControl:(OTControl*)control {
126 if((self = [super init])) {
127 _control = control;
128 }
129
130 return self;
131 }
132
133 - (long)startOctagonStateMachine:(NSString*)container context:(NSString*)contextID {
134 #if OCTAGON
135 __block long ret = -1;
136
137 [self.control startOctagonStateMachine:container
138 context:contextID
139 reply:^(NSError* _Nullable error) {
140 if(error) {
141 printf("Error starting state machine: %s\n", [[error description] UTF8String]);
142 ret = -1;
143 } else {
144 printf("state machine started.\n");
145 }
146 }];
147
148 return ret;
149 #else
150 printf("Unimplemented.\n");
151 return -1;
152 #endif
153 }
154
155 - (long)signIn:(NSString*)altDSID container:(NSString* _Nullable)container context:(NSString*)contextID {
156 #if OCTAGON
157 __block long ret = -1;
158
159 [self.control signIn:altDSID
160 container:container
161 context:contextID
162 reply:^(NSError* _Nullable error) {
163 if(error) {
164 printf("Error signing in: %s\n", [[error description] UTF8String]);
165 } else {
166 printf("Sign in complete.\n");
167 ret = 0;
168 }
169 }];
170
171 return ret;
172 #else
173 printf("Unimplemented.\n");
174 return -1;
175 #endif
176 }
177
178 - (long)signOut:(NSString* _Nullable)container context:(NSString*)contextID {
179 #if OCTAGON
180 __block long ret = -1;
181 [self.control signOut:container
182 context:contextID
183 reply:^(NSError* _Nullable error) {
184 if(error) {
185 printf("Error signing out: %s\n", [[error description] UTF8String]);
186 } else {
187 printf("Sign out complete.\n");
188 ret = 0;
189 }
190 }];
191 return ret;
192 #else
193 printf("Unimplemented.\n");
194 return -1;
195 #endif
196 }
197
198 - (long)depart:(NSString* _Nullable)container context:(NSString*)contextID {
199 #if OCTAGON
200 __block long ret = -1;
201
202 [self.control leaveClique:container
203 context:contextID
204 reply:^(NSError* _Nullable error) {
205 if(error) {
206 printf("Error departing clique: %s\n", [[error description] UTF8String]);
207 } else {
208 printf("Departing clique completed.\n");
209 ret = 0;
210 }
211 }];
212 return ret;
213 #else
214 printf("Unimplemented.\n");
215 return -1;
216 #endif
217 }
218
219 - (long)resetOctagon:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID {
220 #if OCTAGON
221 __block long ret = -1;
222
223 [self.control resetAndEstablish:container
224 context:contextID
225 altDSID:altDSID
226 resetReason:CuttlefishResetReasonUserInitiatedReset
227 reply:^(NSError* _Nullable error) {
228 if(error) {
229 printf("Error resetting: %s\n", [[error description] UTF8String]);
230 } else {
231 printf("reset and establish:\n");
232 ret = 0;
233 }
234 }];
235
236 return ret;
237 #else
238 printf("Unimplemented.\n");
239 return -1;
240 #endif
241 }
242
243
244 - (long)resetProtectedData:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID appleID:(NSString*)appleID dsid:(NSString*)dsid
245 {
246 #if OCTAGON
247 __block long ret = -1;
248
249 NSError* error = nil;
250 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
251 data.passwordEquivalentToken = fetch_pet(appleID, dsid);
252 data.authenticationAppleID = appleID;
253 data.altDSID = altDSID;
254 data.context = contextID;
255
256 OTClique* clique = [OTClique resetProtectedData:data error:&error];
257 if(clique != nil && error == nil) {
258 ret = 0;
259 }
260 return ret;
261 #else
262 printf("Unimplemented.\n");
263 return -1;
264 #endif
265 }
266
267 - (void)printPeer:(NSDictionary*)peerInformation prefix:(NSString* _Nullable)prefix {
268 NSString* peerID = peerInformation[@"peerID"];
269 NSString* model = peerInformation[@"permanentInfo"][@"model_id"];
270 NSNumber* epoch = peerInformation[@"permanentInfo"][@"epoch"];
271 NSString* deviceName = peerInformation[@"stableInfo"][@"device_name"];
272 NSString* serialNumber = peerInformation[@"stableInfo"][@"serial_number"];
273 NSString* os = peerInformation[@"stableInfo"][@"os_version"];
274
275 printf("%s%s hw:'%s' name:'%s' serial: '%s' os:'%s' epoch:%d\n",
276 (prefix ? [prefix UTF8String] : ""),
277 [peerID UTF8String],
278 [model UTF8String],
279 [deviceName UTF8String],
280 [serialNumber UTF8String],
281 [os UTF8String],
282 [epoch intValue]);
283 }
284
285 - (void)printPeers:(NSArray<NSString*>*)peerIDs
286 egoPeerID:(NSString* _Nullable)egoPeerID
287 informationOnPeers:(NSDictionary<NSString*, NSDictionary*>*)informationOnPeers {
288 for(NSString* peerID in peerIDs) {
289 NSDictionary* peerInformation = informationOnPeers[peerID];
290
291 if(!peerInformation) {
292 printf(" Peer: %s; further information missing\n", [peerID UTF8String]);
293 continue;
294 }
295
296 if([peerID isEqualToString:egoPeerID]) {
297 [self printPeer:peerInformation prefix:@" Self: "];
298 } else {
299 [self printPeer:peerInformation prefix:@" Peer: "];
300 }
301 }
302 }
303
304 - (long)status:(NSString* _Nullable)container context:(NSString*)contextID json:(bool)json {
305 #if OCTAGON
306 __block long ret = 0;
307
308 [self.control status:container
309 context:contextID
310 reply:^(NSDictionary* result, NSError* _Nullable error) {
311 if(error) {
312 if(json) {
313 print_json(@{@"error" : [error description]});
314 } else {
315 printf("Error fetching status: %s\n", [[error description] UTF8String]);
316 }
317 ret = -1;
318 } else {
319 if(json) {
320 print_json(result);
321 } else {
322 printf("Status for %s,%s:\n", [result[@"containerName"] UTF8String], [result[@"contextID"] UTF8String]);
323
324 printf("\n");
325 printf("State: %s\n", [[result[@"state"] description] UTF8String]);
326 printf("Flags: %s; Flags Pending: %s\n\n",
327 ([result[@"stateFlags"] count] == 0u) ? "none" : [[result[@"stateFlags"] description] UTF8String],
328 ([result[@"statePendingFlags"] count] == 0u) ? "none" : [[result[@"statePendingFlags"] description] UTF8String]);
329
330 NSDictionary* contextDump = result[@"contextDump"];
331
332 // Make it easy to find peer information
333 NSMutableDictionary<NSString*, NSDictionary*>* peers = [NSMutableDictionary dictionary];
334 NSMutableArray<NSString*>* allPeerIDs = [NSMutableArray array];
335 for(NSDictionary* peerInformation in contextDump[@"peers"]) {
336 NSString* peerID = peerInformation[@"peerID"];
337 if(peerID) {
338 peers[peerID] = peerInformation;
339 [allPeerIDs addObject:peerID];
340 }
341 }
342
343 NSDictionary* egoInformation = contextDump[@"self"];
344 NSString* egoPeerID = egoInformation[@"peerID"];
345 NSDictionary* egoDynamicInfo = egoInformation[@"dynamicInfo"];
346
347 if(egoPeerID) {
348 NSMutableArray *otherPeers = [allPeerIDs mutableCopy];
349 [self printPeer:egoInformation prefix:@" Self: "];
350 printf("\n");
351
352 // The self peer is technically a peer, so, shove it on in there
353 peers[egoPeerID] = egoInformation;
354
355 NSArray<NSString*>* includedPeers = egoDynamicInfo[@"included"];
356 printf("Trusted peers (by me):\n");
357 if(includedPeers && includedPeers.count > 0) {
358 [self printPeers:includedPeers egoPeerID:egoPeerID informationOnPeers:peers];
359 [otherPeers removeObjectsInArray:includedPeers];
360 } else {
361 printf(" No trusted peers.\n");
362 }
363 printf("\n");
364
365 NSArray<NSString*>* excludedPeers = egoDynamicInfo[@"excluded"];
366 printf("Excluded peers (by me):\n");
367 if(excludedPeers && excludedPeers.count > 0) {
368 [self printPeers:excludedPeers egoPeerID:egoPeerID informationOnPeers:peers];
369 [otherPeers removeObjectsInArray:excludedPeers];
370 } else {
371 printf(" No excluded peers.\n");
372 }
373 printf("\n");
374
375 printf("Other peers (included/excluded by others):\n");
376 if(otherPeers.count > 0) {
377 [self printPeers:otherPeers egoPeerID:egoPeerID informationOnPeers:peers];
378 } else {
379 printf(" No other peers.\n");
380 }
381 printf("\n");
382
383 } else {
384 printf("No current identity for this device.\n\n");
385
386 if(allPeerIDs.count > 0) {
387 printf("All peers currently in this account:\n");
388 [self printPeers:allPeerIDs egoPeerID:nil informationOnPeers:peers];
389 } else {
390 printf("No peers currently exist for this account.\n");
391 }
392 }
393
394 printf("\n");
395 }
396 }
397 }];
398
399 return ret;
400 #else
401 printf("Unimplemented.\n");
402 return -1;
403 #endif
404 }
405
406 - (long)recoverUsingBottleID:(NSString*)bottleID
407 entropy:(NSData*)entropy
408 altDSID:(NSString*)altDSID
409 containerName:(NSString*)containerName
410 context:(NSString*)context
411 control:(OTControl*)control {
412 __block long ret = 0;
413
414 #if OCTAGON
415 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
416
417 [control restore:containerName
418 contextID:context
419 bottleSalt:altDSID
420 entropy:entropy
421 bottleID:bottleID
422 reply:^(NSError* _Nullable error) {
423 if(error) {
424 ret = -1;
425 printf("Error recovering: %s\n", [[error description] UTF8String]);
426 } else {
427 printf("Succeeded recovering bottled peer %s\n", [[bottleID description] UTF8String]);
428 }
429 dispatch_semaphore_signal(sema);
430 }];
431
432 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
433 printf("timed out waiting for restore/recover\n");
434 ret = -1;
435 }
436
437 return ret;
438 #else
439 ret = -1;
440 return ret;
441 #endif
442 }
443
444 - (long)fetchAllBottles:(NSString*)altDSID
445 containerName:(NSString*)containerName
446 context:(NSString*)context
447 control:(OTControl*)control {
448 __block long ret = 0;
449
450 #if OCTAGON
451 __block NSError* localError = nil;
452
453 __block NSArray<NSString*>* localViableBottleIDs = nil;
454 __block NSArray<NSString*>* localPartiallyViableBottleIDs = nil;
455
456 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
457
458 [control fetchAllViableBottles:containerName
459 context:context
460 reply:^(NSArray<NSString*>* _Nullable sortedBottleIDs,
461 NSArray<NSString*>* _Nullable sortedPartialBottleIDs,
462 NSError* _Nullable controlError) {
463 if(controlError) {
464 secnotice("clique", "findOptimalBottleIDsWithContextData errored: %@\n", controlError);
465 } else {
466 secnotice("clique", "findOptimalBottleIDsWithContextData succeeded: %@, %@\n", sortedBottleIDs, sortedPartialBottleIDs);
467 }
468 localError = controlError;
469 localViableBottleIDs = sortedBottleIDs;
470 localPartiallyViableBottleIDs = sortedPartialBottleIDs;
471 dispatch_semaphore_signal(sema);
472 }];
473
474 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
475 secnotice("clique", "findOptimalBottleIDsWithContextData failed to fetch bottles\n");
476 return -1;
477 }
478
479 [localViableBottleIDs enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL* stop) {
480 printf("preferred bottleID: %s\n", [obj UTF8String]);
481 }];
482
483 [localPartiallyViableBottleIDs enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL* stop) {
484 printf("partial recovery bottleID: %s\n", [obj UTF8String]);
485 }];
486
487 return ret;
488 #else
489 ret = -1;
490 return ret;
491 #endif
492 }
493
494 - (long)healthCheck:(NSString* _Nullable)container context:(NSString*)contextID skipRateLimitingCheck:(BOOL)skipRateLimitingCheck
495 {
496 #if OCTAGON
497 __block long ret = -1;
498
499 [self.control healthCheck:container
500 context:contextID
501 skipRateLimitingCheck:skipRateLimitingCheck
502 reply:^(NSError* _Nullable error) {
503 if(error) {
504 printf("Error checking health: %s\n", [[error description] UTF8String]);
505 } else {
506 printf("Checking Octagon Health completed.\n");
507 ret = 0;
508 }
509 }];
510 return ret;
511 #else
512 printf("Unimplemented.\n");
513 return -1;
514 #endif
515 }
516
517 - (long)refetchCKKSPolicy:(NSString*)container context:(NSString*)contextID
518 {
519 #if OCTAGON
520 __block long ret = 1;
521
522 [self.control refetchCKKSPolicy:container
523 contextID:contextID
524 reply:^(NSError * _Nullable error) {
525 if(error) {
526 printf("Error refetching CKKS policy: %s\n", [[error description] UTF8String]);
527 } else {
528 printf("CKKS refetch completed.\n");
529 ret = 0;
530 }
531 }];
532 return ret;
533 #else
534 printf("Unimplemented.\n");
535 return 1;
536 #endif
537 }
538
539 - (long)tapToRadar:(NSString *)action description:(NSString *)description radar:(NSString *)radar
540 {
541 #if OCTAGON
542 __block long ret = 1;
543
544 [self.control tapToRadar:action
545 description:description
546 radar:radar
547 reply:^(NSError* _Nullable error) {
548 if(error) {
549 printf("Error trigger TTR: %s\n", [[error description] UTF8String]);
550 } else {
551 printf("Trigger TTR completed.\n");
552 ret = 0;
553 }
554 }];
555 return ret;
556 #else
557 printf("Unimplemented.\n");
558 return 1;
559 #endif
560 }
561
562 @end