]>
Commit | Line | Data |
---|---|---|
b54c578e A |
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 |