]> git.saurik.com Git - apple/security.git/blob - keychain/otctl/OTControlCLI.m
Security-59306.11.20.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 reply:^(NSError* _Nullable error) {
188 if(error) {
189 printf("Error resetting: %s\n", [[error description] UTF8String]);
190 } else {
191 printf("reset and establish:\n");
192 ret = 0;
193 }
194 }];
195
196 return ret;
197 #else
198 printf("Unimplemented.\n");
199 return -1;
200 #endif
201 }
202
203 - (void)printPeer:(NSDictionary*)peerInformation prefix:(NSString* _Nullable)prefix {
204 NSString* peerID = peerInformation[@"peerID"];
205 NSString* model = peerInformation[@"permanentInfo"][@"model_id"];
206 NSNumber* epoch = peerInformation[@"permanentInfo"][@"epoch"];
207 NSString* deviceName = peerInformation[@"stableInfo"][@"device_name"];
208 NSString* serialNumber = peerInformation[@"stableInfo"][@"serial_number"];
209 NSString* os = peerInformation[@"stableInfo"][@"os_version"];
210
211 printf("%s%s hw:'%s' name:'%s' serial: '%s' os:'%s' epoch:%d\n",
212 (prefix ? [prefix UTF8String] : ""),
213 [peerID UTF8String],
214 [model UTF8String],
215 [deviceName UTF8String],
216 [serialNumber UTF8String],
217 [os UTF8String],
218 [epoch intValue]);
219 }
220
221 - (void)printPeers:(NSArray<NSString*>*)peerIDs
222 egoPeerID:(NSString* _Nullable)egoPeerID
223 informationOnPeers:(NSDictionary<NSString*, NSDictionary*>*)informationOnPeers {
224 for(NSString* peerID in peerIDs) {
225 NSDictionary* peerInformation = informationOnPeers[peerID];
226
227 if(!peerInformation) {
228 printf(" Peer: %s; further information missing\n", [peerID UTF8String]);
229 continue;
230 }
231
232 if([peerID isEqualToString:egoPeerID]) {
233 [self printPeer:peerInformation prefix:@" Self: "];
234 } else {
235 [self printPeer:peerInformation prefix:@" Peer: "];
236 }
237 }
238 }
239
240 - (long)status:(NSString* _Nullable)container context:(NSString*)contextID json:(bool)json {
241 #if OCTAGON
242 __block long ret = 0;
243
244 [self.control status:container
245 context:contextID
246 reply:^(NSDictionary* result, NSError* _Nullable error) {
247 if(error) {
248 if(json) {
249 print_json(@{@"error" : [error description]});
250 } else {
251 printf("Error fetching status: %s\n", [[error description] UTF8String]);
252 }
253 ret = -1;
254 } else {
255 if(json) {
256 print_json(result);
257 } else {
258 printf("Status for %s,%s:\n", [result[@"containerName"] UTF8String], [result[@"contextID"] UTF8String]);
259
260 printf("\n");
261 printf("State: %s\n", [[result[@"state"] description] UTF8String]);
262 printf("Flags: %s; Flags Pending: %s\n\n",
263 ([result[@"stateFlags"] count] == 0u) ? "none" : [[result[@"stateFlags"] description] UTF8String],
264 ([result[@"statePendingFlags"] count] == 0u) ? "none" : [[result[@"statePendingFlags"] description] UTF8String]);
265
266 NSDictionary* contextDump = result[@"contextDump"];
267
268 // Make it easy to find peer information
269 NSMutableDictionary<NSString*, NSDictionary*>* peers = [NSMutableDictionary dictionary];
270 NSMutableArray<NSString*>* allPeerIDs = [NSMutableArray array];
271 for(NSDictionary* peerInformation in contextDump[@"peers"]) {
272 NSString* peerID = peerInformation[@"peerID"];
273 if(peerID) {
274 peers[peerID] = peerInformation;
275 [allPeerIDs addObject:peerID];
276 }
277 }
278
279 NSDictionary* egoInformation = contextDump[@"self"];
280 NSString* egoPeerID = egoInformation[@"peerID"];
281 NSDictionary* egoDynamicInfo = egoInformation[@"dynamicInfo"];
282
283 if(egoPeerID) {
284 NSMutableArray *otherPeers = [allPeerIDs mutableCopy];
285 [self printPeer:egoInformation prefix:@" Self: "];
286 printf("\n");
287
288 // The self peer is technically a peer, so, shove it on in there
289 peers[egoPeerID] = egoInformation;
290
291 NSArray<NSString*>* includedPeers = egoDynamicInfo[@"included"];
292 printf("Trusted peers (by me):\n");
293 if(includedPeers && includedPeers.count > 0) {
294 [self printPeers:includedPeers egoPeerID:egoPeerID informationOnPeers:peers];
295 [otherPeers removeObjectsInArray:includedPeers];
296 } else {
297 printf(" No trusted peers.\n");
298 }
299 printf("\n");
300
301 NSArray<NSString*>* excludedPeers = egoDynamicInfo[@"excluded"];
302 printf("Excluded peers (by me):\n");
303 if(excludedPeers && excludedPeers.count > 0) {
304 [self printPeers:excludedPeers egoPeerID:egoPeerID informationOnPeers:peers];
305 [otherPeers removeObjectsInArray:excludedPeers];
306 } else {
307 printf(" No excluded peers.\n");
308 }
309 printf("\n");
310
311 printf("Other peers (included/excluded by others):\n");
312 if(otherPeers.count > 0) {
313 [self printPeers:otherPeers egoPeerID:egoPeerID informationOnPeers:peers];
314 } else {
315 printf(" No other peers.\n");
316 }
317 printf("\n");
318
319 } else {
320 printf("No current identity for this device.\n\n");
321
322 if(allPeerIDs.count > 0) {
323 printf("All peers currently in this account:\n");
324 [self printPeers:allPeerIDs egoPeerID:nil informationOnPeers:peers];
325 } else {
326 printf("No peers currently exist for this account.\n");
327 }
328 }
329
330 printf("\n");
331 }
332 }
333 }];
334
335 return ret;
336 #else
337 printf("Unimplemented.\n");
338 return -1;
339 #endif
340 }
341
342 - (long)recoverUsingBottleID:(NSString*)bottleID
343 entropy:(NSData*)entropy
344 altDSID:(NSString*)altDSID
345 containerName:(NSString*)containerName
346 context:(NSString*)context
347 control:(OTControl*)control {
348 __block long ret = 0;
349
350 #if OCTAGON
351 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
352
353 [control restore:containerName
354 contextID:context
355 bottleSalt:altDSID
356 entropy:entropy
357 bottleID:bottleID
358 reply:^(NSError* _Nullable error) {
359 if(error) {
360 ret = -1;
361 printf("Error recovering: %s\n", [[error description] UTF8String]);
362 } else {
363 printf("Succeeded recovering bottled peer %s\n", [[bottleID description] UTF8String]);
364 }
365 dispatch_semaphore_signal(sema);
366 }];
367
368 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
369 printf("timed out waiting for restore/recover\n");
370 ret = -1;
371 }
372
373 return ret;
374 #else
375 ret = -1;
376 return ret;
377 #endif
378 }
379
380 - (long)fetchAllBottles:(NSString*)altDSID
381 containerName:(NSString*)containerName
382 context:(NSString*)context
383 control:(OTControl*)control {
384 __block long ret = 0;
385
386 #if OCTAGON
387 __block NSError* localError = nil;
388
389 __block NSArray<NSString*>* localViableBottleIDs = nil;
390 __block NSArray<NSString*>* localPartiallyViableBottleIDs = nil;
391
392 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
393
394 [control fetchAllViableBottles:containerName
395 context:context
396 reply:^(NSArray<NSString*>* _Nullable sortedBottleIDs,
397 NSArray<NSString*>* _Nullable sortedPartialBottleIDs,
398 NSError* _Nullable controlError) {
399 if(controlError) {
400 secnotice("clique", "findOptimalBottleIDsWithContextData errored: %@\n", controlError);
401 } else {
402 secnotice("clique", "findOptimalBottleIDsWithContextData succeeded: %@, %@\n", sortedBottleIDs, sortedPartialBottleIDs);
403 }
404 localError = controlError;
405 localViableBottleIDs = sortedBottleIDs;
406 localPartiallyViableBottleIDs = sortedPartialBottleIDs;
407 dispatch_semaphore_signal(sema);
408 }];
409
410 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
411 secnotice("clique", "findOptimalBottleIDsWithContextData failed to fetch bottles\n");
412 return -1;
413 }
414
415 [localViableBottleIDs enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL* stop) {
416 printf("preferred bottleID: %s\n", [obj UTF8String]);
417 }];
418
419 [localPartiallyViableBottleIDs enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL* stop) {
420 printf("partial recovery bottleID: %s\n", [obj UTF8String]);
421 }];
422
423 return ret;
424 #else
425 ret = -1;
426 return ret;
427 #endif
428 }
429
430 - (long)healthCheck:(NSString* _Nullable)container context:(NSString*)contextID skipRateLimitingCheck:(BOOL)skipRateLimitingCheck
431 {
432 #if OCTAGON
433 __block long ret = -1;
434
435 [self.control healthCheck:container
436 context:contextID
437 skipRateLimitingCheck:skipRateLimitingCheck
438 reply:^(NSError* _Nullable error) {
439 if(error) {
440 printf("Error checking health: %s\n", [[error description] UTF8String]);
441 } else {
442 printf("Checking Octagon Health completed.\n");
443 ret = 0;
444 }
445 }];
446 return ret;
447 #else
448 printf("Unimplemented.\n");
449 return -1;
450 #endif
451 }
452
453 - (long)tapToRadar:(NSString *)action description:(NSString *)description radar:(NSString *)radar
454 {
455 #if OCTAGON
456 __block long ret = 1;
457
458 [self.control tapToRadar:action
459 description:description
460 radar:radar
461 reply:^(NSError* _Nullable error) {
462 if(error) {
463 printf("Error trigger TTR: %s\n", [[error description] UTF8String]);
464 } else {
465 printf("Trigger TTR completed.\n");
466 ret = 0;
467 }
468 }];
469 return ret;
470 #else
471 printf("Unimplemented.\n");
472 return 1;
473 #endif
474 }
475
476 @end