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

Adding proxy server to macOS app #233

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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