Skip to content

Commit

Permalink
Adding proxy server to macOS app
Browse files Browse the repository at this point in the history
  • Loading branch information
rkreutz committed Mar 3, 2024
1 parent 7d72fa1 commit 5e30b84
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 80 deletions.
4 changes: 4 additions & 0 deletions OpenHaystack/OpenHaystack.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
377841A52B929D650083F97A /* APISource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377841A42B929D650083F97A /* APISource.swift */; };
5A2C9089273425720044407E /* NRF in Resources */ = {isa = PBXBuildFile; fileRef = 5A2C9088273425720044407E /* NRF */; };
5A2C908B2734266A0044407E /* DataToHexExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2C908A2734266A0044407E /* DataToHexExtension.swift */; };
5A2C908D273429360044407E /* NRFController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2C908C273429360044407E /* NRFController.swift */; };
Expand Down Expand Up @@ -114,6 +115,7 @@
025DFEDB248FED250039C718 /* DecryptReports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptReports.swift; sourceTree = "<group>"; };
0298C0C8248F9506003928FE /* AuthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthKit.framework; path = ../../../../../../../../../../System/Library/PrivateFrameworks/AuthKit.framework; sourceTree = "<group>"; };
116B4EEC24A913AA007BA636 /* SavePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavePanel.swift; sourceTree = "<group>"; };
377841A42B929D650083F97A /* APISource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISource.swift; sourceTree = "<group>"; };
5A2C9088273425720044407E /* NRF */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NRF; sourceTree = "<group>"; };
5A2C908A2734266A0044407E /* DataToHexExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataToHexExtension.swift; sourceTree = "<group>"; };
5A2C908C273429360044407E /* NRFController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -372,6 +374,7 @@
children = (
78EC227125DBC8CE0042B775 /* Accessory.swift */,
78286D8B25E5355B00F65511 /* PreviewData.swift */,
377841A42B929D650083F97A /* APISource.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -627,6 +630,7 @@
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */,
5A2C908F273429540044407E /* NRFInstallSheet.swift in Sources */,
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */,
377841A52B929D650083F97A /* APISource.swift in Sources */,
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */,
78486BEF25DD711E0007ED87 /* PopUpAlertView.swift in Sources */,
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions OpenHaystack/OpenHaystack/AnisetteDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class AnisetteDataManager: NSObject {
}

func requestAnisetteDataAuthKit() -> AppleAccountData? {
let anisetteData = ReportsFetcher().anisetteDataDictionary()
let anisetteData = AnisetteDependentReportsFetcher().anisetteDataDictionary()

let dateFormatter = ISO8601DateFormatter()

Expand Down Expand Up @@ -79,7 +79,7 @@ public class AnisetteDataManager: NSObject {
locale: Locale.current,
timeZone: TimeZone.current)

if let spToken = ReportsFetcher().fetchSearchpartyToken() {
if let spToken = AnisetteDependentReportsFetcher().fetchSearchpartyToken() {
accountData.searchPartyToken = spToken
}

Expand Down
34 changes: 14 additions & 20 deletions OpenHaystack/OpenHaystack/FindMy/FindMyController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,6 @@ class FindMyController: ObservableObject {
@Published var error: Error?
@Published var devices = [FindMyDevice]()

func loadPrivateKeys(from data: Data, with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {
do {
let devices = try PropertyListDecoder().decode([FindMyDevice].self, from: data)

self.devices.append(contentsOf: devices)
self.fetchReports(with: searchPartyToken, completion: completion)
} catch {
self.error = FindMyErrors.decodingPlistFailed(message: String(describing: error))
}
}

func importReports(reports: [FindMyReport], and keys: Data, completion: @escaping () -> Void) throws {
let devices = try PropertyListDecoder().decode([FindMyDevice].self, from: keys)
self.devices = devices
Expand Down Expand Up @@ -88,6 +77,14 @@ class FindMyController: ObservableObject {
}

func fetchReports(for accessories: [Accessory], with token: Data, completion: @escaping (Result<[FindMyDevice], Error>) -> Void) {
fetchReports(for: accessories, fetcher: AnisetteDependentReportsFetcher(searchPartyToken: token), completion: completion)
}

func fetchReports(for accessories: [Accessory], with url: URL, authorizationHeader: String?, completion: @escaping (Result<[FindMyDevice], Error>) -> Void) {
fetchReports(for: accessories, fetcher: ExternalReportsFetcher(serverUrl: url, authorizationHeader: authorizationHeader), completion: completion)
}

private func fetchReports(for accessories: [Accessory], fetcher: ReportsFetcher, completion: @escaping (Result<[FindMyDevice], Error>) -> Void) {
let findMyDevices = accessories.compactMap({ acc -> FindMyDevice? in
do {
return try acc.toFindMyDevice()
Expand All @@ -99,7 +96,7 @@ class FindMyController: ObservableObject {

self.devices = findMyDevices

self.fetchReports(with: token) { error in
self.fetchReports(from: fetcher) { error in

if let error = error {
completion(.failure(error))
Expand All @@ -109,18 +106,15 @@ class FindMyController: ObservableObject {
}
}
}

func fetchReports(with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {


private func fetchReports(from fetcher: ReportsFetcher, completion: @escaping (Error?) -> Void) {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else {
completion(FindMyErrors.objectReleased)
return
}
let fetchReportGroup = DispatchGroup()

let fetcher = ReportsFetcher()


var devices = self.devices
for deviceIndex in 0..<devices.count {
fetchReportGroup.enter()
Expand All @@ -134,8 +128,8 @@ class FindMyController: ObservableObject {
// 21 days
let duration: Double = (24 * 60 * 60) * 21
let startDate = Date() - duration

fetcher.query(forHashes: keyHashes, start: startDate, duration: duration, searchPartyToken: searchPartyToken) { jd in
fetcher.query(forHashes: keyHashes, start: startDate, duration: duration) { jd in
guard let jsonData = jd else {
fetchReportGroup.leave()
return
Expand Down
81 changes: 54 additions & 27 deletions OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import SwiftUI

class AccessoryController: ObservableObject {
@Published var accessories: [Accessory]
@AppStorage(APISource.storageKey) var apiSource: APISource = .mailPlugin

var selfObserver: AnyCancellable?
var listElementsObserver = [AnyCancellable]()
let findMyController: FindMyController
Expand Down Expand Up @@ -235,39 +237,64 @@ class AccessoryController: ObservableObject {
///
/// - Parameter completion: called when the reports have been succesfully downloaded or the request has failed
func downloadLocationReports(completion: @escaping (Result<Void, OpenHaystackMainView.AlertType>) -> Void) {
AnisetteDataManager.shared.requestAnisetteData { [weak self] result in
guard let self = self else {
completion(.failure(.noReportsFound))
return
}
switch result {
case .failure(_):
completion(.failure(.activatePlugin))
case .success(let accountData):

guard let token = accountData.searchPartyToken,
token.isEmpty == false
else {
completion(.failure(.searchPartyToken))
switch apiSource {
case .mailPlugin:
AnisetteDataManager.shared.requestAnisetteData { [weak self] result in
guard let self = self else {
completion(.failure(.noReportsFound))
return
}
switch result {
case .failure(_):
completion(.failure(.activatePlugin))
case .success(let accountData):

guard let token = accountData.searchPartyToken,
token.isEmpty == false
else {
completion(.failure(.searchPartyToken))
return
}

self.findMyController.fetchReports(for: self.accessories, with: token) { [weak self] result in
switch result {
case .failure(let error):
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
completion(.failure(.downloadingReportsFailed))
case .success(let devices):
let reports = devices.compactMap({ $0.reports }).flatMap({ $0 })
if reports.isEmpty {
completion(.failure(.noReportsFound))
} else {
self?.updateWithDecryptedReports(devices: devices)
completion(.success(()))
self.findMyController.fetchReports(for: self.accessories, with: token) { [weak self] result in
switch result {
case .failure(let error):
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
completion(.failure(.downloadingReportsFailed))
case .success(let devices):
let reports = devices.compactMap({ $0.reports }).flatMap({ $0 })
if reports.isEmpty {
completion(.failure(.noReportsFound))
} else {
self?.updateWithDecryptedReports(devices: devices)
completion(.success(()))
}
}
}
}

}
}
case .reportsServer(let serverOptions):
guard let url = serverOptions.url else {
os_log(.error, "Downloading reports failed, no URL provided")
completion(.failure(.downloadingReportsFailed))
return
}

self.findMyController.fetchReports(for: self.accessories, with: url, authorizationHeader: serverOptions.authorizationHeader) { [weak self] result in
switch result {
case .failure(let error):
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
completion(.failure(.downloadingReportsFailed))
case .success(let devices):
let reports = devices.compactMap({ $0.reports }).flatMap({ $0 })
if reports.isEmpty {
completion(.failure(.noReportsFound))
} else {
self?.updateWithDecryptedReports(devices: devices)
completion(.success(()))
}
}
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions OpenHaystack/OpenHaystack/HaystackApp/Model/APISource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
//
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
// Copyright © 2021 The Open Wireless Link Project
//
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

enum APISource {
static let storageKey = "api_source"
struct ServerOptions {
var url: URL?
var authorizationHeader: String?
var isProtected: Bool { authorizationHeader != nil }
}

case mailPlugin
case reportsServer(ServerOptions)
}

extension APISource: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.mailPlugin, .mailPlugin),
(.reportsServer, .reportsServer):
return true
default:
return false
}
}
}

extension APISource: Hashable {
private struct MailPluginHash: Hashable {}
private struct ReportsServerHash: Hashable {}

func hash(into hasher: inout Hasher) {
switch self {
case .mailPlugin:
hasher.combine(MailPluginHash())
case .reportsServer:
hasher.combine(ReportsServerHash())
}
}
}

extension APISource: RawRepresentable {
private static let separator: Character = "|"
private static let mailPluginIdenitifier = "mailPlugin"
private static let reportsServerIdentifier = "reportsServer"

init?(rawValue: String) {
let components = rawValue.split(separator: APISource.separator)
guard let rawType = components.first else { return nil }
switch rawType {
case APISource.mailPluginIdenitifier:
self = .mailPlugin
case APISource.reportsServerIdentifier where components.count == 1:
self = .reportsServer(.init())
case APISource.reportsServerIdentifier where components.count == 2:
self = .reportsServer(.init(url: URL(string: String(components[1]))))
case APISource.reportsServerIdentifier where components.count == 3:
self = .reportsServer(.init(url: URL(string: String(components[1])), authorizationHeader: String(components[2])))
default:
return nil
}
}

var rawValue: String {
switch self {
case .mailPlugin:
return APISource.mailPluginIdenitifier
case .reportsServer(let serverOptions):
var components: [String] = [APISource.reportsServerIdentifier]
guard let url = serverOptions.url else { return components.joined(separator: String(APISource.separator)) }
components.append(url.absoluteString)
if let authorizationHeader = serverOptions.authorizationHeader {
components.append(authorizationHeader)
}
return components.joined(separator: String(APISource.separator))
}
}
}
Loading

0 comments on commit 5e30b84

Please sign in to comment.