Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various fixes on mobile app #145

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 11 additions & 5 deletions Firmware/Linux_HCI/HCI.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ def bytes_to_strarray(bytes_, with_prefix=False):
def run_hci_cmd(cmd, hci="hci0", wait=1):
cmd_ = ["hcitool", "-i", hci, "cmd"]
cmd_ += cmd
print(cmd_)
#print(cmd_)
subprocess.run(cmd_)
if wait > 0:
time.sleep(wait)

def run_hciconf_leadv_3(hci="hci0"):
#EDIT: the above command makes the advertised service connectable. If you don't want to allow connections, change it to $ sudo hciconfig hci0 leadv 3
cmd_ = ["hciconfig", hci, "leadv", "3"]
subprocess.run(cmd_)

def start_advertising(key, interval_ms=2000):
addr = bytearray(key[:6])
Expand All @@ -46,14 +50,14 @@ def start_advertising(key, interval_ms=2000):
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()}")
#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)
time.sleep(3)

# Set BLE advertisement payload
run_hci_cmd(["0x08", "0x0008"] + [format(len(adv), "x")] + bytes_to_strarray(adv))
Expand All @@ -70,6 +74,8 @@ def start_advertising(key, interval_ms=2000):
# Start BLE advertising
run_hci_cmd(["0x08", "0x000a"] + ["01"], wait=0)

run_hciconf_leadv_3()


def main(args):
parser = argparse.ArgumentParser()
Expand Down
11 changes: 8 additions & 3 deletions openhaystack-mobile/lib/accessory/accessory_list.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
Expand Down Expand Up @@ -63,12 +64,16 @@ class _AccessoryListState extends State<AccessoryList> {
return const NoAccessoriesPlaceholder();
}

// TODO: Refresh Indicator for desktop
// Use pull to refresh method
return SlidableAutoCloseBehavior(child:
RefreshIndicator(
onRefresh: widget.loadLocationUpdates,
child: Scrollbar(
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: ListView(
children: accessories.map((accessory) {
// Calculate distance from users devices location
Expand Down
7 changes: 4 additions & 3 deletions openhaystack-mobile/lib/accessory/accessory_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:openhaystack_mobile/accessory/accessory_model.dart';
import 'package:latlong2/latlong.dart';
import 'package:openhaystack_mobile/accessory/accessory_storage.dart';
import 'package:openhaystack_mobile/findMy/find_my_controller.dart';
import 'package:openhaystack_mobile/findMy/models.dart';

const accessoryStorageKey = 'ACCESSORIES';

class AccessoryRegistry extends ChangeNotifier {

final _storage = const FlutterSecureStorage();
final _storage = const AccessoryStorage(const FlutterSecureStorage());
final _findMyController = FindMyController();
List<Accessory> _accessories = [];
bool loading = false;
Expand Down Expand Up @@ -99,12 +100,12 @@ class AccessoryRegistry extends ChangeNotifier {
var reportsForAccessories = await Future.wait(runningLocationRequests);
for (var i = 0; i < currentAccessories.length; i++) {
var accessory = currentAccessories.elementAt(i);
var reports = reportsForAccessories.elementAt(i);
var reports = reportsForAccessories.elementAt(i)
.where((report) => report.latitude.abs() <= 90 && report.longitude.abs() < 90 );

print("Found ${reports.length} reports for accessory '${accessory.name}'");

accessory.locationHistory = reports
.where((report) => report.latitude.abs() <= 90 && report.longitude.abs() < 90 )
.map((report) => Pair<LatLng, DateTime>(
LatLng(report.latitude, report.longitude),
report.timestamp ?? report.published,
Expand Down
75 changes: 75 additions & 0 deletions openhaystack-mobile/lib/accessory/accessory_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'dart:io';

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

const CRED_MAX_CREDENTIAL_BLOB_SIZE = 5 * 512;

class AccessoryStorage {
final FlutterSecureStorage flutterSecureStorage;

const AccessoryStorage(this.flutterSecureStorage);
Future<void> deleteAll() async {
await flutterSecureStorage.deleteAll();
}

Future<void> delete({required String key}) async {
if (Platform.isWindows) {
final chunkedKey = '\$${key}_chunk_size';

final chunkSize =
int.parse(await flutterSecureStorage.read(key: chunkedKey) ?? '0');

if (chunkSize > 0) {
await Future.wait(List.generate(chunkSize,
(i) async => await flutterSecureStorage.delete(key: '${key}_${i + 1}')));
} else {
await flutterSecureStorage.delete(key: key);
}
await flutterSecureStorage.delete(key:chunkedKey);
} else {
await flutterSecureStorage.delete(key: key);
}
}

Future<void> write({
required String key,
required String? value,
}) async {
if (Platform.isWindows &&
value != null &&
value.length > CRED_MAX_CREDENTIAL_BLOB_SIZE) {
final exp = RegExp(r".{1,512}");
final matches = exp.allMatches(value).toList();
final chunkedKey = '\$${key}_chunk_size';
await Future.wait(List.generate(matches.length + 1, (i) {
return i == 0
? flutterSecureStorage.write(
key: chunkedKey, value: matches.length.toString())
: flutterSecureStorage.write(
key: '${key}_${i}', value: matches[i - 1].group(0));
}));
} else {
await flutterSecureStorage.write(key: key, value: value);
}
}

Future<String?> read({required String key}) async {
if (Platform.isWindows) {
// await this.delete(key: key);
final chunkedKey = '\$${key}_chunk_size';

final chunkSize =
int.parse(await flutterSecureStorage.read(key: chunkedKey) ?? '0');

if (chunkSize > 0) {
final chunks = await Future.wait(List.generate(chunkSize,
(i) async => await flutterSecureStorage.read(key: '${key}_${i+1}')));
return chunks.join();
} else {
return await flutterSecureStorage.read(key: key);
}
} else {
return await flutterSecureStorage.read(key: key);
}
}
}
39 changes: 35 additions & 4 deletions openhaystack-mobile/lib/dashboard/dashboard_desktop.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:openhaystack_mobile/item_management/item_management_desktop.dart';
import 'package:provider/provider.dart';
import 'package:openhaystack_mobile/accessory/accessory_list.dart';
import 'package:openhaystack_mobile/accessory/accessory_registry.dart';
import 'package:openhaystack_mobile/location/location_model.dart';
import 'package:openhaystack_mobile/map/map.dart';
import 'package:openhaystack_mobile/preferences/preferences_page.dart';
import 'package:openhaystack_mobile/preferences/user_preferences_model.dart';
import 'package:latlong2/latlong.dart';

class DashboardDesktop extends StatefulWidget {

Expand All @@ -20,7 +23,13 @@ class DashboardDesktop extends StatefulWidget {
}

class _DashboardDesktopState extends State<DashboardDesktop> {
final MapController _mapController = MapController();

void _centerPoint(LatLng point) {
_mapController.fitBounds(
LatLngBounds(point),
);
}
@override
void initState() {
super.initState();
Expand All @@ -40,7 +49,21 @@ class _DashboardDesktopState extends State<DashboardDesktop> {
/// Fetch locaiton updates for all accessories.
Future<void> loadLocationUpdates() async {
var accessoryRegistry = Provider.of<AccessoryRegistry>(context, listen: false);
await accessoryRegistry.loadLocationReports();
try {
await accessoryRegistry.loadLocationReports();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).colorScheme.error,
content: Text(
'Could not find location reports. Try again later.',
style: TextStyle(
color: Theme.of(context).colorScheme.onError,
),
),
),
);
}
}

@override
Expand All @@ -55,7 +78,12 @@ class _DashboardDesktopState extends State<DashboardDesktop> {
AppBar(
title: const Text('OpenHaystack'),
leading: IconButton(
onPressed: () { /* reload */ },
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ItemManagementDesktop()),
);
},
icon: const Icon(Icons.menu),
),
actions: <Widget>[
Expand All @@ -77,13 +105,16 @@ class _DashboardDesktopState extends State<DashboardDesktop> {
Expanded(
child: AccessoryList(
loadLocationUpdates: loadLocationUpdates,
centerOnPoint: _centerPoint,
),
),
],
),
),
const Expanded(
child: AccessoryMap(),
Expanded(
child: AccessoryMap(
mapController: _mapController,
),
),
],
),
Expand Down
52 changes: 34 additions & 18 deletions openhaystack-mobile/lib/findMy/decrypt_reports.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'dart:convert';
import 'dart:isolate';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/src/utils.dart' as pc_utils;
import 'package:openhaystack_mobile/findMy/models.dart';

import 'package:cryptography/cryptography.dart' as nice_crypto;

class DecryptReports {
/// Decrypts a given [FindMyReport] with the given private key.
static Future<FindMyLocationReport> decryptReport(
Expand All @@ -19,26 +21,26 @@ class DecryptReports {

_decodeTimeAndConfidence(payloadData, report);

final privateKey = ECPrivateKey(
pc_utils.decodeBigIntWithSign(1, key),
curveDomainParam);
final privateKey =
ECPrivateKey(pc_utils.decodeBigIntWithSign(1, key), curveDomainParam);

final decodePoint = curveDomainParam.curve.decodePoint(ephemeralKeyBytes);
final ephemeralPublicKey = ECPublicKey(decodePoint, curveDomainParam);

final Uint8List sharedKeyBytes = _ecdh(ephemeralPublicKey, privateKey);
final Uint8List derivedKey = _kdf(sharedKeyBytes, ephemeralKeyBytes);

final decryptedPayload = _decryptPayload(encData, derivedKey, tag);
final decryptedPayload = await _decryptPayload(encData, derivedKey, tag);
final locationReport = _decodePayload(decryptedPayload, report);

return locationReport;
}

/// Decodes the unencrypted timestamp and confidence
static void _decodeTimeAndConfidence(Uint8List payloadData, FindMyReport report) {
final seenTimeStamp = payloadData.sublist(0, 4).buffer.asByteData()
.getInt32(0, Endian.big);
static void _decodeTimeAndConfidence(
Uint8List payloadData, FindMyReport report) {
final seenTimeStamp =
payloadData.sublist(0, 4).buffer.asByteData().getInt32(0, Endian.big);
final timestamp = DateTime(2001).add(Duration(seconds: seenTimeStamp));
final confidence = payloadData.elementAt(4);
report.timestamp = timestamp;
Expand All @@ -47,11 +49,12 @@ class DecryptReports {

/// Performs an Elliptic Curve Diffie-Hellman with the given keys.
/// Returns the derived raw key data.
static Uint8List _ecdh(ECPublicKey ephemeralPublicKey, ECPrivateKey privateKey) {
static Uint8List _ecdh(
ECPublicKey ephemeralPublicKey, ECPrivateKey privateKey) {
final sharedKey = ephemeralPublicKey.Q! * privateKey.d;
final sharedKeyBytes = pc_utils.encodeBigIntAsUnsigned(
sharedKey!.x!.toBigInteger()!);
print("Isolate:${Isolate.current.hashCode}: Shared Key (shared secret): ${base64Encode(sharedKeyBytes)}");
final sharedKeyBytes =
pc_utils.encodeBigIntAsUnsigned(sharedKey!.x!.toBigInteger()!);
print("Shared Key (shared secret): ${base64Encode(sharedKeyBytes)}");

return sharedKeyBytes;
}
Expand All @@ -60,7 +63,6 @@ class DecryptReports {
/// the resulting [FindMyLocationReport].
static FindMyLocationReport _decodePayload(
Uint8List payload, FindMyReport report) {

final latitude = payload.buffer.asByteData(0, 4).getUint32(0, Endian.big);
final longitude = payload.buffer.asByteData(4, 4).getUint32(0, Endian.big);
final accuracy = payload.buffer.asByteData(8, 1).getUint8(0);
Expand All @@ -74,14 +76,28 @@ class DecryptReports {

/// Decrypts the given cipher text with the key data using an AES-GCM block cipher.
/// Returns the decrypted raw data.
static Uint8List _decryptPayload(
Uint8List cipherText, Uint8List symmetricKey, Uint8List tag) {
static Future<Uint8List> _decryptPayload(
Uint8List cipherText, Uint8List symmetricKey, Uint8List tag) async {
final decryptionKey = symmetricKey.sublist(0, 16);
final iv = symmetricKey.sublist(16, symmetricKey.length);
if (kIsWeb) {
nice_crypto.SecretKey secretKey =
new nice_crypto.SecretKey(decryptionKey);

nice_crypto.SecretBox secretBox = new nice_crypto.SecretBox(cipherText,
nonce: iv, mac: nice_crypto.Mac(tag));

List<int> decrypted = await nice_crypto.AesGcm.with128bits()
.decrypt(secretBox, secretKey: secretKey);

return Uint8List.fromList(decrypted);
}

final aesGcm = GCMBlockCipher(AESEngine())
..init(false, AEADParameters(KeyParameter(decryptionKey),
tag.lengthInBytes * 8, iv, tag));
..init(
false,
AEADParameters(
KeyParameter(decryptionKey), tag.lengthInBytes * 8, iv, tag));

final plainText = Uint8List(cipherText.length);
var offset = 0;
Expand Down Expand Up @@ -109,7 +125,7 @@ class DecryptReports {
Uint8List out = Uint8List(shaDigest.digestSize);
shaDigest.doFinal(out, 0);

print("Isolate:${Isolate.current.hashCode}: Derived key: ${base64Encode(out)}");
print("Derived key: ${base64Encode(out)}");
return out;
}
}