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 policyVersion: NSNumber?
28 var policySecrets: [String: Data]?
33 case distrust(Set<String>)
43 case vouch(String, Data, Data, Data, Data)
44 case vouchWithBottle(String, Data, String)
45 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(" update Fetch new information from Cuttlefish, and perform any actions this node deems necessary")
65 print(" validate Vvalidate SOS and Octagon data structures from server side")
66 print(" viable-bottles Show bottles in preference order of server")
67 print(" vouch PEERID PERMANENTINFO PERMANENTINFOSIG STABLEINFO STABLEINFOSIG")
68 print(" Create a voucher for a new peer. permanentInfo, permanentInfoSig, stableInfo, stableInfoSig should be base64 data")
69 print(" vouchWithBottle BOTTLEID ENTROPY SALT")
70 print(" Create a voucher for the ego peer using the given bottle. entropy should be base64 data.")
71 print(" reset Resets Cuttlefish for this account")
73 print("Options applying to `join', `establish' and `update'")
74 print(" --preapprove KEY... Sets the (space-separated base64) list of public keys that are preapproved.")
75 print(" --device-name NAME Sets the device name string.")
76 print(" --os-version VERSION Sets the OS version string.")
77 print(" --policy-version VERSION Sets the policy version.")
78 print(" --policy-secret NAME DATA Adds a name-value pair to policy secrets. DATA must be base-64.")
79 print("Options applying to `vouch' and `join'")
80 print(" --config FILE Configuration file with json data.")
84 func exitUsage(_ code: Int32) -> Never {
89 func extractJSONData(dictionary: [String: Any], key: String) -> Data? {
90 guard let b64string = dictionary[key] as? String else {
93 guard let data = Data(base64Encoded: b64string) else {
99 func jsonFromFile(filename: String) -> [String: Any] {
101 let json: [String: Any]?
103 data = try Data(contentsOf: URL(fileURLWithPath: filename), options: .mappedIfSafe)
104 json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
106 print("Error: failed to parse json file \(filename): \(error)")
109 guard let dictionary = json else {
110 print("Error: failed to get dictionary in file \(filename)")
116 var commands: [Command] = []
117 var argIterator = args.makeIterator()
118 var configurationData: [String: Any]?
120 while let arg = argIterator.next() {
123 let newContainer = argIterator.next()
124 guard newContainer != nil else {
125 print("Error: --container takes a value")
129 container = newContainer!
132 let newContext = argIterator.next()
133 guard newContext != nil else {
134 print("Error: --context takes a value")
138 context = newContext!
141 let newModelID = argIterator.next()
142 guard newModelID != nil else {
143 print("Error: --modelid takes a value")
147 modelID = newModelID!
150 let newMachineID = argIterator.next()
151 guard newMachineID != nil else {
152 print("Error: --machineid takes a value")
156 machineID = newMachineID!
159 let newEpoch = argIterator.next()
160 guard newEpoch != nil else {
161 print("Error: --epoch takes a value")
165 epoch = UInt64(newEpoch!)!
168 let salt = argIterator.next()
169 guard salt != nil else {
170 print("Error: --bottlesalt takes a value")
177 var newPreapprovedKeys: [Data] = []
178 while let arg = argIterator.next() {
179 let data = Data(base64Encoded: arg)
180 guard let key = data else {
181 print("Error: preapproved keys must be base-64 data")
184 newPreapprovedKeys.append(key)
186 preapprovedKeys = newPreapprovedKeys
188 case "--device-name":
189 guard let newDeviceName = argIterator.next() else {
190 print("Error: --device-name takes a string argument")
193 deviceName = newDeviceName
195 case "--serial-number":
196 guard let newSerialNumber = argIterator.next() else {
197 print("Error: --serial-number takes a string argument")
200 serialNumber = newSerialNumber
203 guard let newOsVersion = argIterator.next() else {
204 print("Error: --os-version takes a string argument")
207 osVersion = newOsVersion
209 case "--policy-version":
210 guard let newPolicyVersion = UInt64(argIterator.next() ?? "") else {
211 print("Error: --policy-version takes an integer argument")
214 policyVersion = NSNumber(value: newPolicyVersion)
216 case "--policy-secret":
217 guard let name = argIterator.next(), let dataBase64 = argIterator.next() else {
218 print("Error: --policy-secret takes a name and data")
221 guard let data = Data(base64Encoded: dataBase64) else {
222 print("Error: --policy-secret data must be base-64")
225 if nil == policySecrets {
228 policySecrets![name] = data
231 guard let filename = argIterator.next() else {
232 print("Error: --config file argument missing")
236 configurationData = jsonFromFile(filename: filename)
242 commands.append(.dump)
245 commands.append(.depart)
248 var peerIDs = Set<String>()
249 while let arg = argIterator.next() {
252 commands.append(.distrust(peerIDs))
255 commands.append(.establish)
261 if let configuration = configurationData {
262 guard let voucherData = extractJSONData(dictionary: configuration, key: "voucher") else {
263 print("Error: join needs a voucher")
264 exitUsage(EXIT_FAILURE)
266 guard let voucherSigData = extractJSONData(dictionary: configuration, key: "voucherSig") else {
267 print("Error: join needs a voucherSig")
268 exitUsage(EXIT_FAILURE)
270 voucher = voucherData
271 voucherSig = voucherSigData
274 guard let voucherBase64 = argIterator.next() else {
275 print("Error: join needs a voucher")
280 guard let voucherData = Data(base64Encoded: voucherBase64) else {
281 print("Error: voucher must be base-64 data")
286 guard let voucherSigBase64 = argIterator.next() else {
287 print("Error: join needs a voucherSig")
292 guard let voucherSigData = Data(base64Encoded: voucherSigBase64) else {
293 print("Error: voucherSig must be base-64 data")
298 voucher = voucherData
299 voucherSig = voucherSigData
301 commands.append(.join(voucher, voucherSig))
304 commands.append(.localReset)
307 commands.append(.prepare)
309 case "healthInquiry":
310 commands.append(.healthInquiry)
313 commands.append(.reset)
316 commands.append(.update)
319 commands.append(.validate)
321 case "viable-bottles":
322 commands.append(.viableBottles)
326 let permanentInfo: Data
327 let permanentInfoSig: Data
329 let stableInfoSig: Data
331 if let configuration = configurationData {
332 guard let peerIDString = configuration["peerID"] as? String else {
333 print("Error: vouch needs a peerID")
334 exitUsage(EXIT_FAILURE)
337 guard let permanentInfoData = extractJSONData(dictionary: configuration, key: "permanentInfo") else {
338 print("Error: vouch needs a permanentInfo")
339 exitUsage(EXIT_FAILURE)
341 guard let permanentInfoSigData = extractJSONData(dictionary: configuration, key: "permanentInfoSig") else {
342 print("Error: vouch needs a permanentInfoSig")
343 exitUsage(EXIT_FAILURE)
345 guard let stableInfoData = extractJSONData(dictionary: configuration, key: "stableInfo") else {
346 print("Error: vouch needs a stableInfo")
347 exitUsage(EXIT_FAILURE)
349 guard let stableInfoSigData = extractJSONData(dictionary: configuration, key: "stableInfoSig") else {
350 print("Error: vouch needs a stableInfoSig")
351 exitUsage(EXIT_FAILURE)
354 peerID = peerIDString
355 permanentInfo = permanentInfoData
356 permanentInfoSig = permanentInfoSigData
357 stableInfo = stableInfoData
358 stableInfoSig = stableInfoSigData
362 guard let peerIDString = argIterator.next() else {
363 print("Error: vouch needs a peerID")
367 guard let permanentInfoBase64 = argIterator.next() else {
368 print("Error: vouch needs a permanentInfo")
372 guard let permanentInfoSigBase64 = argIterator.next() else {
373 print("Error: vouch needs a permanentInfoSig")
377 guard let stableInfoBase64 = argIterator.next() else {
378 print("Error: vouch needs a stableInfo")
382 guard let stableInfoSigBase64 = argIterator.next() else {
383 print("Error: vouch needs a stableInfoSig")
388 guard let permanentInfoData = Data(base64Encoded: permanentInfoBase64) else {
389 print("Error: permanentInfo must be base-64 data")
394 guard let permanentInfoSigData = Data(base64Encoded: permanentInfoSigBase64) else {
395 print("Error: permanentInfoSig must be base-64 data")
399 guard let stableInfoData = Data(base64Encoded: stableInfoBase64) else {
400 print("Error: stableInfo must be base-64 data")
405 guard let stableInfoSigData = Data(base64Encoded: stableInfoSigBase64) else {
406 print("Error: stableInfoSig must be base-64 data")
411 peerID = peerIDString
412 permanentInfo = permanentInfoData
413 permanentInfoSig = permanentInfoSigData
414 stableInfo = stableInfoData
415 stableInfoSig = stableInfoSigData
418 commands.append(.vouch(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig))
420 case "vouchWithBottle":
421 guard let bottleID = argIterator.next() else {
422 print("Error: vouchWithBottle needs a bottleID")
426 guard let entropyBase64 = argIterator.next() else {
427 print("Error: vouchWithBottle needs entropy")
431 guard let salt = argIterator.next() else {
432 print("Error: vouchWithBottle needs a salt")
437 guard let entropy = Data(base64Encoded: entropyBase64) else {
438 print("Error: entropy must be base-64 data")
443 commands.append(.vouchWithBottle(bottleID, entropy, salt))
446 var machineIDs = Set<String>()
447 var performIDMS = false
448 while let arg = argIterator.next() {
449 if(arg == "--idms") {
452 machineIDs.insert(arg)
455 commands.append(.allow(machineIDs, performIDMS))
458 print("Unknown argument:", arg)
463 if commands.count == 0 {
467 // JSONSerialization has no idea how to handle NSData. Help it out.
468 func cleanDictionaryForJSON(_ d: [AnyHashable: Any]) -> [AnyHashable: Any] {
469 func cleanValue(_ value: Any) -> Any {
471 case let subDict as [AnyHashable: Any]:
472 return cleanDictionaryForJSON(subDict)
473 case let subArray as [Any]:
474 return subArray.map(cleanValue)
475 case let data as Data:
476 return data.base64EncodedString()
482 return d.mapValues(cleanValue)
485 // Bring up a connection to TrustedPeersHelper
486 let connection = NSXPCConnection(serviceName: "com.apple.TrustedPeersHelper")
487 connection.remoteObjectInterface = TrustedPeersHelperSetupProtocol(NSXPCInterface(with: TrustedPeersHelperProtocol.self))
489 let tpHelper = connection.synchronousRemoteObjectProxyWithErrorHandler { error in print("Unable to connect to TPHelper:", error) } as! TrustedPeersHelperProtocol
491 for command in commands {
494 os_log("dumping (%@, %@)", log: tplogDebug, type: .default, container, context)
495 tpHelper.dump(withContainer: container, context: context) { reply, error in
496 guard error == nil else {
497 print("Error dumping:", error!)
501 if let reply = reply {
503 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
505 print("Error encoding JSON: \(error)")
508 print("Error: no results, but no error either?")
513 os_log("departing (%@, %@)", log: tplogDebug, type: .default, container, context)
514 tpHelper.departByDistrustingSelf(withContainer: container, context: context) { error in
515 guard error == nil else {
516 print("Error departing:", error!)
520 print("Depart successful")
523 case .distrust(let peerIDs):
524 os_log("distrusting %@ for (%@, %@)", log: tplogDebug, type: .default, peerIDs, container, context)
525 tpHelper.distrustPeerIDs(withContainer: container, context: context, peerIDs: peerIDs) { error in
526 guard error == nil else {
527 print("Error distrusting:", error!)
530 print("Distrust successful")
533 case .join(let voucher, let voucherSig):
534 os_log("joining (%@, %@)", log: tplogDebug, type: .default, container, context)
535 tpHelper.join(withContainer: container,
537 voucherData: voucher,
538 voucherSig: voucherSig,
541 preapprovedKeys: preapprovedKeys ?? []) { peerID, _, error in
542 guard error == nil else {
543 print("Error joining:", error!)
546 print("Join successful. PeerID:", peerID!)
550 os_log("establishing (%@, %@)", log: tplogDebug, type: .default, container, context)
551 tpHelper.establish(withContainer: container,
555 preapprovedKeys: preapprovedKeys ?? []) { peerID, _, error in
556 guard error == nil else {
557 print("Error establishing:", error!)
560 print("Establish successful. Peer ID:", peerID!)
564 os_log("healthInquiry (%@, %@)", log: tplogDebug, type: .default, container, context)
565 tpHelper.pushHealthInquiry(withContainer: container, context: context) { error in
566 guard error == nil else {
567 print("Error healthInquiry: \(String(describing: error))")
570 print("healthInquiry successful")
574 os_log("local-reset (%@, %@)", log: tplogDebug, type: .default, container, context)
575 tpHelper.localReset(withContainer: container, context: context) { error in
576 guard error == nil else {
577 print("Error resetting:", error!)
581 os_log("local-reset (%@, %@): successful", log: tplogDebug, type: .default, container, context)
582 print("Local reset successful")
586 os_log("preparing (%@, %@)", log: tplogDebug, type: .default, container, context)
588 if machineID == nil {
589 let anisetteController = AKAnisetteProvisioningController()
591 let anisetteData = try anisetteController.anisetteData()
592 machineID = anisetteData.machineID
593 guard machineID != nil else {
594 print("failed to get machineid from anisette data")
599 let deviceInfo = OTDeviceInformationActualAdapter()
601 tpHelper.prepare(withContainer: container,
604 machineID: machineID!,
605 bottleSalt: bottleSalt,
606 bottleID: UUID().uuidString,
607 modelID: modelID ?? deviceInfo.modelID(),
608 deviceName: deviceName ?? deviceInfo.deviceName(),
609 serialNumber: serialNumber ?? deviceInfo.serialNumber(),
610 osVersion: osVersion ?? deviceInfo.osVersion(),
611 policyVersion: policyVersion,
612 policySecrets: policySecrets,
613 signingPrivKeyPersistentRef: nil,
614 encPrivKeyPersistentRef: nil) {
615 peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in
616 guard error == nil else {
617 print("Error preparing:", error!)
623 "permanentInfo": permanentInfo!.base64EncodedString(),
624 "permanentInfoSig": permanentInfoSig!.base64EncodedString(),
625 "stableInfo": stableInfo!.base64EncodedString(),
626 "stableInfoSig": stableInfoSig!.base64EncodedString(),
627 "machineID": machineID!,
630 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
632 print("Error encoding JSON: \(error)")
637 os_log("updating (%@, %@)", log: tplogDebug, type: .default, container, context)
638 tpHelper.update(withContainer: container,
640 deviceName: deviceName,
641 serialNumber: serialNumber,
642 osVersion: osVersion,
643 policyVersion: policyVersion,
644 policySecrets: policySecrets) { _, error in
645 guard error == nil else {
646 print("Error updating:", error!)
650 print("Update complete")
654 os_log("resetting (%@, %@)", log: tplogDebug, type: .default, container, context)
655 tpHelper.reset(withContainer: container, context: context, resetReason: .userInitiatedReset) { error in
656 guard error == nil else {
657 print("Error during reset:", error!)
661 print("Reset complete")
665 os_log("validate (%@, %@)", log: tplogDebug, type: .default, container, context)
666 tpHelper.validatePeers(withContainer: container, context: context) { reply, error in
667 guard error == nil else {
668 print("Error validating:", error!)
672 if let reply = reply {
674 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
676 print("Error encoding JSON: \(error)")
679 print("Error: no results, but no error either?")
684 os_log("viableBottles (%@, %@)", log: tplogDebug, type: .default, container, context)
685 tpHelper.fetchViableBottles(withContainer: container, context: context) { sortedBottleIDs, partialBottleIDs, error in
686 guard error == nil else {
687 print("Error fetching viable bottles:", error!)
690 var result: [String: [String]] = [:]
691 result["sortedBottleIDs"] = sortedBottleIDs
692 result["partialBottleIDs"] = partialBottleIDs
694 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
696 print("Error encoding JSON: \(error)")
700 case .vouch(let peerID, let permanentInfo, let permanentInfoSig, let stableInfo, let stableInfoSig):
701 os_log("vouching (%@, %@)", log: tplogDebug, type: .default, container, context)
702 tpHelper.vouch(withContainer: container,
705 permanentInfo: permanentInfo,
706 permanentInfoSig: permanentInfoSig,
707 stableInfo: stableInfo,
708 stableInfoSig: stableInfoSig,
710 ) { voucher, voucherSig, error in
711 guard error == nil else {
712 print("Error during vouch:", error!)
717 let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
718 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
720 print("Error during processing vouch results: \(error)")
724 case .vouchWithBottle(let bottleID, let entropy, let salt):
725 os_log("vouching with bottle (%@, %@)", log: tplogDebug, type: .default, container, context)
726 tpHelper.vouchWithBottle(withContainer: container,
731 tlkShares: []) { voucher, voucherSig, error in
732 guard error == nil else {
733 print("Error during vouchWithBottle", error!)
737 let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
738 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
740 print("Error during processing vouch results: \(error)")
744 case .allow(let machineIDs, let performIDMS):
745 os_log("allow-listing (%@, %@)", log: tplogDebug, type: .default, container, context)
747 var idmsDeviceIDs: Set<String> = Set()
750 let store = ACAccountStore()
751 guard let account = store.aa_primaryAppleAccount() else {
752 print("Unable to fetch primary Apple account!")
756 let requestArguments = AKDeviceListRequestContext()
757 requestArguments.altDSID = account.aa_altDSID
758 requestArguments.services = [AKServiceNameiCloud]
760 guard let controller = AKAppleIDAuthenticationController() else {
761 print("Unable to create AKAppleIDAuthenticationController!")
764 let semaphore = DispatchSemaphore(value: 0)
766 controller.fetchDeviceList(with: requestArguments) { deviceList, error in
767 guard error == nil else {
768 print("Unable to fetch IDMS device list: \(error!)")
771 guard let deviceList = deviceList else {
772 print("IDMS returned empty device list")
776 idmsDeviceIDs = Set(deviceList.map { $0.machineId })
782 let allMachineIDs = machineIDs.union(idmsDeviceIDs)
783 print("Setting allowed machineIDs to \(allMachineIDs)")
784 tpHelper.setAllowedMachineIDsWithContainer(container, context: context, allowedMachineIDs: allMachineIDs) { listChanged, error in
785 guard error == nil else {
786 print("Error during allow:", error!)
790 print("Allow complete, differences: \(listChanged)")