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