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)
50 print("Usage:", (CommandLine.arguments[0] as NSString).lastPathComponent,
51 "[--container CONTAINER] [--context CONTEXT] COMMAND")
54 print(" allow [--idms] MACHINEID ...")
55 print(" Set the (space-separated) list of machine IDs allowed in the account. If --idms is provided, append the IDMS trusted device list")
56 print(" dump Print the state of the world as this peer knows it")
57 print(" depart Attempt to depart the account and mark yourself as untrusted")
58 print(" distrust PEERID ... Distrust one or more peers by peer ID")
59 print(" establish Calls Cuttlefish Establish, creating a new account-wide trust arena with a single peer (previously generated with prepare")
60 print(" healthInquiry Request peers to check in with reportHealth")
61 print(" join VOUCHER VOUCHERSIG Join a circle using this (base64) voucher and voucherSig")
62 print(" local-reset Resets the local cuttlefish database, and ignores all previous information. Does not change anything off-device")
63 print(" prepare [--modelid MODELID] [--machineid MACHINEID] [--epoch EPOCH] [--bottlesalt BOTTLESALT]")
64 print(" Creates a new identity and returns its attributes. If not provided, modelid and machineid will be given some defaults (ignoring the local device)")
65 print(" supportApp Get SupportApp information from Cuttlefish")
66 print(" update Fetch new information from Cuttlefish, and perform any actions this node deems necessary")
67 print(" validate Vvalidate SOS and Octagon data structures from server side")
68 print(" viable-bottles Show bottles in preference order of server")
69 print(" vouch PEERID PERMANENTINFO PERMANENTINFOSIG STABLEINFO STABLEINFOSIG")
70 print(" Create a voucher for a new peer. permanentInfo, permanentInfoSig, stableInfo, stableInfoSig should be base64 data")
71 print(" vouchWithBottle BOTTLEID ENTROPY SALT")
72 print(" Create a voucher for the ego peer using the given bottle. entropy should be base64 data.")
73 print(" reset Resets Cuttlefish for this account")
75 print("Options applying to `join', `establish' and `update'")
76 print(" --preapprove KEY... Sets the (space-separated base64) list of public keys that are preapproved.")
77 print(" --device-name NAME Sets the device name string.")
78 print(" --os-version VERSION Sets the OS version string.")
79 print(" --policy-version VERSION Sets the policy version.")
80 print(" --policy-secret NAME DATA Adds a name-value pair to policy secrets. DATA must be base-64.")
81 print("Options applying to `vouch' and `join'")
82 print(" --config FILE Configuration file with json data.")
86 func exitUsage(_ code: Int32) -> Never {
91 func extractJSONData(dictionary: [String: Any], key: String) -> Data? {
92 guard let b64string = dictionary[key] as? String else {
95 guard let data = Data(base64Encoded: b64string) else {
101 func jsonFromFile(filename: String) -> [String: Any] {
103 let json: [String: Any]?
105 data = try Data(contentsOf: URL(fileURLWithPath: filename), options: .mappedIfSafe)
106 json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
108 print("Error: failed to parse json file \(filename): \(error)")
111 guard let dictionary = json else {
112 print("Error: failed to get dictionary in file \(filename)")
118 var commands: [Command] = []
119 var argIterator = args.makeIterator()
120 var configurationData: [String: Any]?
122 while let arg = argIterator.next() {
125 let newContainer = argIterator.next()
126 guard newContainer != nil else {
127 print("Error: --container takes a value")
131 container = newContainer!
134 let newContext = argIterator.next()
135 guard newContext != nil else {
136 print("Error: --context takes a value")
140 context = newContext!
143 let newModelID = argIterator.next()
144 guard newModelID != nil else {
145 print("Error: --modelid takes a value")
149 modelID = newModelID!
152 let newMachineID = argIterator.next()
153 guard newMachineID != nil else {
154 print("Error: --machineid takes a value")
158 machineID = newMachineID!
161 let newEpoch = argIterator.next()
162 guard newEpoch != nil else {
163 print("Error: --epoch takes a value")
167 epoch = UInt64(newEpoch!)!
170 let salt = argIterator.next()
171 guard salt != nil else {
172 print("Error: --bottlesalt takes a value")
179 var newPreapprovedKeys: [Data] = []
180 while let arg = argIterator.next() {
181 let data = Data(base64Encoded: arg)
182 guard let key = data else {
183 print("Error: preapproved keys must be base-64 data")
186 newPreapprovedKeys.append(key)
188 preapprovedKeys = newPreapprovedKeys
190 case "--device-name":
191 guard let newDeviceName = argIterator.next() else {
192 print("Error: --device-name takes a string argument")
195 deviceName = newDeviceName
197 case "--serial-number":
198 guard let newSerialNumber = argIterator.next() else {
199 print("Error: --serial-number takes a string argument")
202 serialNumber = newSerialNumber
205 guard let newOsVersion = argIterator.next() else {
206 print("Error: --os-version takes a string argument")
209 osVersion = newOsVersion
211 case "--policy-version":
212 guard let newPolicyVersion = UInt64(argIterator.next() ?? "") else {
213 print("Error: --policy-version takes an integer argument")
216 policyVersion = NSNumber(value: newPolicyVersion)
218 case "--policy-secret":
219 guard let name = argIterator.next(), let dataBase64 = argIterator.next() else {
220 print("Error: --policy-secret takes a name and data")
223 guard let data = Data(base64Encoded: dataBase64) else {
224 print("Error: --policy-secret data must be base-64")
227 if nil == policySecrets {
230 policySecrets![name] = data
233 guard let filename = argIterator.next() else {
234 print("Error: --config file argument missing")
238 configurationData = jsonFromFile(filename: filename)
244 commands.append(.dump)
247 commands.append(.depart)
250 var peerIDs = Set<String>()
251 while let arg = argIterator.next() {
254 commands.append(.distrust(peerIDs))
257 commands.append(.establish)
263 if let configuration = configurationData {
264 guard let voucherData = extractJSONData(dictionary: configuration, key: "voucher") else {
265 print("Error: join needs a voucher")
266 exitUsage(EXIT_FAILURE)
268 guard let voucherSigData = extractJSONData(dictionary: configuration, key: "voucherSig") else {
269 print("Error: join needs a voucherSig")
270 exitUsage(EXIT_FAILURE)
272 voucher = voucherData
273 voucherSig = voucherSigData
276 guard let voucherBase64 = argIterator.next() else {
277 print("Error: join needs a voucher")
282 guard let voucherData = Data(base64Encoded: voucherBase64) else {
283 print("Error: voucher must be base-64 data")
288 guard let voucherSigBase64 = argIterator.next() else {
289 print("Error: join needs a voucherSig")
294 guard let voucherSigData = Data(base64Encoded: voucherSigBase64) else {
295 print("Error: voucherSig must be base-64 data")
300 voucher = voucherData
301 voucherSig = voucherSigData
303 commands.append(.join(voucher, voucherSig))
306 commands.append(.localReset)
309 commands.append(.prepare)
311 case "healthInquiry":
312 commands.append(.healthInquiry)
315 commands.append(.reset)
318 commands.append(.update)
321 commands.append(.supportApp)
324 commands.append(.validate)
326 case "viable-bottles":
327 commands.append(.viableBottles)
331 let permanentInfo: Data
332 let permanentInfoSig: Data
334 let stableInfoSig: Data
336 if let configuration = configurationData {
337 guard let peerIDString = configuration["peerID"] as? String else {
338 print("Error: vouch needs a peerID")
339 exitUsage(EXIT_FAILURE)
342 guard let permanentInfoData = extractJSONData(dictionary: configuration, key: "permanentInfo") else {
343 print("Error: vouch needs a permanentInfo")
344 exitUsage(EXIT_FAILURE)
346 guard let permanentInfoSigData = extractJSONData(dictionary: configuration, key: "permanentInfoSig") else {
347 print("Error: vouch needs a permanentInfoSig")
348 exitUsage(EXIT_FAILURE)
350 guard let stableInfoData = extractJSONData(dictionary: configuration, key: "stableInfo") else {
351 print("Error: vouch needs a stableInfo")
352 exitUsage(EXIT_FAILURE)
354 guard let stableInfoSigData = extractJSONData(dictionary: configuration, key: "stableInfoSig") else {
355 print("Error: vouch needs a stableInfoSig")
356 exitUsage(EXIT_FAILURE)
359 peerID = peerIDString
360 permanentInfo = permanentInfoData
361 permanentInfoSig = permanentInfoSigData
362 stableInfo = stableInfoData
363 stableInfoSig = stableInfoSigData
367 guard let peerIDString = argIterator.next() else {
368 print("Error: vouch needs a peerID")
372 guard let permanentInfoBase64 = argIterator.next() else {
373 print("Error: vouch needs a permanentInfo")
377 guard let permanentInfoSigBase64 = argIterator.next() else {
378 print("Error: vouch needs a permanentInfoSig")
382 guard let stableInfoBase64 = argIterator.next() else {
383 print("Error: vouch needs a stableInfo")
387 guard let stableInfoSigBase64 = argIterator.next() else {
388 print("Error: vouch needs a stableInfoSig")
393 guard let permanentInfoData = Data(base64Encoded: permanentInfoBase64) else {
394 print("Error: permanentInfo must be base-64 data")
399 guard let permanentInfoSigData = Data(base64Encoded: permanentInfoSigBase64) else {
400 print("Error: permanentInfoSig must be base-64 data")
404 guard let stableInfoData = Data(base64Encoded: stableInfoBase64) else {
405 print("Error: stableInfo must be base-64 data")
410 guard let stableInfoSigData = Data(base64Encoded: stableInfoSigBase64) else {
411 print("Error: stableInfoSig must be base-64 data")
416 peerID = peerIDString
417 permanentInfo = permanentInfoData
418 permanentInfoSig = permanentInfoSigData
419 stableInfo = stableInfoData
420 stableInfoSig = stableInfoSigData
423 commands.append(.vouch(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig))
425 case "vouchWithBottle":
426 guard let bottleID = argIterator.next() else {
427 print("Error: vouchWithBottle needs a bottleID")
431 guard let entropyBase64 = argIterator.next() else {
432 print("Error: vouchWithBottle needs entropy")
436 guard let salt = argIterator.next() else {
437 print("Error: vouchWithBottle needs a salt")
442 guard let entropy = Data(base64Encoded: entropyBase64) else {
443 print("Error: entropy must be base-64 data")
448 commands.append(.vouchWithBottle(bottleID, entropy, salt))
451 var machineIDs = Set<String>()
452 var performIDMS = false
453 while let arg = argIterator.next() {
454 if(arg == "--idms") {
457 machineIDs.insert(arg)
460 commands.append(.allow(machineIDs, performIDMS))
463 print("Unknown argument:", arg)
468 if commands.count == 0 {
472 // JSONSerialization has no idea how to handle NSData. Help it out.
473 func cleanDictionaryForJSON(_ d: [AnyHashable: Any]) -> [AnyHashable: Any] {
474 func cleanValue(_ value: Any) -> Any {
476 case let subDict as [AnyHashable: Any]:
477 return cleanDictionaryForJSON(subDict)
478 case let subArray as [Any]:
479 return subArray.map(cleanValue)
480 case let data as Data:
481 return data.base64EncodedString()
487 return d.mapValues(cleanValue)
490 // Bring up a connection to TrustedPeersHelper
491 let connection = NSXPCConnection(serviceName: "com.apple.TrustedPeersHelper")
492 connection.remoteObjectInterface = TrustedPeersHelperSetupProtocol(NSXPCInterface(with: TrustedPeersHelperProtocol.self))
494 let tpHelper = connection.synchronousRemoteObjectProxyWithErrorHandler { error in print("Unable to connect to TPHelper:", error) } as! TrustedPeersHelperProtocol
496 for command in commands {
499 os_log("dumping (%@, %@)", log: tplogDebug, type: .default, container, context)
500 tpHelper.dump(withContainer: container, context: context) { reply, error in
501 guard error == nil else {
502 print("Error dumping:", error!)
506 if let reply = reply {
508 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
510 print("Error encoding JSON: \(error)")
513 print("Error: no results, but no error either?")
518 os_log("departing (%@, %@)", log: tplogDebug, type: .default, container, context)
519 tpHelper.departByDistrustingSelf(withContainer: container, context: context) { error in
520 guard error == nil else {
521 print("Error departing:", error!)
525 print("Depart successful")
528 case .distrust(let peerIDs):
529 os_log("distrusting %@ for (%@, %@)", log: tplogDebug, type: .default, peerIDs, container, context)
530 tpHelper.distrustPeerIDs(withContainer: container, context: context, peerIDs: peerIDs) { error in
531 guard error == nil else {
532 print("Error distrusting:", error!)
535 print("Distrust successful")
538 case .join(let voucher, let voucherSig):
539 os_log("joining (%@, %@)", log: tplogDebug, type: .default, container, context)
540 tpHelper.join(withContainer: container,
542 voucherData: voucher,
543 voucherSig: voucherSig,
546 preapprovedKeys: preapprovedKeys ?? []) { peerID, _, error in
547 guard error == nil else {
548 print("Error joining:", error!)
551 print("Join successful. PeerID:", peerID!)
555 os_log("establishing (%@, %@)", log: tplogDebug, type: .default, container, context)
556 tpHelper.establish(withContainer: container,
560 preapprovedKeys: preapprovedKeys ?? []) { peerID, _, error in
561 guard error == nil else {
562 print("Error establishing:", error!)
565 print("Establish successful. Peer ID:", peerID!)
569 os_log("healthInquiry (%@, %@)", log: tplogDebug, type: .default, container, context)
570 tpHelper.pushHealthInquiry(withContainer: container, context: context) { error in
571 guard error == nil else {
572 print("Error healthInquiry: \(String(describing: error))")
575 print("healthInquiry successful")
579 os_log("local-reset (%@, %@)", log: tplogDebug, type: .default, container, context)
580 tpHelper.localReset(withContainer: container, context: context) { error in
581 guard error == nil else {
582 print("Error resetting:", error!)
586 os_log("local-reset (%@, %@): successful", log: tplogDebug, type: .default, container, context)
587 print("Local reset successful")
591 os_log("supportApp (%@, %@)", log: tplogDebug, type: .default, container, context)
593 tpHelper.getSupportAppInfo(withContainer: container, context: context) { data, error in
594 guard error == nil else {
595 print("Error getting supportApp:", error!)
601 let string = try GetSupportAppInfoResponse(serializedData: data).jsonString()
604 print("Error decoding protobuf: \(error)")
607 print("Error: no results, but no error either?")
612 os_log("preparing (%@, %@)", log: tplogDebug, type: .default, container, context)
614 if machineID == nil {
615 let anisetteController = AKAnisetteProvisioningController()
617 let anisetteData = try anisetteController.anisetteData()
618 machineID = anisetteData.machineID
619 guard machineID != nil else {
620 print("failed to get machineid from anisette data")
625 let deviceInfo = OTDeviceInformationActualAdapter()
627 tpHelper.prepare(withContainer: container,
630 machineID: machineID!,
631 bottleSalt: bottleSalt,
632 bottleID: UUID().uuidString,
633 modelID: modelID ?? deviceInfo.modelID(),
634 deviceName: deviceName ?? deviceInfo.deviceName(),
635 serialNumber: serialNumber ?? deviceInfo.serialNumber(),
636 osVersion: osVersion ?? deviceInfo.osVersion(),
637 policyVersion: policyVersion,
638 policySecrets: policySecrets,
639 signingPrivKeyPersistentRef: nil,
640 encPrivKeyPersistentRef: nil) {
641 peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in
642 guard error == nil else {
643 print("Error preparing:", error!)
649 "permanentInfo": permanentInfo!.base64EncodedString(),
650 "permanentInfoSig": permanentInfoSig!.base64EncodedString(),
651 "stableInfo": stableInfo!.base64EncodedString(),
652 "stableInfoSig": stableInfoSig!.base64EncodedString(),
653 "machineID": machineID!,
656 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
658 print("Error encoding JSON: \(error)")
663 os_log("updating (%@, %@)", log: tplogDebug, type: .default, container, context)
664 tpHelper.update(withContainer: container,
666 deviceName: deviceName,
667 serialNumber: serialNumber,
668 osVersion: osVersion,
669 policyVersion: policyVersion,
670 policySecrets: policySecrets) { _, error in
671 guard error == nil else {
672 print("Error updating:", error!)
676 print("Update complete")
680 os_log("resetting (%@, %@)", log: tplogDebug, type: .default, container, context)
681 tpHelper.reset(withContainer: container, context: context, resetReason: .userInitiatedReset) { error in
682 guard error == nil else {
683 print("Error during reset:", error!)
687 print("Reset complete")
691 os_log("validate (%@, %@)", log: tplogDebug, type: .default, container, context)
692 tpHelper.validatePeers(withContainer: container, context: context) { reply, error in
693 guard error == nil else {
694 print("Error validating:", error!)
698 if let reply = reply {
700 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(reply)))
702 print("Error encoding JSON: \(error)")
705 print("Error: no results, but no error either?")
710 os_log("viableBottles (%@, %@)", log: tplogDebug, type: .default, container, context)
711 tpHelper.fetchViableBottles(withContainer: container, context: context) { sortedBottleIDs, partialBottleIDs, error in
712 guard error == nil else {
713 print("Error fetching viable bottles:", error!)
716 var result: [String: [String]] = [:]
717 result["sortedBottleIDs"] = sortedBottleIDs
718 result["partialBottleIDs"] = partialBottleIDs
720 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
722 print("Error encoding JSON: \(error)")
726 case .vouch(let peerID, let permanentInfo, let permanentInfoSig, let stableInfo, let stableInfoSig):
727 os_log("vouching (%@, %@)", log: tplogDebug, type: .default, container, context)
728 tpHelper.vouch(withContainer: container,
731 permanentInfo: permanentInfo,
732 permanentInfoSig: permanentInfoSig,
733 stableInfo: stableInfo,
734 stableInfoSig: stableInfoSig,
736 ) { voucher, voucherSig, error in
737 guard error == nil else {
738 print("Error during vouch:", error!)
743 let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
744 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
746 print("Error during processing vouch results: \(error)")
750 case .vouchWithBottle(let bottleID, let entropy, let salt):
751 os_log("vouching with bottle (%@, %@)", log: tplogDebug, type: .default, container, context)
752 tpHelper.vouchWithBottle(withContainer: container,
757 tlkShares: []) { voucher, voucherSig, error in
758 guard error == nil else {
759 print("Error during vouchWithBottle", error!)
763 let result = ["voucher": voucher!.base64EncodedString(), "voucherSig": voucherSig!.base64EncodedString()]
764 print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result)))
766 print("Error during processing vouch results: \(error)")
770 case .allow(let machineIDs, let performIDMS):
771 os_log("allow-listing (%@, %@)", log: tplogDebug, type: .default, container, context)
773 var idmsDeviceIDs: Set<String> = Set()
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 guard let controller = AKAppleIDAuthenticationController() else {
787 print("Unable to create AKAppleIDAuthenticationController!")
790 let semaphore = DispatchSemaphore(value: 0)
792 controller.fetchDeviceList(with: requestArguments) { deviceList, error in
793 guard error == nil else {
794 print("Unable to fetch IDMS device list: \(error!)")
797 guard let deviceList = deviceList else {
798 print("IDMS returned empty device list")
802 idmsDeviceIDs = Set(deviceList.map { $0.machineId })
808 let allMachineIDs = machineIDs.union(idmsDeviceIDs)
809 print("Setting allowed machineIDs to \(allMachineIDs)")
810 tpHelper.setAllowedMachineIDsWithContainer(container, context: context, allowedMachineIDs: allMachineIDs) { listChanged, error in
811 guard error == nil else {
812 print("Error during allow:", error!)
816 print("Allow complete, differences: \(listChanged)")