/ keychain / tpctl / main.swift
main.swift
  1  import Foundation
  2  import os
  3  
  4  let logger = Logger(subsystem: "com.apple.security.trustedpeers", category: "tpctl")
  5  
  6  // This should definitely use the ArgumentParser library from the Utility package.
  7  // However, right now that's not accessible from this code due to build system issues.
  8  // Do it the hard way.
  9  
 10  let programName = CommandLine.arguments[0]
 11  var args: ArraySlice<String> = CommandLine.arguments[1...]
 12  
 13  var context: String = OTDefaultContext
 14  var container: String = "com.apple.security.keychain"
 15  
 16  // Only used for prepare, but in the absence of a real command line library this is the easiest way to get them
 17  var modelID: String?
 18  var machineID: String?
 19  var epoch: UInt64 = 1
 20  var bottleSalt: String = ""
 21  
 22  // Only used for join, establish and update
 23  var preapprovedKeys: [Data]?
 24  var deviceName: String?
 25  var serialNumber: String?
 26  var osVersion: String?
 27  var policySecrets: [String: Data]?
 28  
 29  enum Command {
 30      case dump
 31      case depart
 32      case distrust(Set<String>)
 33      case join(Data, Data)
 34      case establish
 35      case localReset
 36      case prepare
 37      case healthInquiry
 38      case update
 39      case reset
 40      case validate
 41      case viableBottles
 42      case vouch(String, Data, Data, Data, Data)
 43      case vouchWithBottle(String, Data, String)
 44      case allow(Set<String>, Bool)
 45      case supportApp
 46  }
 47  
 48  func printUsage() {
 49      print("Usage:", (CommandLine.arguments[0] as NSString).lastPathComponent,
 50            "[--container CONTAINER] [--context CONTEXT] COMMAND")
 51      print()
 52      print("Commands:")
 53      print("  allow [--idms] MACHINEID ...")
 54      print("                            Set the (space-separated) list of machine IDs allowed in the account. If --idms is provided, append the IDMS trusted device list")
 55      print("  dump                      Print the state of the world as this peer knows it")
 56      print("  depart                    Attempt to depart the account and mark yourself as untrusted")
 57      print("  distrust PEERID ...       Distrust one or more peers by peer ID")
 58      print("  establish                 Calls Cuttlefish Establish, creating a new account-wide trust arena with a single peer (previously generated with prepare")
 59      print("  healthInquiry             Request peers to check in with reportHealth")
 60      print("  join VOUCHER VOUCHERSIG   Join a circle using this (base64) voucher and voucherSig")
 61      print("  local-reset               Resets the local cuttlefish database, and ignores all previous information. Does not change anything off-device")
 62      print("  prepare [--modelid MODELID] [--machineid MACHINEID] [--epoch EPOCH] [--bottlesalt BOTTLESALT]")
 63      print("                            Creates a new identity and returns its attributes. If not provided, modelid and machineid will be given some defaults (ignoring the local device)")
 64      print("  supportApp                Get SupportApp information from Cuttlefish")
 65      print("  update                    Fetch new information from Cuttlefish, and perform any actions this node deems necessary")
 66      print("  validate                  Vvalidate SOS and Octagon data structures from server side")
 67      print("  viable-bottles            Show bottles in preference order of server")
 68      print("  vouch PEERID PERMANENTINFO PERMANENTINFOSIG STABLEINFO STABLEINFOSIG")
 69      print("                            Create a voucher for a new peer. permanentInfo, permanentInfoSig, stableInfo, stableInfoSig should be base64 data")
 70      print("  vouchWithBottle BOTTLEID ENTROPY SALT")
 71      print("                            Create a voucher for the ego peer using the given bottle. entropy should be base64 data.")
 72      print("  reset                     Resets Cuttlefish for this account")
 73      print()
 74      print("Options applying to `join', `establish' and `update'")
 75      print("  --preapprove KEY...       Sets the (space-separated base64) list of public keys that are preapproved.")
 76      print("  --device-name NAME        Sets the device name string.")
 77      print("  --os-version VERSION      Sets the OS version string.")
 78      print("  --policy-version VERSION  Sets the policy version.")
 79      print("  --policy-secret NAME DATA Adds a name-value pair to policy secrets. DATA must be base-64.")
 80      print("Options applying to `vouch' and `join'")
 81      print("  --config FILE             Configuration file with json data.")
 82      print()
 83  }
 84  
 85  func exitUsage(_ code: Int32) -> Never {
 86      printUsage()
 87      exit(code)
 88  }
 89  
 90  func extractJSONData(dictionary: [String: Any], key: String) -> Data? {
 91      guard let b64string = dictionary[key] as? String else {
 92          return nil
 93      }
 94      guard let data = Data(base64Encoded: b64string) else {
 95          return nil
 96      }
 97      return data
 98  }
 99  
100  func jsonFromFile(filename: String) -> [String: Any] {
101      let data: Data
102      let json: [String: Any]?
103      do {
104          data = try Data(contentsOf: URL(fileURLWithPath: filename), options: .mappedIfSafe)
105          json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
106      } catch {
107          print("Error: failed to parse json file \(filename): \(error)")
108          exit(EXIT_FAILURE)
109      }
110      guard let dictionary = json else {
111          print("Error: failed to get dictionary in file \(filename)")
112          exit(EXIT_FAILURE)
113      }
114      return dictionary
115  }
116  
117  var commands: [Command] = []
118  var argIterator = args.makeIterator()
119  var configurationData: [String: Any]?
120  
121  while let arg = argIterator.next() {
122      switch arg {
123      case "--container":
124          let newContainer = argIterator.next()
125          guard newContainer != nil  else {
126              print("Error: --container takes a value")
127              print()
128              exitUsage(1)
129          }
130          container = newContainer!
131  
132      case "--context":
133          let newContext = argIterator.next()
134          guard newContext != nil  else {
135              print("Error: --context takes a value")
136              print()
137              exitUsage(1)
138          }
139          context = newContext!
140  
141      case "--modelid":
142          let newModelID = argIterator.next()
143          guard newModelID != nil else {
144              print("Error: --modelid takes a value")
145              print()
146              exitUsage(1)
147          }
148          modelID = newModelID!
149  
150      case "--machineid":
151          let newMachineID = argIterator.next()
152          guard newMachineID != nil else {
153              print("Error: --machineid takes a value")
154              print()
155              exitUsage(1)
156          }
157          machineID = newMachineID!
158  
159      case "--epoch":
160          let newEpoch = argIterator.next()
161          guard newEpoch != nil else {
162              print("Error: --epoch takes a value")
163              print()
164              exitUsage(1)
165          }
166          epoch = UInt64(newEpoch!)!
167  
168      case "--bottlesalt":
169          let salt = argIterator.next()
170          guard salt != nil else {
171              print("Error: --bottlesalt takes a value")
172              print()
173              exitUsage(1)
174          }
175          bottleSalt = salt!
176  
177      case "--preapprove":
178          var newPreapprovedKeys: [Data] = []
179          while let arg = argIterator.next() {
180              let data = Data(base64Encoded: arg)
181              guard let key = data else {
182                  print("Error: preapproved keys must be base-64 data")
183                  exitUsage(1)
184              }
185              newPreapprovedKeys.append(key)
186          }
187          preapprovedKeys = newPreapprovedKeys
188  
189      case "--device-name":
190          guard let newDeviceName = argIterator.next() else {
191              print("Error: --device-name takes a string argument")
192              exitUsage(1)
193          }
194          deviceName = newDeviceName
195  
196      case "--serial-number":
197          guard let newSerialNumber = argIterator.next() else {
198              print("Error: --serial-number takes a string argument")
199              exitUsage(1)
200          }
201          serialNumber = newSerialNumber
202  
203      case "--os-version":
204          guard let newOsVersion = argIterator.next() else {
205              print("Error: --os-version takes a string argument")
206              exitUsage(1)
207          }
208          osVersion = newOsVersion
209  
210      case "--policy-version":
211          guard let _ = UInt64(argIterator.next() ?? "") else {
212              print("Error: --policy-version takes an integer argument")
213              exitUsage(1)
214          }
215          // Option ignored for now
216  
217      case "--policy-secret":
218          guard let name = argIterator.next(), let dataBase64 = argIterator.next() else {
219              print("Error: --policy-secret takes a name and data")
220              exitUsage(1)
221          }
222          guard let data = Data(base64Encoded: dataBase64) else {
223              print("Error: --policy-secret data must be base-64")
224              exitUsage(1)
225          }
226          if policySecrets == nil {
227              policySecrets = [:]
228          }
229          policySecrets![name] = data
230  
231      case "--config":
232          guard let filename = argIterator.next() else {
233              print("Error: --config file argument missing")
234              exitUsage(1)
235          }
236  
237          configurationData = jsonFromFile(filename: filename)
238  
239      case "--help":
240          exitUsage(0)
241  
242      case "dump":
243          commands.append(.dump)
244  
245      case "depart":
246          commands.append(.depart)
247  
248      case "distrust":
249          var peerIDs = Set<String>()
250          while let arg = argIterator.next() {
251              peerIDs.insert(arg)
252          }
253          commands.append(.distrust(peerIDs))
254  
255      case "establish":
256          commands.append(.establish)
257  
258      case "join":
259          let voucher: Data
260          let voucherSig: Data
261  
262          if let configuration = configurationData {
263              guard let voucherData = extractJSONData(dictionary: configuration, key: "voucher") else {
264                  print("Error: join needs a voucher")
265                  exitUsage(EXIT_FAILURE)
266              }
267              guard let voucherSigData = extractJSONData(dictionary: configuration, key: "voucherSig") else {
268                  print("Error: join needs a voucherSig")
269                  exitUsage(EXIT_FAILURE)
270              }
271              voucher = voucherData
272              voucherSig = voucherSigData
273          } else {
274              guard let voucherBase64 = argIterator.next() else {
275                  print("Error: join needs a voucher")
276                  print()
277                  exitUsage(1)
278              }
279  
280              guard let voucherData = Data(base64Encoded: voucherBase64) else {
281                  print("Error: voucher must be base-64 data")
282                  print()
283                  exitUsage(1)
284              }
285  
286              guard let voucherSigBase64 = argIterator.next() else {
287                  print("Error: join needs a voucherSig")
288                  print()
289                  exitUsage(1)
290              }
291  
292              guard let voucherSigData = Data(base64Encoded: voucherSigBase64) else {
293                  print("Error: voucherSig must be base-64 data")
294                  print()
295                  exitUsage(1)
296              }
297  
298              voucher = voucherData
299              voucherSig = voucherSigData
300          }
301          commands.append(.join(voucher, voucherSig))
302  
303      case "local-reset":
304          commands.append(.localReset)
305  
306      case "prepare":
307          commands.append(.prepare)
308  
309      case "healthInquiry":
310          commands.append(.healthInquiry)
311  
312      case "reset":
313          commands.append(.reset)
314  
315      case "update":
316          commands.append(.update)
317  
318      case "supportApp":
319          commands.append(.supportApp)
320  
321      case "validate":
322          commands.append(.validate)
323  
324      case "viable-bottles":
325          commands.append(.viableBottles)
326  
327      case "vouch":
328          let peerID: String
329          let permanentInfo: Data
330          let permanentInfoSig: Data
331          let stableInfo: Data
332          let stableInfoSig: Data
333  
334          if let configuration = configurationData {
335              guard let peerIDString = configuration["peerID"] as? String else {
336                  print("Error: vouch needs a peerID")
337                  exitUsage(EXIT_FAILURE)
338              }
339  
340              guard let permanentInfoData = extractJSONData(dictionary: configuration, key: "permanentInfo") else {
341                  print("Error: vouch needs a permanentInfo")
342                  exitUsage(EXIT_FAILURE)
343              }
344              guard let permanentInfoSigData = extractJSONData(dictionary: configuration, key: "permanentInfoSig") else {
345                  print("Error: vouch needs a permanentInfoSig")
346                  exitUsage(EXIT_FAILURE)
347              }
348              guard let stableInfoData = extractJSONData(dictionary: configuration, key: "stableInfo") else {
349                  print("Error: vouch needs a stableInfo")
350                  exitUsage(EXIT_FAILURE)
351              }
352              guard let stableInfoSigData = extractJSONData(dictionary: configuration, key: "stableInfoSig") else {
353                  print("Error: vouch needs a stableInfoSig")
354                  exitUsage(EXIT_FAILURE)
355              }
356  
357              peerID = peerIDString
358              permanentInfo = permanentInfoData
359              permanentInfoSig = permanentInfoSigData
360              stableInfo = stableInfoData
361              stableInfoSig = stableInfoSigData
362          } else {
363              guard let peerIDString = argIterator.next() else {
364                  print("Error: vouch needs a peerID")
365                  print()
366                  exitUsage(1)
367              }
368              guard let permanentInfoBase64 = argIterator.next() else {
369                  print("Error: vouch needs a permanentInfo")
370                  print()
371                  exitUsage(1)
372              }
373              guard let permanentInfoSigBase64 = argIterator.next() else {
374                  print("Error: vouch needs a permanentInfoSig")
375                  print()
376                  exitUsage(1)
377              }
378              guard let stableInfoBase64 = argIterator.next() else {
379                  print("Error: vouch needs a stableInfo")
380                  print()
381                  exitUsage(1)
382              }
383              guard let stableInfoSigBase64 = argIterator.next() else {
384                  print("Error: vouch needs a stableInfoSig")
385                  print()
386                  exitUsage(1)
387              }
388  
389              guard let permanentInfoData = Data(base64Encoded: permanentInfoBase64) else {
390                  print("Error: permanentInfo must be base-64 data")
391                  print()
392                  exitUsage(1)
393              }
394  
395              guard let permanentInfoSigData = Data(base64Encoded: permanentInfoSigBase64) else {
396                  print("Error: permanentInfoSig must be base-64 data")
397                  print()
398                  exitUsage(1)
399              }
400              guard let stableInfoData = Data(base64Encoded: stableInfoBase64) else {
401                  print("Error: stableInfo must be base-64 data")
402                  print()
403                  exitUsage(1)
404              }
405  
406              guard let stableInfoSigData = Data(base64Encoded: stableInfoSigBase64) else {
407                  print("Error: stableInfoSig must be base-64 data")
408                  print()
409                  exitUsage(1)
410              }
411  
412              peerID = peerIDString
413              permanentInfo = permanentInfoData
414              permanentInfoSig = permanentInfoSigData
415              stableInfo = stableInfoData
416              stableInfoSig = stableInfoSigData
417          }
418  
419          commands.append(.vouch(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig))
420  
421      case "vouchWithBottle":
422          guard let bottleID = argIterator.next() else {
423              print("Error: vouchWithBottle needs a bottleID")
424              print()
425              exitUsage(1)
426          }
427          guard let entropyBase64 = argIterator.next() else {
428              print("Error: vouchWithBottle needs entropy")
429              print()
430              exitUsage(1)
431          }
432          guard let salt = argIterator.next() else {
433              print("Error: vouchWithBottle needs a salt")
434              print()
435              exitUsage(1)
436          }
437  
438          guard let entropy = Data(base64Encoded: entropyBase64) else {
439              print("Error: entropy must be base-64 data")
440              print()
441              exitUsage(1)
442          }
443  
444          commands.append(.vouchWithBottle(bottleID, entropy, salt))
445  
446      case "allow":
447          var machineIDs = Set<String>()
448          var performIDMS = false
449          while let arg = argIterator.next() {
450              if arg == "--idms" {
451                  performIDMS = true
452              } else {
453                  machineIDs.insert(arg)
454              }
455          }
456          commands.append(.allow(machineIDs, performIDMS))
457  
458      default:
459          print("Unknown argument:", arg)
460          exitUsage(1)
461      }
462  }
463  
464  if commands.isEmpty {
465      exitUsage(0)
466  }
467  
468  // JSONSerialization has no idea how to handle NSData. Help it out.
469  func cleanDictionaryForJSON(_ d: [AnyHashable: Any]) -> [AnyHashable: Any] {
470      func cleanValue(_ value: Any) -> Any {
471          switch value {
472          case let subDict as [AnyHashable: Any]:
473              return cleanDictionaryForJSON(subDict)
474          case let subArray as [Any]:
475              return subArray.map(cleanValue)
476          case let data as Data:
477              return data.base64EncodedString()
478          default:
479              return value
480          }
481      }
482  
483      return d.mapValues(cleanValue)
484  }
485  
486  // Bring up a connection to TrustedPeersHelper
487  let connection = NSXPCConnection(serviceName: "com.apple.TrustedPeersHelper")
488  
489  connection.remoteObjectInterface = TrustedPeersHelperSetupProtocol(NSXPCInterface(with: TrustedPeersHelperProtocol.self))
490  connection.resume()
491  
492  let tpHelper = connection.synchronousRemoteObjectProxyWithErrorHandler { error in print("Unable to connect to TPHelper:", error) } as! TrustedPeersHelperProtocol
493  
494  for command in commands {
495      switch command {
496      case .dump:
497          logger.log("dumping (\(container), \(context))")
498          tpHelper.dump(withContainer: container, context: context) { reply, error in
499              guard error == nil else {
500                  print("Error dumping:", error!)
501                  return
502              }
503  
504              if let reply = reply {
505                  do {
506                      print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
507                  } catch {
508                      print("Error encoding JSON: \(error)")
509                  }
510              } else {
511                  print("Error: no results, but no error either?")
512              }
513          }
514  
515      case .depart:
516          logger.log("departing (\(container), \(context))")
517          tpHelper.departByDistrustingSelf(withContainer: container, context: context) { error in
518              guard error == nil else {
519                  print("Error departing:", error!)
520                  return
521              }
522  
523              print("Depart successful")
524          }
525  
526      case .distrust(let peerIDs):
527          logger.log("distrusting \(peerIDs.description) for (\(container), \(context))")
528          tpHelper.distrustPeerIDs(withContainer: container, context: context, peerIDs: peerIDs) { error in
529              guard error == nil else {
530                  print("Error distrusting:", error!)
531                  return
532              }
533              print("Distrust successful")
534          }
535  
536      case let .join(voucher, voucherSig):
537          logger.log("joining (\(container), \(context))")
538          tpHelper.join(withContainer: container,
539                        context: context,
540                        voucherData: voucher,
541                        voucherSig: voucherSig,
542                        ckksKeys: [],
543                        tlkShares: [],
544                        preapprovedKeys: preapprovedKeys ?? []) { peerID, _, _, error in
545                          guard error == nil else {
546                              print("Error joining:", error!)
547                              return
548                          }
549                          print("Join successful. PeerID:", peerID!)
550          }
551  
552      case .establish:
553          logger.log("establishing (\(container), \(context))")
554          tpHelper.establish(withContainer: container,
555                             context: context,
556                             ckksKeys: [],
557                             tlkShares: [],
558                             preapprovedKeys: preapprovedKeys ?? []) { peerID, _, _, error in
559                              guard error == nil else {
560                                  print("Error establishing:", error!)
561                                  return
562                              }
563                              print("Establish successful. Peer ID:", peerID!)
564          }
565  
566      case .healthInquiry:
567          logger.log("healthInquiry (\(container), \(context))")
568          tpHelper.pushHealthInquiry(withContainer: container, context: context) { error in
569              guard error == nil else {
570                  print("Error healthInquiry: \(String(describing: error))")
571                  return
572              }
573              print("healthInquiry successful")
574          }
575  
576      case .localReset:
577          logger.log("local-reset (\(container), \(context))")
578          tpHelper.localReset(withContainer: container, context: context) { error in
579              guard error == nil else {
580                  print("Error resetting:", error!)
581                  return
582              }
583  
584              logger.log("local-reset (\(container), \(context)): successful")
585              print("Local reset successful")
586          }
587  
588      case .supportApp:
589          logger.log("supportApp (\(container), \(context))")
590  
591          tpHelper.getSupportAppInfo(withContainer: container, context: context) { data, error in
592              guard error == nil else {
593                  print("Error getting supportApp:", error!)
594                  return
595              }
596  
597              if let data = data {
598                  do {
599                      let string = try GetSupportAppInfoResponse(serializedData: data).jsonString()
600                      print("\(string)")
601                  } catch {
602                      print("Error decoding protobuf: \(error)")
603                  }
604              } else {
605                  print("Error: no results, but no error either?")
606              }
607          }
608  
609      case .prepare:
610          logger.log("preparing (\(container), \(context))")
611  
612          if machineID == nil {
613              let anisetteController = AKAnisetteProvisioningController()
614  
615              let anisetteData = try anisetteController.anisetteData()
616              machineID = anisetteData.machineID
617              guard machineID != nil else {
618                  print("failed to get machineid from anisette data")
619                  abort()
620              }
621          }
622  
623          let deviceInfo = OTDeviceInformationActualAdapter()
624  
625          serialNumber = serialNumber ?? deviceInfo.serialNumber()
626          guard let serialNumber = serialNumber else {
627              print("failed to get serial number")
628              abort()
629          }
630  
631          tpHelper.prepare(withContainer: container,
632                           context: context,
633                           epoch: epoch,
634                           machineID: machineID!,
635                           bottleSalt: bottleSalt,
636                           bottleID: UUID().uuidString,
637                           modelID: modelID ?? deviceInfo.modelID(),
638                           deviceName: deviceName ?? deviceInfo.deviceName(),
639                           serialNumber: serialNumber,
640                           osVersion: osVersion ?? deviceInfo.osVersion(),
641                           policyVersion: nil,
642                           policySecrets: policySecrets,
643                           syncUserControllableViews: .UNKNOWN,
644                           signingPrivKeyPersistentRef: nil,
645                           encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, syncingPolicy, error in
646                              guard error == nil else {
647                                  print("Error preparing:", error!)
648                                  return
649                              }
650  
651                              let result = [
652                                  "peerID": peerID!,
653                                  "permanentInfo": permanentInfo!.base64EncodedString(),
654                                  "permanentInfoSig": permanentInfoSig!.base64EncodedString(),
655                                  "stableInfo": stableInfo!.base64EncodedString(),
656                                  "stableInfoSig": stableInfoSig!.base64EncodedString(),
657                                  "machineID": machineID!,
658                                  "views": Array(syncingPolicy?.viewList ?? Set()),
659                                  ] as [String: Any]
660                              do {
661                                  print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
662                              } catch {
663                                  print("Error encoding JSON: \(error)")
664                              }
665          }
666  
667      case .update:
668          logger.log("updating (\(container), \(context))")
669          tpHelper.update(withContainer: container,
670                          context: context,
671                          deviceName: deviceName,
672                          serialNumber: serialNumber,
673                          osVersion: osVersion,
674                          policyVersion: nil,
675                          policySecrets: policySecrets,
676                          syncUserControllableViews: nil) { _, _, error in
677                              guard error == nil else {
678                                  print("Error updating:", error!)
679                                  return
680                              }
681  
682                              print("Update complete")
683          }
684  
685      case .reset:
686          logger.log("resetting (\(container), \(context))")
687          tpHelper.reset(withContainer: container, context: context, resetReason: .userInitiatedReset) { error in
688              guard error == nil else {
689                  print("Error during reset:", error!)
690                  return
691              }
692  
693              print("Reset complete")
694          }
695  
696      case .validate:
697          logger.log("validate (\(container), \(context))")
698          tpHelper.validatePeers(withContainer: container, context: context) { reply, error in
699              guard error == nil else {
700                  print("Error validating:", error!)
701                  return
702              }
703  
704              if let reply = reply {
705                  do {
706                      print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
707                  } catch {
708                      print("Error encoding JSON: \(error)")
709                  }
710              } else {
711                  print("Error: no results, but no error either?")
712              }
713          }
714  
715      case .viableBottles:
716          logger.log("viableBottles (\(container), \(context))")
717          tpHelper.fetchViableBottles(withContainer: container, context: context) { sortedBottleIDs, partialBottleIDs, error in
718              guard error == nil else {
719                  print("Error fetching viable bottles:", error!)
720                  return
721              }
722              var result: [String: [String]] = [:]
723              result["sortedBottleIDs"] = sortedBottleIDs
724              result["partialBottleIDs"] = partialBottleIDs
725              do {
726                  print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
727              } catch {
728                  print("Error encoding JSON: \(error)")
729              }
730          }
731  
732      case let .vouch(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig):
733          logger.log("vouching (\(container), \(context))")
734          tpHelper.vouch(withContainer: container,
735                         context: context,
736                         peerID: peerID,
737                         permanentInfo: permanentInfo,
738                         permanentInfoSig: permanentInfoSig,
739                         stableInfo: stableInfo,
740                         stableInfoSig: stableInfoSig,
741                         ckksKeys: []
742          ) { voucher, voucherSig, error in
743              guard error == nil else {
744                  print("Error during vouch:", error!)
745                  return
746              }
747  
748              do {
749                  let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
750                  print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
751              } catch {
752                  print("Error during processing vouch results: \(error)")
753              }
754          }
755  
756      case let .vouchWithBottle(bottleID, entropy, salt):
757          logger.log("vouching with bottle (\(container), \(context))")
758          tpHelper.vouchWithBottle(withContainer: container,
759                                   context: context,
760                                   bottleID: bottleID,
761                                   entropy: entropy,
762                                   bottleSalt: salt,
763                                   tlkShares: []) { voucher, voucherSig, _, _, error in
764                                      guard error == nil else {
765                                          print("Error during vouchWithBottle", error!)
766                                          return
767                                      }
768                                      do {
769                                          let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
770                                          print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
771                                      } catch {
772                                          print("Error during processing vouch results: \(error)")
773                                      }
774          }
775  
776      case let .allow(machineIDs, performIDMS):
777          logger.log("allow-listing (\(container), \(context))")
778  
779          var idmsDeviceIDs: Set<String> = Set()
780          var accountIsDemo: Bool = false
781  
782          if performIDMS {
783              let store = ACAccountStore()
784              guard let account = store.aa_primaryAppleAccount() else {
785                  print("Unable to fetch primary Apple account!")
786                  abort()
787              }
788  
789              let requestArguments = AKDeviceListRequestContext()
790              requestArguments.altDSID = account.aa_altDSID
791              requestArguments.services = [AKServiceNameiCloud]
792  
793              let akManager = AKAccountManager.sharedInstance
794              let authKitAccount = akManager.authKitAccount(withAltDSID: account.aa_altDSID)
795              if let account = authKitAccount {
796                  accountIsDemo = akManager.demoAccount(for: account)
797              }
798  
799              guard let controller = AKAppleIDAuthenticationController() else {
800                  print("Unable to create AKAppleIDAuthenticationController!")
801                  abort()
802              }
803              let semaphore = DispatchSemaphore(value: 0)
804  
805              controller.fetchDeviceList(with: requestArguments) { deviceList, error in
806                  guard error == nil else {
807                      print("Unable to fetch IDMS device list: \(error!)")
808                      abort()
809                  }
810                  guard let deviceList = deviceList else {
811                      print("IDMS returned empty device list")
812                      return
813                  }
814  
815                  idmsDeviceIDs = Set(deviceList.map { $0.machineId })
816                  semaphore.signal()
817              }
818              semaphore.wait()
819          }
820          let allMachineIDs = machineIDs.union(idmsDeviceIDs)
821          print("Setting allowed machineIDs to \(allMachineIDs)")
822          tpHelper.setAllowedMachineIDsWithContainer(container, context: context, allowedMachineIDs: allMachineIDs, honorIDMSListChanges: accountIsDemo) { listChanged, error in
823              guard error == nil else {
824                  print("Error during allow:", error!)
825                  return
826              }
827  
828              print("Allow complete, differences: \(listChanged)")
829          }
830      }
831  }