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
Show file tree
Hide file tree
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
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -380,6 +382,7 @@
isa = PBXGroup;
children = (
78F8BB4A261C50D500D9F37F /* Styles */,
9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */,
78286D7625E5114600F65511 /* ActivityIndicator.swift */,
78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */,
78486BEE25DD711E0007ED87 /* PopUpAlertView.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down
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",
Expand All @@ -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
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions OpenHaystack/OpenHaystack/FindMy/FindMyController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Up @@ -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]()
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -388,6 +412,7 @@ struct OpenHaystackMainView: View {

case keyError
case searchPartyToken
case invalidSearchPartyToken
case deployFailed
case nrfDeployFailed
case deployedSuccessfully
Expand Down
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
Expand Up @@ -44,6 +44,11 @@ struct OpenHaystackApp: App {
.commands {
SidebarCommands()
}
#if os(macOS)
Settings {
OpenHaystackSettingsView()
}
#endif
}

func checkForUpdates() {
Expand Down