Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make OpenHaystack work on macOS 14, parse new report format correctly #250

Merged
merged 3 commits into from
Jul 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion OpenHaystack/OpenHaystack.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -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 = "<group>"; };
78EC227625DBDB7E0042B775 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
78F8BB4B261C50EB00D9F37F /* LargeButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeButtonStyle.swift; sourceTree = "<group>"; };
9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackSettingsView.swift; sourceTree = "<group>"; };
F126102E2600D1D80066A859 /* Slider+LogScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Slider+LogScale.swift"; sourceTree = "<group>"; };
F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothAccessoryScanner.swift; sourceTree = "<group>"; };
F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advertisement.swift; sourceTree = "<group>"; };
@@ -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 */,
Original file line number Diff line number Diff line change
@@ -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,19 +33,28 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "124119f0bb12384cef35aa041d7c3a686108722d",
"version" : "2.40.0"
"revision" : "9428f62793696d9a0cc1f26a63f63bb31da0516d",
"version" : "2.66.0"
}
},
{
"identity" : "swift-nio-ssl",
"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
}
72 changes: 71 additions & 1 deletion OpenHaystack/OpenHaystack/AnisetteDataManager.swift
Original file line number Diff line number Diff line change
@@ -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<AppleAccountData, Error>) -> 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,61 @@ 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 }

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 +232,5 @@ extension AnisetteDataManager {
enum AnisetteDataError: Error {
case pluginNotFound
case invalidAnisetteData
case aosKitFailure
}
7 changes: 6 additions & 1 deletion OpenHaystack/OpenHaystack/FindMy/DecryptReports.swift
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions OpenHaystack/OpenHaystack/FindMy/FindMyController.swift
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 9 additions & 5 deletions OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift
Original file line number Diff line number Diff line change
@@ -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 {
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 5 additions & 0 deletions OpenHaystack/OpenHaystack/OpenHaystackApp.swift
Original file line number Diff line number Diff line change
@@ -44,6 +44,11 @@ struct OpenHaystackApp: App {
.commands {
SidebarCommands()
}
#if os(macOS)
Settings {
OpenHaystackSettingsView()
}
#endif
}

func checkForUpdates() {