mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-02-19 03:59:53 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffc5170ea4 | ||
|
|
f73c1ac636 | ||
|
|
5dc6158da7 | ||
|
|
ba174196c0 | ||
|
|
c618aab843 | ||
|
|
f8fb99cc41 | ||
|
|
9f41994380 | ||
|
|
b5a577ec4e | ||
|
|
b513d47ddc |
3
Firmware/ESP32/.vscode/settings.json
vendored
Normal file
3
Firmware/ESP32/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"idf.port": "/dev/cu.usbserial-0001"
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cleanup() {
|
||||
echo "cleanup ..."
|
||||
rm "$KEYFILE"
|
||||
}
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
@@ -127,13 +132,13 @@ fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
trap cleanup INT TERM EXIT
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
esptool.py --after no_reset --port "$PORT" \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
esptool.py --before no_reset --baud $BAUDRATE --port "$PORT" \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
|
||||
@@ -52,7 +52,11 @@
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
NSLog(@"Shared key: %@", [sharedKey base64EncodedStringWithOptions:0]);
|
||||
// NSLog(@"Shared key: %@", [sharedKey base64EncodedStringWithOptions:0]);
|
||||
//Free
|
||||
EC_KEY_free(key);
|
||||
EC_GROUP_free(curve);
|
||||
EC_POINT_free(publicKey);
|
||||
|
||||
return sharedKey;
|
||||
}
|
||||
@@ -90,26 +94,32 @@
|
||||
BN_CTX *ctx = BN_CTX_new();
|
||||
BN_CTX_start(ctx);
|
||||
|
||||
// Read in the private key data
|
||||
BIGNUM *privateKeyNum = BN_bin2bn(privateKeyData.bytes, privateKeyData.length, nil);
|
||||
|
||||
int res = EC_POINT_mul(group, point, privateKeyNum, nil, nil, ctx);
|
||||
|
||||
if (res != 1) {
|
||||
NSLog(@"Failed");
|
||||
return nil;
|
||||
}
|
||||
|
||||
res = EC_KEY_set_public_key(key, point);
|
||||
EC_POINT_free(point);
|
||||
|
||||
if (res != 1) {
|
||||
NSLog(@"Failed");
|
||||
return nil;
|
||||
}
|
||||
|
||||
privateKeyNum = BN_bin2bn(privateKeyData.bytes, privateKeyData.length, nil);
|
||||
|
||||
EC_KEY_set_private_key(key, privateKeyNum);
|
||||
BN_free(privateKeyNum);
|
||||
|
||||
// Free the big numbers
|
||||
// Free
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
|
||||
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -126,6 +136,10 @@
|
||||
|
||||
size_t size = EC_POINT_point2oct(curve, publicKey, POINT_CONVERSION_COMPRESSED, publicKeyBytes.mutableBytes, keySize, NULL);
|
||||
|
||||
//Free
|
||||
EC_KEY_free(key);
|
||||
EC_GROUP_free(curve);
|
||||
|
||||
if (size == 0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -146,6 +160,7 @@
|
||||
|
||||
size_t size = BN_bn2bin(privateKey, privateKeyBytes.mutableBytes);
|
||||
|
||||
EC_KEY_free(key);
|
||||
if (size == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,11 @@ class FindMyController: ObservableObject {
|
||||
self.devices = devices
|
||||
|
||||
// Decrypt the reports with the imported keys
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
var d = self.devices
|
||||
// Add the reports to the according device by finding the right key for the report
|
||||
@@ -57,8 +61,8 @@ class FindMyController: ObservableObject {
|
||||
}
|
||||
|
||||
// Decrypt the reports
|
||||
self.decryptReports {
|
||||
self.exportDevices()
|
||||
self.decryptReports { [weak self] in
|
||||
self?.exportDevices()
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
@@ -108,7 +112,11 @@ class FindMyController: ObservableObject {
|
||||
|
||||
func fetchReports(with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else {
|
||||
completion(FindMyErrors.objectReleased)
|
||||
return
|
||||
}
|
||||
let fetchReportGroup = DispatchGroup()
|
||||
|
||||
let fetcher = ReportsFetcher()
|
||||
@@ -166,7 +174,11 @@ class FindMyController: ObservableObject {
|
||||
}
|
||||
#endif
|
||||
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else {
|
||||
completion(FindMyErrors.objectReleased)
|
||||
return
|
||||
}
|
||||
self.devices = devices
|
||||
|
||||
self.decryptReports {
|
||||
@@ -228,4 +240,5 @@ class FindMyController: ObservableObject {
|
||||
|
||||
enum FindMyErrors: Error {
|
||||
case decodingPlistFailed(message: String)
|
||||
case objectReleased
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ class AccessoryController: ObservableObject {
|
||||
}
|
||||
|
||||
func initAccessoryObserver() {
|
||||
self.selfObserver = self.objectWillChange.sink { _ in
|
||||
self.selfObserver = self.objectWillChange.sink { [weak self] _ in
|
||||
// objectWillChange is called before the values are actually changed,
|
||||
// so we dispatch the call to save()
|
||||
DispatchQueue.main.async {
|
||||
self.initObserver()
|
||||
try? self.save()
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.initObserver()
|
||||
try? self?.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class AccessoryController: ObservableObject {
|
||||
$0.cancel()
|
||||
})
|
||||
self.accessories.forEach({
|
||||
let c = $0.objectWillChange.sink(receiveValue: { self.objectWillChange.send() })
|
||||
let c = $0.objectWillChange.sink(receiveValue: { [weak self] in self?.objectWillChange.send() })
|
||||
// Important: You have to keep the returned value allocated,
|
||||
// otherwise the sink subscription gets cancelled
|
||||
self.listElementsObserver.append(c)
|
||||
@@ -160,7 +160,11 @@ class AccessoryController: ObservableObject {
|
||||
///
|
||||
/// - Parameter completion: called when the reports have been succesfully downloaded or the request has failed
|
||||
func downloadLocationReports(completion: @escaping (Result<Void, OpenHaystackMainView.AlertType>) -> Void) {
|
||||
AnisetteDataManager.shared.requestAnisetteData { result in
|
||||
AnisetteDataManager.shared.requestAnisetteData { [weak self] result in
|
||||
guard let self = self else {
|
||||
completion(.failure(.noReportsFound))
|
||||
return
|
||||
}
|
||||
switch result {
|
||||
case .failure(_):
|
||||
completion(.failure(.activatePlugin))
|
||||
@@ -173,7 +177,7 @@ class AccessoryController: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
self.findMyController.fetchReports(for: self.accessories, with: token) { result in
|
||||
self.findMyController.fetchReports(for: self.accessories, with: token) { [weak self] result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
|
||||
@@ -183,7 +187,7 @@ class AccessoryController: ObservableObject {
|
||||
if reports.isEmpty {
|
||||
completion(.failure(.noReportsFound))
|
||||
} else {
|
||||
self.updateWithDecryptedReports(devices: devices)
|
||||
self?.updateWithDecryptedReports(devices: devices)
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ class AccessoryNearbyMonitor: BluetoothAccessoryDelegate {
|
||||
}
|
||||
|
||||
func initTimer() {
|
||||
self.cleanup = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
|
||||
self.removeNearbyAccessories()
|
||||
self.cleanup = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
self?.removeNearbyAccessories()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Defaults: Serial port to access the ESP32
|
||||
PORT=/dev/ttyS0
|
||||
|
||||
# Defaults: Fast baud rate
|
||||
BAUDRATE=921600
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-p|--port)
|
||||
PORT="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-s|--slow)
|
||||
BAUDRATE=115200
|
||||
shift
|
||||
;;
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_esp32.sh - Flash the OpenHaystack firmware onto an ESP32 module"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_esp32.sh [-p <port>] [-v <dir>] [-s] PUBKEY"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBKEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -p, --port <port>"
|
||||
echo " Specify the serial interface to which the device is connected."
|
||||
echo " -s, --slow"
|
||||
echo " Use 115200 instead of 921600 baud when flashing."
|
||||
echo " Might be required for long/bad USB cables or slow USB-to-Serial converters."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Port
|
||||
if [[ ! -e "$PORT" ]]; then
|
||||
echo "$PORT does not exist, please specify a valid serial interface with the -p argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install esptool
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module esptool in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Prepare the key
|
||||
KEYFILE="$SCRIPT_DIR/tmp.key"
|
||||
if [[ -f "$KEYFILE" ]]; then
|
||||
echo "$KEYFILE already exists, stopping here not to override files..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PUBKEY" | python3 -m base64 -d - > "$KEYFILE"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not parse the public key. Please provide valid base64 input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
@@ -64,6 +64,7 @@ struct AccessoryListEntry: View {
|
||||
.fill(accessory.isNearby ? Color.green : accessory.isActive ? Color.orange : Color.red)
|
||||
.frame(width: 8, height: 8)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.padding(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0))
|
||||
.contextMenu {
|
||||
Button("Delete", action: { self.delete(accessory) })
|
||||
|
||||
@@ -45,7 +45,8 @@ class AccessoryAnnotationView: MKAnnotationView {
|
||||
func updateView() {
|
||||
guard let accessory = (self.annotation as? AccessoryAnnotation)?.accessory else { return }
|
||||
self.pinView?.removeFromSuperview()
|
||||
self.pinView = NSHostingView(rootView: AccessoryPinView(accessory: accessory))
|
||||
self.pinView = nil
|
||||
self.pinView = NSHostingView(rootView: AccessoryPinView(accessory: accessory)) // TODO: LEAK! This view is not release properly
|
||||
|
||||
self.addSubview(pinView!)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ struct ManageAccessoriesView: View {
|
||||
|
||||
/// Accessory List view.
|
||||
var accessoryList: some View {
|
||||
|
||||
List(self.accessories, id: \.self, selection: $focusedAccessory) { accessory in
|
||||
AccessoryListEntry(
|
||||
accessory: accessory,
|
||||
@@ -74,9 +75,10 @@ struct ManageAccessoriesView: View {
|
||||
alertType: self.$alertType,
|
||||
delete: self.delete(accessory:),
|
||||
deployAccessoryToMicrobit: self.deploy(accessory:),
|
||||
zoomOn: { self.focusedAccessory = $0 })
|
||||
zoomOn: { self.focusedAccessory = $0 }
|
||||
)
|
||||
}
|
||||
.listStyle(SidebarListStyle())
|
||||
.listStyle(PlainListStyle())
|
||||
|
||||
}
|
||||
|
||||
@@ -271,3 +273,11 @@ struct ManageAccessoriesView_Previews: PreviewProvider {
|
||||
ManageAccessoriesView(alertType: self.$alertType, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy, showESP32DeploySheet: self.$showESPSheet)
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME: This is a workaround, because the List with Default style (and clear background) started to crop the rows on macOS 11.3
|
||||
extension NSTableView {
|
||||
open override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ final class MapViewController: NSViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
func zoomInOn(annotations: [MKAnnotation]) {
|
||||
DispatchQueue.main.async {
|
||||
self.mapView.showAnnotations(annotations, animated: true)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.mapView.showAnnotations(annotations, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
|
||||
CFTypeRef item;
|
||||
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &item);
|
||||
|
||||
|
||||
|
||||
if (status == errSecSuccess) {
|
||||
NSData *securityToken = (__bridge NSData *)(item);
|
||||
|
||||
CFRelease(item);
|
||||
|
||||
NSLog(@"Fetched token %@", [[NSString alloc] initWithData:securityToken encoding:NSUTF8StringEncoding]);
|
||||
|
||||
if (securityToken.length == 0) {
|
||||
@@ -79,7 +81,8 @@
|
||||
|
||||
if (status == errSecSuccess) {
|
||||
NSDictionary *itemDict = (__bridge NSDictionary *)(item);
|
||||
|
||||
CFRelease(item);
|
||||
|
||||
NSString *accountId = itemDict[(NSString *)kSecAttrAccount];
|
||||
|
||||
return accountId;
|
||||
|
||||
@@ -58,6 +58,7 @@ Our plugin does not access any other private data such as emails (see [source co
|
||||
2. Open OpenHaystack. This will ask you to install the Mail plugin in `~/Library/Mail/Bundle`.
|
||||
3. Open a terminal and run `sudo spctl --master-disable`, which will disable Gatekeeper and allow our Apple Mail plugin to run.
|
||||
4. Open Apple Mail. Go to _Preferences_ → _General_ → _Manage Plug-Ins..._ and activate the checkbox next to _OpenHaystackMail.mailbundle_.
|
||||
* If the _Manage Plug-Ins..._ button does not appear. Run this command in terminal `sudo defaults write "/Library/Preferences/com.apple.mail" EnableBundles 1`
|
||||
5. Allow access and restart Mail.
|
||||
6. Open a terminal and enter `sudo spctl --master-enable`, which will enable Gatekeeper again.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user