From 422328339dfab99c4daefe9e803332e1d009e012 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 9 Jun 2024 16:25:49 +0200 Subject: [PATCH 1/3] Fix report decryption for new report format --- OpenHaystack/OpenHaystack/FindMy/DecryptReports.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OpenHaystack/OpenHaystack/FindMy/DecryptReports.swift b/OpenHaystack/OpenHaystack/FindMy/DecryptReports.swift index aa27f2a7..982a9930 100755 --- a/OpenHaystack/OpenHaystack/FindMy/DecryptReports.swift +++ b/OpenHaystack/OpenHaystack/FindMy/DecryptReports.swift @@ -20,7 +20,12 @@ struct DecryptReports { /// - Throws: Errors if the decryption fails /// - Returns: An decrypted location report static func decrypt(report: FindMyReport, with key: FindMyKey) throws -> FindMyLocationReport { - let payloadData = report.payload + var payloadData = report.payload + /// Fix decryption for new report format + /// See: https://github.com/biemster/FindMy/issues/52 + if payloadData.count > 88 { + payloadData.remove(at: 5) + } let keyData = key.privateKey let privateKey = keyData From 766772e3e5d91ba568887ef079775bc8d95cb62b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 10 Jun 2024 13:45:10 +0200 Subject: [PATCH 2/3] Use AOSKit to generate anisette headers --- .../OpenHaystack/AnisetteDataManager.swift | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/OpenHaystack/OpenHaystack/AnisetteDataManager.swift b/OpenHaystack/OpenHaystack/AnisetteDataManager.swift index 5a20ea0a..e8a6d047 100644 --- a/OpenHaystack/OpenHaystack/AnisetteDataManager.swift +++ b/OpenHaystack/OpenHaystack/AnisetteDataManager.swift @@ -10,6 +10,20 @@ import Foundation import OSLog +/// Uses AOSKit to get anisette headers +@objc private protocol AOSUtilitiesProtocol +{ + static var machineSerialNumber: String? { get } + static var machineUDID: String? { get } + + static func retrieveOTPHeadersForDSID(_ dsid: String) -> [String: Any]? + + // Non-static versions used for respondsToSelector: + var machineSerialNumber: String? { get } + var machineUDID: String? { get } + func retrieveOTPHeadersForDSID(_ dsid: String) -> [String: Any]? +} + /// Uses the AltStore Mail plugin to access recent anisette data. public class AnisetteDataManager: NSObject { @objc static let shared = AnisetteDataManager() @@ -28,7 +42,7 @@ public class AnisetteDataManager: NSObject { } func requestAnisetteData(_ completion: @escaping (Result) -> Void) { - if let accountData = self.requestAnisetteDataAuthKit() { + if let accountData = self.requestAnisetteDataAOSKit() { os_log(.debug, "Anisette Data loaded %@", accountData.debugDescription) completion(.success(accountData)) return @@ -86,6 +100,63 @@ public class AnisetteDataManager: NSObject { return accountData } + /// Adapted from: https://github.com/altstoreio/AltStore/blob/main/AltServer/Anisette%20Data/AnisetteDataManager.swift + func requestAnisetteDataAOSKit() -> AppleAccountData? { + do + { + let aosKitURL = URL(fileURLWithPath: "/System/Library/PrivateFrameworks/AOSKit.framework") + guard let aosKit = Bundle(url: aosKitURL) else { throw AnisetteDataError.aosKitFailure } + try aosKit.loadAndReturnError() + + guard let AOSUtilitiesClass = NSClassFromString("AOSUtilities"), + AOSUtilitiesClass.responds(to: #selector(AOSUtilitiesProtocol.retrieveOTPHeadersForDSID(_:))), + AOSUtilitiesClass.responds(to: #selector(getter: AOSUtilitiesProtocol.machineSerialNumber)), + AOSUtilitiesClass.responds(to: #selector(getter: AOSUtilitiesProtocol.machineUDID)) + else { throw AnisetteDataError.aosKitFailure } + + let AOSUtilities = unsafeBitCast(AOSUtilitiesClass, to: AOSUtilitiesProtocol.Type.self) + + guard let anisetteData = AOSUtilities.retrieveOTPHeadersForDSID("-2") else { throw AnisetteDataError.aosKitFailure } + + let dateFormatter = ISO8601DateFormatter() + + guard let machineID = anisetteData["X-Apple-MD-M"] as? String, + let otp = anisetteData["X-Apple-MD"] as? String, + let deviceId = AOSUtilities.machineUDID, + let localUserId = deviceId.data(using: .utf8)?.base64EncodedString(), + let deviceClass = NSClassFromString("AKDevice") + else { + print("Failure retrieving anisette headers from AOSKit") + throw AnisetteDataError.aosKitFailure + } + let device: AKDevice = deviceClass.current() + + let routingInfo: UInt64 = 84215040 + let accountData = AppleAccountData( + machineID: machineID, + oneTimePassword: otp, + localUserID: localUserId, + routingInfo: routingInfo, + deviceUniqueIdentifier: device.uniqueDeviceIdentifier(), + deviceSerialNumber: device.serialNumber(), + deviceDescription: device.serverFriendlyDescription(), + date: Date(), + locale: Locale.current, + timeZone: TimeZone.current) + + /// This only works with SIP disabled + if let spToken = ReportsFetcher().fetchSearchpartyToken() { + accountData.searchPartyToken = spToken + } + return accountData + } + catch + { + return nil + } + + } + @objc func requestAnisetteDataObjc(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { self.requestAnisetteData { result in switch result { @@ -163,4 +234,5 @@ extension AnisetteDataManager { enum AnisetteDataError: Error { case pluginNotFound case invalidAnisetteData + case aosKitFailure } From 6727f40b0a6169884d55a96e1ee4ea1be30013e9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 29 Jun 2024 11:42:34 +0200 Subject: [PATCH 3/3] Create a SetttingsView to manually enter Search Party Token, add error handling for expired token --- .../OpenHaystack.xcodeproj/project.pbxproj | 6 ++- .../xcshareddata/swiftpm/Package.resolved | 38 +++++++++++++--- .../OpenHaystack/AnisetteDataManager.swift | 2 - .../FindMy/FindMyController.swift | 5 +++ .../HaystackApp/AccessoryController.swift | 14 +++--- .../Views/OpenHaystackMainView.swift | 45 ++++++++++++++----- .../Views/OpenHaystackSettingsView.swift | 36 +++++++++++++++ .../OpenHaystack/OpenHaystackApp.swift | 5 +++ 8 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackSettingsView.swift diff --git a/OpenHaystack/OpenHaystack.xcodeproj/project.pbxproj b/OpenHaystack/OpenHaystack.xcodeproj/project.pbxproj index 3c2f1ff7..e6dd75bf 100644 --- a/OpenHaystack/OpenHaystack.xcodeproj/project.pbxproj +++ b/OpenHaystack/OpenHaystack.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -58,6 +58,7 @@ 78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227125DBC8CE0042B775 /* Accessory.swift */; }; 78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227625DBDB7E0042B775 /* KeychainController.swift */; }; 78F8BB4C261C50EB00D9F37F /* LargeButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F8BB4B261C50EB00D9F37F /* LargeButtonStyle.swift */; }; + 9ED440A02C1605EF002574D1 /* OpenHaystackSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */; }; F126102F2600D1D80066A859 /* Slider+LogScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126102E2600D1D80066A859 /* Slider+LogScale.swift */; }; F12D5A5A25FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */; }; F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */; }; @@ -169,6 +170,7 @@ 78EC227125DBC8CE0042B775 /* Accessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessory.swift; sourceTree = ""; }; 78EC227625DBDB7E0042B775 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; 78F8BB4B261C50EB00D9F37F /* LargeButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeButtonStyle.swift; sourceTree = ""; }; + 9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackSettingsView.swift; sourceTree = ""; }; F126102E2600D1D80066A859 /* Slider+LogScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Slider+LogScale.swift"; sourceTree = ""; }; F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothAccessoryScanner.swift; sourceTree = ""; }; F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advertisement.swift; sourceTree = ""; }; @@ -380,6 +382,7 @@ isa = PBXGroup; children = ( 78F8BB4A261C50D500D9F37F /* Styles */, + 9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */, 78286D7625E5114600F65511 /* ActivityIndicator.swift */, 78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */, 78486BEE25DD711E0007ED87 /* PopUpAlertView.swift */, @@ -644,6 +647,7 @@ 7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */, 781EB3F125DAD7EA00FEAA19 /* FindMyKeyDecoder.swift in Sources */, 787D8AC125DECD3C00148766 /* AccessoryController.swift in Sources */, + 9ED440A02C1605EF002574D1 /* OpenHaystackSettingsView.swift in Sources */, 78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */, F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */, 781EB3F225DAD7EA00FEAA19 /* OpenHaystackApp.swift in Sources */, diff --git a/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7b30c94a..e0c2cac5 100644 --- a/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,24 @@ { + "originHash" : "bfeb00ee66eb6db71ff8535b5ea7585725e9fe73d97f066170be55b745d346e9", "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "ee97538f5b81ae89698fd95938896dec5217b148", + "version" : "1.1.1" + } + }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -14,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "124119f0bb12384cef35aa041d7c3a686108722d", - "version" : "2.40.0" + "revision" : "9428f62793696d9a0cc1f26a63f63bb31da0516d", + "version" : "2.66.0" } }, { @@ -23,10 +42,19 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl", "state" : { - "revision" : "c30c680c78c99afdabf84805a83c8745387c4ac7", - "version" : "2.20.2" + "revision" : "2b09805797f21c380f7dc9bedaab3157c5508efb", + "version" : "2.27.0" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "f9266c85189c2751589a50ea5aec72799797e471", + "version" : "1.3.0" } } ], - "version" : 2 + "version" : 3 } diff --git a/OpenHaystack/OpenHaystack/AnisetteDataManager.swift b/OpenHaystack/OpenHaystack/AnisetteDataManager.swift index e8a6d047..0bef93d2 100644 --- a/OpenHaystack/OpenHaystack/AnisetteDataManager.swift +++ b/OpenHaystack/OpenHaystack/AnisetteDataManager.swift @@ -118,8 +118,6 @@ public class AnisetteDataManager: NSObject { guard let anisetteData = AOSUtilities.retrieveOTPHeadersForDSID("-2") else { throw AnisetteDataError.aosKitFailure } - let dateFormatter = ISO8601DateFormatter() - guard let machineID = anisetteData["X-Apple-MD-M"] as? String, let otp = anisetteData["X-Apple-MD"] as? String, let deviceId = AOSUtilities.machineUDID, diff --git a/OpenHaystack/OpenHaystack/FindMy/FindMyController.swift b/OpenHaystack/OpenHaystack/FindMy/FindMyController.swift index 62f1a08c..63a3fb0f 100755 --- a/OpenHaystack/OpenHaystack/FindMy/FindMyController.swift +++ b/OpenHaystack/OpenHaystack/FindMy/FindMyController.swift @@ -148,6 +148,10 @@ class FindMyController: ObservableObject { } catch { print("Failed with error \(error)") + if jsonData.isEmpty { + print("Empty response, consider updating your Search Party Token") + completion(FindMyErrors.invalidSearchPartyToken) + } devices[deviceIndex].reports = [] } fetchReportGroup.leave() @@ -241,4 +245,5 @@ class FindMyController: ObservableObject { enum FindMyErrors: Error { case decodingPlistFailed(message: String) case objectReleased + case invalidSearchPartyToken } diff --git a/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift b/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift index 3c2adc16..e248c1b5 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift @@ -13,6 +13,7 @@ import OSLog import SwiftUI class AccessoryController: ObservableObject { + @AppStorage("searchPartyToken") private var searchPartyToken: String = "" @Published var accessories: [Accessory] var selfObserver: AnyCancellable? var listElementsObserver = [AnyCancellable]() @@ -244,10 +245,8 @@ class AccessoryController: ObservableObject { case .failure(_): completion(.failure(.activatePlugin)) case .success(let accountData): - - guard let token = accountData.searchPartyToken, - token.isEmpty == false - else { + let token = accountData.searchPartyToken ?? self.searchPartyToken.data(using: .utf8) ?? Data() + if token.isEmpty { completion(.failure(.searchPartyToken)) return } @@ -256,7 +255,12 @@ class AccessoryController: ObservableObject { switch result { case .failure(let error): os_log(.error, "Downloading reports failed %@", error.localizedDescription) - completion(.failure(.downloadingReportsFailed)) + switch error { + case FindMyErrors.invalidSearchPartyToken: + completion(.failure(.invalidSearchPartyToken)) + default: + completion(.failure(.downloadingReportsFailed)) + } case .success(let devices): let reports = devices.compactMap({ $0.reports }).flatMap({ $0 }) if reports.isEmpty { diff --git a/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift b/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift index 0ef144e5..e864d3b9 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift @@ -38,6 +38,9 @@ struct OpenHaystackMainView: View { @State var showESP32DeploySheet = false + @AppStorage("searchPartyToken") private var settingsSPToken: String? + @AppStorage("useMailPlugin") private var settingsUseMailPlugin: Bool = false + var body: some View { NavigationView { @@ -135,7 +138,7 @@ struct OpenHaystackMainView: View { Button( action: { - if !self.mailPluginIsActive { + if self.settingsUseMailPlugin && !self.mailPluginIsActive { self.showMailPlugInPopover.toggle() self.checkPluginIsRunning(silent: true, nil) } else { @@ -174,17 +177,26 @@ struct OpenHaystackMainView: View { return } - let pluginManager = MailPluginManager() + /// Checks if the search party token was set in the settings. If true the plugin is also not needed + if let tokenString = self.settingsSPToken { + self.searchPartyToken = tokenString + return + } - // Check if the plugin is installed - if pluginManager.isMailPluginInstalled == false { - // Install the mail plugin - self.alertType = .activatePlugin - self.checkPluginIsRunning(silent: true, nil) - } else { - self.checkPluginIsRunning(nil) + /// Uses mail plugin if enabled in settings + if self.settingsUseMailPlugin { + let pluginManager = MailPluginManager() + // Check if the plugin is installed + if pluginManager.isMailPluginInstalled == false { + // Install the mail plugin + self.alertType = .activatePlugin + self.checkPluginIsRunning(silent: true, nil) + } else { + self.checkPluginIsRunning(nil) + } } + } /// Download the location reports for all current accessories. Shows an error if something fails, like plug-in is missing @@ -308,7 +320,19 @@ struct OpenHaystackMainView: View { title: Text("Add the search party token"), message: Text( """ - Please paste the search party token below after copying itfrom the macOS Keychain. + Please paste the search party token in the settings after copying it from the macOS Keychain. + The item that contains the key can be found by searching for: + com.apple.account.DeviceLocator.search-party-token + """ + ), + dismissButton: Alert.Button.okay()) + case .invalidSearchPartyToken: + return Alert( + title: Text("Invalid search party token"), + message: Text( + """ + The request returned an empty result, this is probably due to an invalid search party token. + Please consider updating your search party token in the settings after copying it from the macOS Keychain. The item that contains the key can be found by searching for: com.apple.account.DeviceLocator.search-party-token """ @@ -388,6 +412,7 @@ struct OpenHaystackMainView: View { case keyError case searchPartyToken + case invalidSearchPartyToken case deployFailed case nrfDeployFailed case deployedSuccessfully diff --git a/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackSettingsView.swift b/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackSettingsView.swift new file mode 100644 index 00000000..56917072 --- /dev/null +++ b/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackSettingsView.swift @@ -0,0 +1,36 @@ +// +// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network +// +// Copyright © 2024 Secure Mobile Networking Lab (SEEMOO) +// Copyright © 2024 The Open Wireless Link Project +// +// SPDX-License-Identifier: AGPL-3.0-only +// + +import Foundation +import SwiftUI + +struct OpenHaystackSettingsView: View { + var body: some View { + TabView { + GeneralSettingsView() + .tabItem { + Label("General", systemImage: "gear") + } + } + } +} + +struct GeneralSettingsView: View { + @AppStorage("useMailPlugin") private var useMailPlugin = false + @AppStorage("searchPartyToken") private var searchPartyToken = "" + + var body: some View { + Form { + Toggle("Use Apple Mail Plugin (only works on macOS 13 and lower)", isOn: $useMailPlugin) + TextField("Search Party Token", text: $searchPartyToken) + } + .padding(20) + .frame(width: 600, height: 200) + } +} diff --git a/OpenHaystack/OpenHaystack/OpenHaystackApp.swift b/OpenHaystack/OpenHaystack/OpenHaystackApp.swift index f886380f..e22a6b64 100644 --- a/OpenHaystack/OpenHaystack/OpenHaystackApp.swift +++ b/OpenHaystack/OpenHaystack/OpenHaystackApp.swift @@ -44,6 +44,11 @@ struct OpenHaystackApp: App { .commands { SidebarCommands() } + #if os(macOS) + Settings { + OpenHaystackSettingsView() + } + #endif } func checkForUpdates() {