4 let tplogDebug = OSLog(subsystem: "com.apple.security.trustedpeers", category: "debug")
 
   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.
 
  10 let programName = CommandLine.arguments[0]
 
  11 var args: ArraySlice<String> = CommandLine.arguments[1...]
 
  13 var context: String = OTDefaultContext
 
  14 var container: String = "com.apple.security.keychain"
 
  16 // Only used for prepare, but in the absence of a real command line library this is the easiest way to get them
 
  18 var machineID: String?
 
  20 var bottleSalt: String = ""
 
  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]?
 
  32     case distrust(Set<String>)
 
  42     case vouch(String, Data, Data, Data, Data)
 
  43     case vouchWithBottle(String, Data, String)
 
  44     case allow(Set<String>, Bool)
 
  49     print("Usage:", (CommandLine.arguments[0] as NSString).lastPathComponent,
 
  50           "[--container CONTAINER] [--context CONTEXT] COMMAND")
 
  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")
 
  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.")
 
  85 func exitUsage(_ code: Int32) -> Never {
 
  90 func extractJSONData(dictionary: [String: Any], key: String) -> Data? {
 
  91     guard let b64string = dictionary[key] as? String else {
 
  94     guard let data = Data(base64Encoded: b64string) else {
 
 100 func jsonFromFile(filename: String) -> [String: Any] {
 
 102     let json: [String: Any]?
 
 104         data = try Data(contentsOf: URL(fileURLWithPath: filename), options: .mappedIfSafe)
 
 105         json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
 
 107         print("Error: failed to parse json file \(filename): \(error)")
 
 110     guard let dictionary = json else {
 
 111         print("Error: failed to get dictionary in file \(filename)")
 
 117 var commands: [Command] = []
 
 118 var argIterator = args.makeIterator()
 
 119 var configurationData: [String: Any]?
 
 121 while let arg = argIterator.next() {
 
 124         let newContainer = argIterator.next()
 
 125         guard newContainer != nil  else {
 
 126             print("Error: --container takes a value")
 
 130         container = newContainer!
 
 133         let newContext = argIterator.next()
 
 134         guard newContext != nil  else {
 
 135             print("Error: --context takes a value")
 
 139         context = newContext!
 
 142         let newModelID = argIterator.next()
 
 143         guard newModelID != nil else {
 
 144             print("Error: --modelid takes a value")
 
 148         modelID = newModelID!
 
 151         let newMachineID = argIterator.next()
 
 152         guard newMachineID != nil else {
 
 153             print("Error: --machineid takes a value")
 
 157         machineID = newMachineID!
 
 160         let newEpoch = argIterator.next()
 
 161         guard newEpoch != nil else {
 
 162             print("Error: --epoch takes a value")
 
 166         epoch = UInt64(newEpoch!)!
 
 169         let salt = argIterator.next()
 
 170         guard salt != nil else {
 
 171             print("Error: --bottlesalt takes a value")
 
 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")
 
 185             newPreapprovedKeys.append(key)
 
 187         preapprovedKeys = newPreapprovedKeys
 
 189     case "--device-name":
 
 190         guard let newDeviceName = argIterator.next() else {
 
 191             print("Error: --device-name takes a string argument")
 
 194         deviceName = newDeviceName
 
 196     case "--serial-number":
 
 197         guard let newSerialNumber = argIterator.next() else {
 
 198             print("Error: --serial-number takes a string argument")
 
 201         serialNumber = newSerialNumber
 
 204         guard let newOsVersion = argIterator.next() else {
 
 205             print("Error: --os-version takes a string argument")
 
 208         osVersion = newOsVersion
 
 210     case "--policy-version":
 
 211         guard let _ = UInt64(argIterator.next() ?? "") else {
 
 212             print("Error: --policy-version takes an integer argument")
 
 215         // Option ignored for now
 
 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")
 
 222         guard let data = Data(base64Encoded: dataBase64) else {
 
 223             print("Error: --policy-secret data must be base-64")
 
 226         if nil == policySecrets {
 
 229         policySecrets![name] = data
 
 232         guard let filename = argIterator.next() else {
 
 233             print("Error: --config file argument missing")
 
 237         configurationData = jsonFromFile(filename: filename)
 
 243         commands.append(.dump)
 
 246         commands.append(.depart)
 
 249         var peerIDs = Set<String>()
 
 250         while let arg = argIterator.next() {
 
 253         commands.append(.distrust(peerIDs))
 
 256         commands.append(.establish)
 
 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)
 
 267             guard let voucherSigData = extractJSONData(dictionary: configuration, key: "voucherSig") else {
 
 268                 print("Error: join needs a voucherSig")
 
 269                 exitUsage(EXIT_FAILURE)
 
 271             voucher = voucherData
 
 272             voucherSig = voucherSigData
 
 275             guard let voucherBase64 = argIterator.next() else {
 
 276                 print("Error: join needs a voucher")
 
 281             guard let voucherData = Data(base64Encoded: voucherBase64) else {
 
 282                 print("Error: voucher must be base-64 data")
 
 287             guard let voucherSigBase64 = argIterator.next() else {
 
 288                 print("Error: join needs a voucherSig")
 
 293             guard let voucherSigData = Data(base64Encoded: voucherSigBase64) else {
 
 294                 print("Error: voucherSig must be base-64 data")
 
 299             voucher = voucherData
 
 300             voucherSig = voucherSigData
 
 302         commands.append(.join(voucher, voucherSig))
 
 305         commands.append(.localReset)
 
 308         commands.append(.prepare)
 
 310     case "healthInquiry":
 
 311         commands.append(.healthInquiry)
 
 314         commands.append(.reset)
 
 317         commands.append(.update)
 
 320         commands.append(.supportApp)
 
 323         commands.append(.validate)
 
 325     case "viable-bottles":
 
 326         commands.append(.viableBottles)
 
 330         let permanentInfo: Data
 
 331         let permanentInfoSig: Data
 
 333         let stableInfoSig: Data
 
 335         if let configuration = configurationData {
 
 336             guard let peerIDString = configuration["peerID"] as? String else {
 
 337                 print("Error: vouch needs a peerID")
 
 338                 exitUsage(EXIT_FAILURE)
 
 341             guard let permanentInfoData = extractJSONData(dictionary: configuration, key: "permanentInfo") else {
 
 342                 print("Error: vouch needs a permanentInfo")
 
 343                 exitUsage(EXIT_FAILURE)
 
 345             guard let permanentInfoSigData = extractJSONData(dictionary: configuration, key: "permanentInfoSig") else {
 
 346                 print("Error: vouch needs a permanentInfoSig")
 
 347                 exitUsage(EXIT_FAILURE)
 
 349             guard let stableInfoData = extractJSONData(dictionary: configuration, key: "stableInfo") else {
 
 350                 print("Error: vouch needs a stableInfo")
 
 351                 exitUsage(EXIT_FAILURE)
 
 353             guard let stableInfoSigData = extractJSONData(dictionary: configuration, key: "stableInfoSig") else {
 
 354                 print("Error: vouch needs a stableInfoSig")
 
 355                 exitUsage(EXIT_FAILURE)
 
 358             peerID = peerIDString
 
 359             permanentInfo = permanentInfoData
 
 360             permanentInfoSig = permanentInfoSigData
 
 361             stableInfo = stableInfoData
 
 362             stableInfoSig = stableInfoSigData
 
 366             guard let peerIDString = argIterator.next() else {
 
 367                 print("Error: vouch needs a peerID")
 
 371             guard let permanentInfoBase64 = argIterator.next() else {
 
 372                 print("Error: vouch needs a permanentInfo")
 
 376             guard let permanentInfoSigBase64 = argIterator.next() else {
 
 377                 print("Error: vouch needs a permanentInfoSig")
 
 381             guard let stableInfoBase64 = argIterator.next() else {
 
 382                 print("Error: vouch needs a stableInfo")
 
 386             guard let stableInfoSigBase64 = argIterator.next() else {
 
 387                 print("Error: vouch needs a stableInfoSig")
 
 392             guard let permanentInfoData = Data(base64Encoded: permanentInfoBase64) else {
 
 393                 print("Error: permanentInfo must be base-64 data")
 
 398             guard let permanentInfoSigData = Data(base64Encoded: permanentInfoSigBase64) else {
 
 399                 print("Error: permanentInfoSig must be base-64 data")
 
 403             guard let stableInfoData = Data(base64Encoded: stableInfoBase64) else {
 
 404                 print("Error: stableInfo must be base-64 data")
 
 409             guard let stableInfoSigData = Data(base64Encoded: stableInfoSigBase64) else {
 
 410                 print("Error: stableInfoSig must be base-64 data")
 
 415             peerID = peerIDString
 
 416             permanentInfo = permanentInfoData
 
 417             permanentInfoSig = permanentInfoSigData
 
 418             stableInfo = stableInfoData
 
 419             stableInfoSig = stableInfoSigData
 
 422         commands.append(.vouch(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig))
 
 424     case "vouchWithBottle":
 
 425         guard let bottleID = argIterator.next() else {
 
 426             print("Error: vouchWithBottle needs a bottleID")
 
 430         guard let entropyBase64 = argIterator.next() else {
 
 431             print("Error: vouchWithBottle needs entropy")
 
 435         guard let salt = argIterator.next() else {
 
 436             print("Error: vouchWithBottle needs a salt")
 
 441         guard let entropy = Data(base64Encoded: entropyBase64) else {
 
 442             print("Error: entropy must be base-64 data")
 
 447         commands.append(.vouchWithBottle(bottleID, entropy, salt))
 
 450         var machineIDs = Set<String>()
 
 451         var performIDMS = false
 
 452         while let arg = argIterator.next() {
 
 456                 machineIDs.insert(arg)
 
 459         commands.append(.allow(machineIDs, performIDMS))
 
 462         print("Unknown argument:", arg)
 
 467 if commands.isEmpty {
 
 471 // JSONSerialization has no idea how to handle NSData. Help it out.
 
 472 func cleanDictionaryForJSON(_ d: [AnyHashable: Any]) -> [AnyHashable: Any] {
 
 473     func cleanValue(_ value: Any) -> Any {
 
 475         case let subDict as [AnyHashable: Any]:
 
 476             return cleanDictionaryForJSON(subDict)
 
 477         case let subArray as [Any]:
 
 478             return subArray.map(cleanValue)
 
 479         case let data as Data:
 
 480             return data.base64EncodedString()
 
 486     return d.mapValues(cleanValue)
 
 489 // Bring up a connection to TrustedPeersHelper
 
 490 let connection = NSXPCConnection(serviceName: "com.apple.TrustedPeersHelper")
 
 491 connection.remoteObjectInterface = TrustedPeersHelperSetupProtocol(NSXPCInterface(with: TrustedPeersHelperProtocol.self))
 
 493 let tpHelper = connection.synchronousRemoteObjectProxyWithErrorHandler { error in print("Unable to connect to TPHelper:", error) } as! TrustedPeersHelperProtocol
 
 495 for command in commands {
 
 498         os_log("dumping (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 499         tpHelper.dump(withContainer: container, context: context) { reply, error in
 
 500             guard error == nil else {
 
 501                 print("Error dumping:", error!)
 
 505             if let reply = reply {
 
 507                     print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
 
 509                     print("Error encoding JSON: \(error)")
 
 512                 print("Error: no results, but no error either?")
 
 517         os_log("departing (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 518         tpHelper.departByDistrustingSelf(withContainer: container, context: context) { error in
 
 519             guard error == nil else {
 
 520                 print("Error departing:", error!)
 
 524             print("Depart successful")
 
 527     case .distrust(let peerIDs):
 
 528         os_log("distrusting %@ for (%@, %@)", log: tplogDebug, type: .default, peerIDs, container, context)
 
 529         tpHelper.distrustPeerIDs(withContainer: container, context: context, peerIDs: peerIDs) { error in
 
 530             guard error == nil else {
 
 531                 print("Error distrusting:", error!)
 
 534             print("Distrust successful")
 
 537     case .join(let voucher, let voucherSig):
 
 538         os_log("joining (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 539         tpHelper.join(withContainer: container,
 
 541                       voucherData: voucher,
 
 542                       voucherSig: voucherSig,
 
 545                       preapprovedKeys: preapprovedKeys ?? []) { peerID, _, _, _, error in
 
 546                         guard error == nil else {
 
 547                             print("Error joining:", error!)
 
 550                         print("Join successful. PeerID:", peerID!)
 
 554         os_log("establishing (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 555         tpHelper.establish(withContainer: container,
 
 559                            preapprovedKeys: preapprovedKeys ?? []) { peerID, _, error in
 
 560                             guard error == nil else {
 
 561                                 print("Error establishing:", error!)
 
 564                             print("Establish successful. Peer ID:", peerID!)
 
 568         os_log("healthInquiry (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 569         tpHelper.pushHealthInquiry(withContainer: container, context: context) { error in
 
 570             guard error == nil else {
 
 571                 print("Error healthInquiry: \(String(describing: error))")
 
 574             print("healthInquiry successful")
 
 578         os_log("local-reset (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 579         tpHelper.localReset(withContainer: container, context: context) { error in
 
 580             guard error == nil else {
 
 581                 print("Error resetting:", error!)
 
 585             os_log("local-reset (%@, %@): successful", log: tplogDebug, type: .default, container, context)
 
 586             print("Local reset successful")
 
 590         os_log("supportApp (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 592         tpHelper.getSupportAppInfo(withContainer: container, context: context) { data, error in
 
 593             guard error == nil else {
 
 594                 print("Error getting supportApp:", error!)
 
 600                     let string = try GetSupportAppInfoResponse(serializedData: data).jsonString()
 
 603                     print("Error decoding protobuf: \(error)")
 
 606                 print("Error: no results, but no error either?")
 
 611         os_log("preparing (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 613         if machineID == nil {
 
 614             let anisetteController = AKAnisetteProvisioningController()
 
 616             let anisetteData = try anisetteController.anisetteData()
 
 617             machineID = anisetteData.machineID
 
 618             guard machineID != nil else {
 
 619                 print("failed to get machineid from anisette data")
 
 624         let deviceInfo = OTDeviceInformationActualAdapter()
 
 626         tpHelper.prepare(withContainer: container,
 
 629                          machineID: machineID!,
 
 630                          bottleSalt: bottleSalt,
 
 631                          bottleID: UUID().uuidString,
 
 632                          modelID: modelID ?? deviceInfo.modelID(),
 
 633                          deviceName: deviceName ?? deviceInfo.deviceName(),
 
 634                          serialNumber: serialNumber ?? deviceInfo.serialNumber(),
 
 635                          osVersion: osVersion ?? deviceInfo.osVersion(),
 
 637                          policySecrets: policySecrets,
 
 638                          signingPrivKeyPersistentRef: nil,
 
 639                          encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, views, _, error in
 
 640                             guard error == nil else {
 
 641                                 print("Error preparing:", error!)
 
 647                                 "permanentInfo": permanentInfo!.base64EncodedString(),
 
 648                                 "permanentInfoSig": permanentInfoSig!.base64EncodedString(),
 
 649                                 "stableInfo": stableInfo!.base64EncodedString(),
 
 650                                 "stableInfoSig": stableInfoSig!.base64EncodedString(),
 
 651                                 "machineID": machineID!,
 
 652                                 "views": Array(views ?? Set()),
 
 655                                 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
 
 657                                 print("Error encoding JSON: \(error)")
 
 662         os_log("updating (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 663         tpHelper.update(withContainer: container,
 
 665                         deviceName: deviceName,
 
 666                         serialNumber: serialNumber,
 
 667                         osVersion: osVersion,
 
 669                         policySecrets: policySecrets) { _, error in
 
 670                             guard error == nil else {
 
 671                                 print("Error updating:", error!)
 
 675                             print("Update complete")
 
 679         os_log("resetting (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 680         tpHelper.reset(withContainer: container, context: context, resetReason: .userInitiatedReset) { error in
 
 681             guard error == nil else {
 
 682                 print("Error during reset:", error!)
 
 686             print("Reset complete")
 
 690         os_log("validate (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 691         tpHelper.validatePeers(withContainer: container, context: context) { reply, error in
 
 692             guard error == nil else {
 
 693                 print("Error validating:", error!)
 
 697             if let reply = reply {
 
 699                     print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
 
 701                     print("Error encoding JSON: \(error)")
 
 704                 print("Error: no results, but no error either?")
 
 709         os_log("viableBottles (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 710         tpHelper.fetchViableBottles(withContainer: container, context: context) { sortedBottleIDs, partialBottleIDs, error in
 
 711             guard error == nil else {
 
 712                 print("Error fetching viable bottles:", error!)
 
 715             var result: [String: [String]] = [:]
 
 716             result["sortedBottleIDs"] = sortedBottleIDs
 
 717             result["partialBottleIDs"] = partialBottleIDs
 
 719                 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
 
 721                 print("Error encoding JSON: \(error)")
 
 725     case .vouch(let peerID, let permanentInfo, let permanentInfoSig, let stableInfo, let stableInfoSig):
 
 726         os_log("vouching (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 727         tpHelper.vouch(withContainer: container,
 
 730                        permanentInfo: permanentInfo,
 
 731                        permanentInfoSig: permanentInfoSig,
 
 732                        stableInfo: stableInfo,
 
 733                        stableInfoSig: stableInfoSig,
 
 735         ) { voucher, voucherSig, error in
 
 736             guard error == nil else {
 
 737                 print("Error during vouch:", error!)
 
 742                 let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
 
 743                 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
 
 745                 print("Error during processing vouch results: \(error)")
 
 749     case .vouchWithBottle(let bottleID, let entropy, let salt):
 
 750         os_log("vouching with bottle (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 751         tpHelper.vouchWithBottle(withContainer: container,
 
 756                                  tlkShares: []) { voucher, voucherSig, _, _, error in
 
 757                                     guard error == nil else {
 
 758                                         print("Error during vouchWithBottle", error!)
 
 762                                         let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
 
 763                                         print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
 
 765                                         print("Error during processing vouch results: \(error)")
 
 769     case .allow(let machineIDs, let performIDMS):
 
 770         os_log("allow-listing (%@, %@)", log: tplogDebug, type: .default, container, context)
 
 772         var idmsDeviceIDs: Set<String> = Set()
 
 773         var accountIsDemo: Bool = false
 
 776             let store = ACAccountStore()
 
 777             guard let account = store.aa_primaryAppleAccount() else {
 
 778                 print("Unable to fetch primary Apple account!")
 
 782             let requestArguments = AKDeviceListRequestContext()
 
 783             requestArguments.altDSID = account.aa_altDSID
 
 784             requestArguments.services = [AKServiceNameiCloud]
 
 786             let akManager = AKAccountManager.sharedInstance
 
 787             let authKitAccount = akManager.authKitAccount(withAltDSID: account.aa_altDSID)
 
 788             if let account = authKitAccount {
 
 789                 accountIsDemo = akManager.demoAccount(for: account)
 
 792             guard let controller = AKAppleIDAuthenticationController() else {
 
 793                 print("Unable to create AKAppleIDAuthenticationController!")
 
 796             let semaphore = DispatchSemaphore(value: 0)
 
 798             controller.fetchDeviceList(with: requestArguments) { deviceList, error in
 
 799                 guard error == nil else {
 
 800                     print("Unable to fetch IDMS device list: \(error!)")
 
 803                 guard let deviceList = deviceList else {
 
 804                     print("IDMS returned empty device list")
 
 808                 idmsDeviceIDs = Set(deviceList.map { $0.machineId })
 
 813         let allMachineIDs = machineIDs.union(idmsDeviceIDs)
 
 814         print("Setting allowed machineIDs to \(allMachineIDs)")
 
 815         tpHelper.setAllowedMachineIDsWithContainer(container, context: context, allowedMachineIDs: allMachineIDs, honorIDMSListChanges: accountIsDemo) { listChanged, error in
 
 816             guard error == nil else {
 
 817                 print("Error during allow:", error!)
 
 821             print("Allow complete, differences: \(listChanged)")