mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-02-18 11:39:54 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78fba7391c | ||
|
|
aa7c0a50af | ||
|
|
48ceb9550c | ||
|
|
6105a9454a | ||
|
|
71fb26da56 | ||
|
|
c7a15fe0e4 | ||
|
|
ffc5170ea4 | ||
|
|
f73c1ac636 | ||
|
|
5dc6158da7 | ||
|
|
ba174196c0 | ||
|
|
c618aab843 | ||
|
|
f8fb99cc41 | ||
|
|
9f41994380 | ||
|
|
b5a577ec4e | ||
|
|
b513d47ddc |
2
.github/workflows/build-app.yml
vendored
2
.github/workflows/build-app.yml
vendored
@@ -18,7 +18,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
format-swift:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
2
.github/workflows/build-cve-2020-9986.yaml
vendored
2
.github/workflows/build-cve-2020-9986.yaml
vendored
@@ -16,7 +16,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
lint-swiftlint:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
2
.github/workflows/build-firmware.yaml
vendored
2
.github/workflows/build-firmware.yaml
vendored
@@ -16,7 +16,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build-firmware:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
build-and-release:
|
||||
name: "Create release on GitHub"
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-11
|
||||
env:
|
||||
APP: OpenHaystack
|
||||
PROJECT_DIR: OpenHaystack
|
||||
|
||||
3
Firmware/ESP32/.vscode/settings.json
vendored
Normal file
3
Firmware/ESP32/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"idf.port": "/dev/cu.usbserial-0001"
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cleanup() {
|
||||
echo "cleanup ..."
|
||||
rm "$KEYFILE"
|
||||
}
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
@@ -127,13 +132,13 @@ fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
trap cleanup INT TERM EXIT
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
esptool.py --after no_reset --port "$PORT" \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
esptool.py --before no_reset --baud $BAUDRATE --port "$PORT" \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
|
||||
@@ -52,7 +52,11 @@
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
NSLog(@"Shared key: %@", [sharedKey base64EncodedStringWithOptions:0]);
|
||||
// NSLog(@"Shared key: %@", [sharedKey base64EncodedStringWithOptions:0]);
|
||||
//Free
|
||||
EC_KEY_free(key);
|
||||
EC_GROUP_free(curve);
|
||||
EC_POINT_free(publicKey);
|
||||
|
||||
return sharedKey;
|
||||
}
|
||||
@@ -90,26 +94,32 @@
|
||||
BN_CTX *ctx = BN_CTX_new();
|
||||
BN_CTX_start(ctx);
|
||||
|
||||
// Read in the private key data
|
||||
BIGNUM *privateKeyNum = BN_bin2bn(privateKeyData.bytes, privateKeyData.length, nil);
|
||||
|
||||
int res = EC_POINT_mul(group, point, privateKeyNum, nil, nil, ctx);
|
||||
|
||||
if (res != 1) {
|
||||
NSLog(@"Failed");
|
||||
return nil;
|
||||
}
|
||||
|
||||
res = EC_KEY_set_public_key(key, point);
|
||||
EC_POINT_free(point);
|
||||
|
||||
if (res != 1) {
|
||||
NSLog(@"Failed");
|
||||
return nil;
|
||||
}
|
||||
|
||||
privateKeyNum = BN_bin2bn(privateKeyData.bytes, privateKeyData.length, nil);
|
||||
|
||||
EC_KEY_set_private_key(key, privateKeyNum);
|
||||
BN_free(privateKeyNum);
|
||||
|
||||
// Free the big numbers
|
||||
// Free
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
|
||||
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -126,6 +136,10 @@
|
||||
|
||||
size_t size = EC_POINT_point2oct(curve, publicKey, POINT_CONVERSION_COMPRESSED, publicKeyBytes.mutableBytes, keySize, NULL);
|
||||
|
||||
//Free
|
||||
EC_KEY_free(key);
|
||||
EC_GROUP_free(curve);
|
||||
|
||||
if (size == 0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -146,6 +160,7 @@
|
||||
|
||||
size_t size = BN_bn2bin(privateKey, privateKeyBytes.mutableBytes);
|
||||
|
||||
EC_KEY_free(key);
|
||||
if (size == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,11 @@ class FindMyController: ObservableObject {
|
||||
self.devices = devices
|
||||
|
||||
// Decrypt the reports with the imported keys
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
var d = self.devices
|
||||
// Add the reports to the according device by finding the right key for the report
|
||||
@@ -57,8 +61,8 @@ class FindMyController: ObservableObject {
|
||||
}
|
||||
|
||||
// Decrypt the reports
|
||||
self.decryptReports {
|
||||
self.exportDevices()
|
||||
self.decryptReports { [weak self] in
|
||||
self?.exportDevices()
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
@@ -108,7 +112,11 @@ class FindMyController: ObservableObject {
|
||||
|
||||
func fetchReports(with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else {
|
||||
completion(FindMyErrors.objectReleased)
|
||||
return
|
||||
}
|
||||
let fetchReportGroup = DispatchGroup()
|
||||
|
||||
let fetcher = ReportsFetcher()
|
||||
@@ -166,7 +174,11 @@ class FindMyController: ObservableObject {
|
||||
}
|
||||
#endif
|
||||
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else {
|
||||
completion(FindMyErrors.objectReleased)
|
||||
return
|
||||
}
|
||||
self.devices = devices
|
||||
|
||||
self.decryptReports {
|
||||
@@ -228,4 +240,5 @@ class FindMyController: ObservableObject {
|
||||
|
||||
enum FindMyErrors: Error {
|
||||
case decodingPlistFailed(message: String)
|
||||
case objectReleased
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ class AccessoryController: ObservableObject {
|
||||
}
|
||||
|
||||
func initAccessoryObserver() {
|
||||
self.selfObserver = self.objectWillChange.sink { _ in
|
||||
self.selfObserver = self.objectWillChange.sink { [weak self] _ in
|
||||
// objectWillChange is called before the values are actually changed,
|
||||
// so we dispatch the call to save()
|
||||
DispatchQueue.main.async {
|
||||
self.initObserver()
|
||||
try? self.save()
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.initObserver()
|
||||
try? self?.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class AccessoryController: ObservableObject {
|
||||
$0.cancel()
|
||||
})
|
||||
self.accessories.forEach({
|
||||
let c = $0.objectWillChange.sink(receiveValue: { self.objectWillChange.send() })
|
||||
let c = $0.objectWillChange.sink(receiveValue: { [weak self] in self?.objectWillChange.send() })
|
||||
// Important: You have to keep the returned value allocated,
|
||||
// otherwise the sink subscription gets cancelled
|
||||
self.listElementsObserver.append(c)
|
||||
@@ -160,7 +160,11 @@ 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 { result in
|
||||
AnisetteDataManager.shared.requestAnisetteData { [weak self] result in
|
||||
guard let self = self else {
|
||||
completion(.failure(.noReportsFound))
|
||||
return
|
||||
}
|
||||
switch result {
|
||||
case .failure(_):
|
||||
completion(.failure(.activatePlugin))
|
||||
@@ -173,7 +177,7 @@ class AccessoryController: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
self.findMyController.fetchReports(for: self.accessories, with: token) { result in
|
||||
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)
|
||||
@@ -183,7 +187,7 @@ class AccessoryController: ObservableObject {
|
||||
if reports.isEmpty {
|
||||
completion(.failure(.noReportsFound))
|
||||
} else {
|
||||
self.updateWithDecryptedReports(devices: devices)
|
||||
self?.updateWithDecryptedReports(devices: devices)
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ class AccessoryNearbyMonitor: BluetoothAccessoryDelegate {
|
||||
}
|
||||
|
||||
func initTimer() {
|
||||
self.cleanup = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
|
||||
self.removeNearbyAccessories()
|
||||
self.cleanup = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
self?.removeNearbyAccessories()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,14 @@ extension FileManager {
|
||||
if isDir.boolValue == true {
|
||||
try self.copyFolder(from: fileURL, to: to.appendingPathComponent(file))
|
||||
} else {
|
||||
// Copy file
|
||||
try FileManager.default.copyItem(at: fileURL, to: to.appendingPathComponent(file))
|
||||
do {
|
||||
// Copy file
|
||||
try FileManager.default.copyItem(at: fileURL, to: to.appendingPathComponent(file))
|
||||
} catch {
|
||||
if fileURL.lastPathComponent != "CodeResources" {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Defaults: Serial port to access the ESP32
|
||||
PORT=/dev/ttyS0
|
||||
|
||||
# Defaults: Fast baud rate
|
||||
BAUDRATE=921600
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-p|--port)
|
||||
PORT="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-s|--slow)
|
||||
BAUDRATE=115200
|
||||
shift
|
||||
;;
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_esp32.sh - Flash the OpenHaystack firmware onto an ESP32 module"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_esp32.sh [-p <port>] [-v <dir>] [-s] PUBKEY"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBKEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -p, --port <port>"
|
||||
echo " Specify the serial interface to which the device is connected."
|
||||
echo " -s, --slow"
|
||||
echo " Use 115200 instead of 921600 baud when flashing."
|
||||
echo " Might be required for long/bad USB cables or slow USB-to-Serial converters."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Port
|
||||
if [[ ! -e "$PORT" ]]; then
|
||||
echo "$PORT does not exist, please specify a valid serial interface with the -p argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install esptool
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module esptool in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Prepare the key
|
||||
KEYFILE="$SCRIPT_DIR/tmp.key"
|
||||
if [[ -f "$KEYFILE" ]]; then
|
||||
echo "$KEYFILE already exists, stopping here not to override files..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PUBKEY" | python3 -m base64 -d - > "$KEYFILE"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not parse the public key. Please provide valid base64 input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
@@ -20,8 +20,36 @@ struct MailPluginManager {
|
||||
|
||||
let pluginURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Mail/Bundles").appendingPathComponent(mailBundleName + ".mailbundle")
|
||||
|
||||
let localPluginURL = Bundle.main.url(forResource: mailBundleName, withExtension: "mailbundle")!
|
||||
|
||||
var isMailPluginInstalled: Bool {
|
||||
return FileManager.default.fileExists(atPath: pluginURL.path)
|
||||
//Check if the plug-in is compatible by comparing the IDs
|
||||
guard FileManager.default.fileExists(atPath: pluginURL.path) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let infoPlistURL = pluginURL.appendingPathComponent("Contents/Info.plist")
|
||||
let localInfoPlistURL = localPluginURL.appendingPathComponent("Contents/Info.plist")
|
||||
|
||||
guard let infoPlistData = try? Data(contentsOf: infoPlistURL),
|
||||
let infoPlistDict = try? PropertyListSerialization.propertyList(from: infoPlistData, options: [], format: nil) as? [String: AnyHashable],
|
||||
let localInfoPlistData = try? Data(contentsOf: localInfoPlistURL),
|
||||
let localInfoPlistDict = try? PropertyListSerialization.propertyList(from: localInfoPlistData, options: [], format: nil) as? [String: AnyHashable]
|
||||
else { return false }
|
||||
|
||||
//Compare the supported plug-ins
|
||||
let uuidEntries = localInfoPlistDict.keys.filter({ $0.contains("PluginCompatibilityUUIDs") })
|
||||
for uuidEntry in uuidEntries {
|
||||
guard let localEntry = localInfoPlistDict[uuidEntry] as? [String],
|
||||
let installedEntry = infoPlistDict[uuidEntry] as? [String]
|
||||
else { return false }
|
||||
|
||||
if localEntry != installedEntry {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/// Shows a NSSavePanel to install the mail plugin at the required place.
|
||||
@@ -58,9 +86,12 @@ struct MailPluginManager {
|
||||
throw PluginError.permissionNotGranted
|
||||
}
|
||||
|
||||
let localPluginURL = Bundle.main.url(forResource: mailBundleName, withExtension: "mailbundle")!
|
||||
|
||||
do {
|
||||
//Remove old plug-ins first
|
||||
if FileManager.default.fileExists(atPath: pluginURL.path) {
|
||||
try FileManager.default.removeItem(at: pluginURL)
|
||||
}
|
||||
|
||||
try FileManager.default.createDirectory(at: pluginsFolderURL, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
|
||||
@@ -19,10 +19,10 @@ struct PreviewData {
|
||||
static let latitude: Double = 49.878046
|
||||
static let longitude: Double = 8.656993
|
||||
|
||||
static func randomLocation() -> CLLocation {
|
||||
static func randomLocation(lat: Double = latitude, lng: Double = longitude, distance: Double = 0.005) -> CLLocation {
|
||||
return CLLocation(
|
||||
latitude: latitude + Double.random(in: 0..<0.005) * (Bool.random() ? -1 : 1),
|
||||
longitude: longitude + Double.random(in: 0..<0.005) * (Bool.random() ? -1 : 1)
|
||||
latitude: lat + Double.random(in: 0..<distance) * (Bool.random() ? -1 : 1),
|
||||
longitude: lng + Double.random(in: 0..<distance) * (Bool.random() ? -1 : 1)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,16 @@ struct PreviewData {
|
||||
accessory.isDeployed = true
|
||||
accessory.isActive = true
|
||||
accessory.isNearby = Bool.random()
|
||||
//Generate recent locations
|
||||
let startDate = Date().addingTimeInterval(-60 * 60 * 24)
|
||||
var date = startDate
|
||||
var locations: [FindMyLocationReport] = []
|
||||
while date < Date() {
|
||||
let location = randomLocation(lat: accessory.lastLocation!.coordinate.latitude, lng: accessory.lastLocation!.coordinate.longitude, distance: 0.0005)
|
||||
locations.append(FindMyLocationReport(lat: location.coordinate.latitude, lng: location.coordinate.longitude, acc: 10, dP: date, t: date, c: 0))
|
||||
date += 30 * 60
|
||||
}
|
||||
accessory.locations = locations
|
||||
return accessory
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ struct AccessoryListEntry: View {
|
||||
.fill(accessory.isNearby ? Color.green : accessory.isActive ? Color.orange : Color.red)
|
||||
.frame(width: 8, height: 8)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.padding(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0))
|
||||
.contextMenu {
|
||||
Button("Delete", action: { self.delete(accessory) })
|
||||
|
||||
@@ -45,7 +45,8 @@ class AccessoryAnnotationView: MKAnnotationView {
|
||||
func updateView() {
|
||||
guard let accessory = (self.annotation as? AccessoryAnnotation)?.accessory else { return }
|
||||
self.pinView?.removeFromSuperview()
|
||||
self.pinView = NSHostingView(rootView: AccessoryPinView(accessory: accessory))
|
||||
self.pinView = nil
|
||||
self.pinView = NSHostingView(rootView: AccessoryPinView(accessory: accessory)) // TODO: LEAK! This view is not release properly
|
||||
|
||||
self.addSubview(pinView!)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ struct ManageAccessoriesView: View {
|
||||
|
||||
/// Accessory List view.
|
||||
var accessoryList: some View {
|
||||
|
||||
List(self.accessories, id: \.self, selection: $focusedAccessory) { accessory in
|
||||
AccessoryListEntry(
|
||||
accessory: accessory,
|
||||
@@ -74,9 +75,10 @@ struct ManageAccessoriesView: View {
|
||||
alertType: self.$alertType,
|
||||
delete: self.delete(accessory:),
|
||||
deployAccessoryToMicrobit: self.deploy(accessory:),
|
||||
zoomOn: { self.focusedAccessory = $0 })
|
||||
zoomOn: { self.focusedAccessory = $0 }
|
||||
)
|
||||
}
|
||||
.listStyle(SidebarListStyle())
|
||||
.listStyle(PlainListStyle())
|
||||
|
||||
}
|
||||
|
||||
@@ -271,3 +273,11 @@ struct ManageAccessoriesView_Previews: PreviewProvider {
|
||||
ManageAccessoriesView(alertType: self.$alertType, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy, showESP32DeploySheet: self.$showESPSheet)
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME: This is a workaround, because the List with Default style (and clear background) started to crop the rows on macOS 11.3
|
||||
extension NSTableView {
|
||||
open override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ final class MapViewController: NSViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
func zoomInOn(annotations: [MKAnnotation]) {
|
||||
DispatchQueue.main.async {
|
||||
self.mapView.showAnnotations(annotations, animated: true)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.mapView.showAnnotations(annotations, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,16 @@ import SwiftUI
|
||||
struct OpenHaystackApp: App {
|
||||
@StateObject var accessoryController: AccessoryController
|
||||
var accessoryNearbyMonitor: AccessoryNearbyMonitor?
|
||||
var frameWidth: CGFloat? = nil
|
||||
var frameHeight: CGFloat? = nil
|
||||
|
||||
init() {
|
||||
let accessoryController: AccessoryController
|
||||
if ProcessInfo().arguments.contains("-preview") {
|
||||
accessoryController = AccessoryControllerPreview(accessories: PreviewData.accessories, findMyController: FindMyController())
|
||||
self.accessoryNearbyMonitor = nil
|
||||
// self.frameWidth = 1920
|
||||
// self.frameHeight = 1080
|
||||
} else {
|
||||
accessoryController = AccessoryController()
|
||||
self.accessoryNearbyMonitor = AccessoryNearbyMonitor(accessoryController: accessoryController)
|
||||
@@ -30,6 +34,7 @@ struct OpenHaystackApp: App {
|
||||
WindowGroup {
|
||||
OpenHaystackMainView()
|
||||
.environmentObject(self.accessoryController)
|
||||
.frame(width: self.frameWidth, height: self.frameHeight)
|
||||
}
|
||||
.commands {
|
||||
SidebarCommands()
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
|
||||
CFTypeRef item;
|
||||
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &item);
|
||||
|
||||
|
||||
|
||||
if (status == errSecSuccess) {
|
||||
NSData *securityToken = (__bridge NSData *)(item);
|
||||
|
||||
CFRelease(item);
|
||||
|
||||
NSLog(@"Fetched token %@", [[NSString alloc] initWithData:securityToken encoding:NSUTF8StringEncoding]);
|
||||
|
||||
if (securityToken.length == 0) {
|
||||
@@ -79,7 +81,8 @@
|
||||
|
||||
if (status == errSecSuccess) {
|
||||
NSDictionary *itemDict = (__bridge NSDictionary *)(item);
|
||||
|
||||
CFRelease(item);
|
||||
|
||||
NSString *accountId = itemDict[(NSString *)kSecAttrAccount];
|
||||
|
||||
return accountId;
|
||||
|
||||
@@ -60,5 +60,65 @@
|
||||
<array>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
</array>
|
||||
<key>Supported11.5PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string># UUIDs for versions from 10.13 to 99.99.99</string>
|
||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
||||
<string># For mail version 13.0 (3608.60.0.2.1) on OS X Version 10.15 (build 19D49f)</string>
|
||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
||||
<string># For mail version 14.0 (3652.0.5.2.1) on OS X Version 11.0 (build 20A5343i)</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
</array>
|
||||
<key>Supported11.7PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string># UUIDs for versions from 10.13 to 99.99.99</string>
|
||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
||||
<string># For mail version 13.0 (3608.60.0.2.1) on OS X Version 10.15 (build 19D49f)</string>
|
||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
||||
<string># For mail version 14.0 (3652.0.5.2.1) on OS X Version 11.0 (build 20A5343i)</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
</array>
|
||||
<key>Supported10.14PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string># UUIDs for versions from 10.13 to 99.99.99</string>
|
||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
||||
<string># For mail version 13.0 (3608.60.0.2.1) on OS X Version 10.15 (build 19D49f)</string>
|
||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
||||
<string># For mail version 14.0 (3652.0.5.2.1) on OS X Version 11.0 (build 20A5343i)</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
</array>
|
||||
<key>Supported10.13PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string># UUIDs for versions from 10.13 to 99.99.99</string>
|
||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
||||
<string># For mail version 13.0 (3608.60.0.2.1) on OS X Version 10.15 (build 19D49f)</string>
|
||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
||||
<string># For mail version 14.0 (3652.0.5.2.1) on OS X Version 11.0 (build 20A5343i)</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
</array>
|
||||
<key>Supported11.8PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string># UUIDs for versions from 10.13 to 99.99.99</string>
|
||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
||||
<string># For mail version 13.0 (3608.60.0.2.1) on OS X Version 10.15 (build 19D49f)</string>
|
||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
||||
<string># For mail version 14.0 (3652.0.5.2.1) on OS X Version 11.0 (build 20A5343i)</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -58,6 +58,7 @@ Our plugin does not access any other private data such as emails (see [source co
|
||||
2. Open OpenHaystack. This will ask you to install the Mail plugin in `~/Library/Mail/Bundle`.
|
||||
3. Open a terminal and run `sudo spctl --master-disable`, which will disable Gatekeeper and allow our Apple Mail plugin to run.
|
||||
4. Open Apple Mail. Go to _Preferences_ → _General_ → _Manage Plug-Ins..._ and activate the checkbox next to _OpenHaystackMail.mailbundle_.
|
||||
* If the _Manage Plug-Ins..._ button does not appear. Run this command in terminal `sudo defaults write "/Library/Preferences/com.apple.mail" EnableBundles 1`
|
||||
5. Allow access and restart Mail.
|
||||
6. Open a terminal and enter `sudo spctl --master-enable`, which will enable Gatekeeper again.
|
||||
|
||||
@@ -122,7 +123,8 @@ Feel free to port OpenHaystack to other devices that support Bluetooth Low Energ
|
||||
|
||||
## References
|
||||
|
||||
- Alexander Heinrich, Milan Stute, Tim Kornhuber, Matthias Hollick. **Who Can _Find My_ Devices? Security and Privacy of Apple's Crowd-Sourced Bluetooth Location Tracking System.** _Proceedings on Privacy Enhancing Technologies (PoPETs)_, 2021. [📄 Preprint](https://arxiv.org/abs/2103.02282).
|
||||
- Alexander Heinrich, Milan Stute, Tim Kornhuber, Matthias Hollick. **Who Can _Find My_ Devices? Security and Privacy of Apple's Crowd-Sourced Bluetooth Location Tracking System.** _Proceedings on Privacy Enhancing Technologies (PoPETs)_, 2021. [doi:10.2478/popets-2021-0045](https://doi.org/10.2478/popets-2021-0045) [📄 Paper](https://www.petsymposium.org/2021/files/papers/issue3/popets-2021-0045.pdf) [📄 Preprint](https://arxiv.org/abs/2103.02282).
|
||||
- Alexander Heinrich, Milan Stute, and Matthias Hollick. **DEMO: OpenHaystack: A Framework for Tracking Personal Bluetooth Devices via Apple’s Massive Find My Network.** _14th ACM Conference on Security and Privacy in Wireless and Mobile (WiSec ’21)_, 2021.
|
||||
- Tim Kornhuber. **Analysis of Apple's Crowd-Sourced Location Tracking System.** _Technical University of Darmstadt_, Master's thesis, 2020.
|
||||
- Apple Inc. **Find My Network Accessory Specification – Developer Preview – Release R3.** 2020. [📄 Download](https://developer.apple.com/find-my/).
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user