eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 923dbe6fa93261fd6cd871a13b56701bafd4b604
parent 0d92b907d2b0e6880ce0845381d2353e3bc99e94
Author: Daniel Barlow <dan@telent.net>
Date:   Thu, 29 Jan 2026 19:10:07 +0000

add session registration with signature checking

Diffstat:
MCargo.lock | 24+++++++++++++++++++++---
Mclient/eculocate.py | 29++++++++++++++++++++++++++---
Mecu-esp32/Cargo.toml | 3++-
Mecu-esp32/src/ota.rs | 2+-
Mecu-esp32/src/streamer.rs | 27++++++++++++++++++---------
Mecu-esp32/src/wifi.rs | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mnotes.md | 1+
7 files changed, 128 insertions(+), 28 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4,6 +4,12 @@ version = 4 [[package]] name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "allocator-api2" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c583acf993cf4245c4acb0a2cc2ab1f9cc097de73411bb6d3647ff6af2b1013d" @@ -350,6 +356,7 @@ dependencies = [ "esp-radio", "esp-rtos", "esp-storage", + "hashbrown", "log", "nb 1.1.0", "rand_core 0.6.4", @@ -664,7 +671,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "641e43d6a60244429117ef2fa7a47182120c7561336ea01f6fb08d634f46bae1" dependencies = [ - "allocator-api2", + "allocator-api2 0.3.1", "cfg-if", "document-features", "enumset", @@ -838,7 +845,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "684c4de2f8907b73c9b891fbda65286a86d34fced4b856f36a7896c211f2f265" dependencies = [ - "allocator-api2", + "allocator-api2 0.3.1", "bt-hci", "cfg-if", "document-features", @@ -901,7 +908,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ec711c8d06e79c67b75d01595539e86b0aac209643af98ca87a12250428b3" dependencies = [ - "allocator-api2", + "allocator-api2 0.3.1", "cfg-if", "document-features", "embassy-executor", @@ -1042,6 +1049,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] name = "fugit" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1120,6 +1133,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2 0.2.21", + "equivalent", + "foldhash", +] [[package]] name = "heapless" diff --git a/client/eculocate.py b/client/eculocate.py @@ -9,6 +9,14 @@ import select import datetime from pathlib import Path import os +import struct +import secrets +import nacl +import nacl.hash +from nacl.encoding import RawEncoder + +from nacl.signing import SigningKey + from zeroconf import ServiceBrowser, ServiceListener, Zeroconf class ZListener(ServiceListener): @@ -31,9 +39,10 @@ class ZListener(ServiceListener): class Client(): - def __init__(self): + def __init__(self, private_key): self.selected_service_name = None self.bytes_recorded = 0 + self.private_key = private_key self.started = False self.host_addresses = {} @@ -67,7 +76,7 @@ class Client(): with open(self.output_pathname(), "wb") as outf: while self.started: print("sending subscribe") - b = bytes([interval >> 8, interval & 0xff, 0x54, 0x11, 0, 25] ) + b = self.session_key + bytes([interval >> 8, interval & 0xff, 0x54, 0x11, 0, 25] ) sock.send(b) timeout = time.time() + 60 while self.started and time.time() < timeout: @@ -86,8 +95,20 @@ class Client(): self.selected_service_name = name self.on_services_changed() + def register_session(self): + session_key = secrets.token_bytes(32) + sock = socket.create_connection(self.host_addresses[self.selected_service_name]) + sig = signing_key.sign(session_key, encoder=RawEncoder) + + sock.send(b"KEY0" + session_key + sig.signature) + response = sock.recv(200) + print(response.decode(errors="replace")) + sock.close() + return session_key + def start_recording(self): if self.selected_service_name != None: + self.session_key = self.register_session() self.started = True print("starting record") peer_thread = threading.Thread(target=self.recording_thread_main, daemon = True) @@ -108,6 +129,8 @@ class Client(): if __name__ == '__main__': import pprint + with open("privkey.bin", "rb") as f: + signing_key = SigningKey(f.read(), encoder=RawEncoder) exists = False @@ -123,7 +146,7 @@ if __name__ == '__main__': if self.host_addresses.get(fqname, False): print(f"found {fqname}") - client = MyClient() + client = MyClient(signing_key) while not exists: time.sleep(2) diff --git a/ecu-esp32/Cargo.toml b/ecu-esp32/Cargo.toml @@ -50,7 +50,7 @@ smoltcp = { version = "0.12.0", default-features = false, features = [ embassy-futures = "0.1.1" -embassy-sync = { version = "0.7" } +embassy-sync = "0.7" static_cell = "2" embassy-net = { version = "0.7.1", features = [ @@ -71,3 +71,4 @@ embedded-storage = "0.3.1" ed25519-dalek = { version = "2", default-features = false, features = ["alloc"] } rand_core = "0.6" nb = "1.1.0" +hashbrown = "0.16.1" diff --git a/ecu-esp32/src/ota.rs b/ecu-esp32/src/ota.rs @@ -11,7 +11,7 @@ use embedded_io_async::{Read,ReadExactError}; use esp_hal::sha; use core::convert::Infallible; -use log::{warn, info, error}; +use log::{warn, info}; const ERASE_BLOCK_BYTES : usize = 4096; const ED25519_SIGNATURE_BYTES : usize = 64; diff --git a/ecu-esp32/src/streamer.rs b/ecu-esp32/src/streamer.rs @@ -8,9 +8,11 @@ use embassy_futures::select::select; use alloc::vec::Vec; -use log::info; +use log::{warn,info}; use crate::ecu::Ecu; +use crate::wifi::SessionStore; + #[derive(Debug)] enum SubscriptionKind { @@ -68,7 +70,7 @@ impl Subscriber { // interval is per subscription: e.g. we want to get the rpm much // more often than the ecm id -pub async fn loop_udp_thing(stack : Stack<'_>, ecu: &mut Ecu<'_>) { +pub async fn loop_udp_thing(stack : Stack<'_>, ecu: &mut Ecu<'_>, sessions: &SessionStore ) { let mut rx_buffer = [0u8; 4096]; let mut tx_buffer = [0u8; 4096]; let mut rx_meta = [PacketMetadata::EMPTY; 512]; @@ -96,13 +98,20 @@ pub async fn loop_udp_thing(stack : Stack<'_>, ecu: &mut Ecu<'_>) { if let Ok((n, meta)) = res { info!("packet from {:?}", meta.endpoint); - let mut s = Subscriber { - subscriptions: Vec::from([]), - endpoint: meta.endpoint, - until: Instant::now() + Duration::from_secs(60), - }; - s.update_subscriptions(&buf[0..n]); - subscriber = Some(s); + if sessions.is_registered(&buf[..32]).await { + let mut s = Subscriber { + subscriptions: Vec::from([]), + endpoint: meta.endpoint, + until: Instant::now() + Duration::from_secs(60), + }; + + s.update_subscriptions(&buf[32..n]); + subscriber = Some(s); + } else { + warn!("subscription attempt without registered session {:?}", + &buf[..32]); + subscriber = None; + } } } if let Some(ref mut sub) = subscriber { diff --git a/ecu-esp32/src/wifi.rs b/ecu-esp32/src/wifi.rs @@ -7,6 +7,9 @@ use alloc::vec::Vec; use alloc::string::String; use alloc::format; +use hashbrown::HashMap; + +use ed25519_dalek::{VerifyingKey, Verifier, Signature}; use embassy_executor::Spawner; use embassy_net::{ @@ -14,8 +17,10 @@ use embassy_net::{ StackResources }; use embassy_futures::join::join3; +use embassy_sync::mutex::Mutex; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_time::{Duration, Timer}; +use embassy_time::{Duration, Timer, Instant}; use esp_alloc as _; use esp_backtrace as _; use esp_hal::{ @@ -89,6 +94,43 @@ fn dns_records(my_ip_address: [u8;4], serial: Option<usize>) -> Vec<mdns::Resour Vec::from(records) } +pub struct SessionStore { + map : Mutex<CriticalSectionRawMutex, HashMap<[u8; 32], Instant>>, + public_key: VerifyingKey +} + +impl SessionStore { + pub fn new() -> Self { + let m = HashMap::new(); + let verifying_key_bytes = include_bytes!("../../pubkey.bin"); + let public_key: VerifyingKey = VerifyingKey::from_bytes(verifying_key_bytes).expect("verify"); + + Self { map : Mutex::new(m), public_key } + } + + pub async fn register(&self, key: &[u8], signature: &[u8]) -> bool { + info!("pretending to register {:?}", key); + + if self.public_key.verify(key, &Signature::try_from(signature).expect("64 bytes")).is_ok() { + let mut m = self.map.lock().await; + m.insert(key.try_into().unwrap(), Instant::now() + Duration::from_secs(86400)); + true + } else { + warn!("wrong key"); + false + } + } + + pub async fn is_registered(&self, key: &[u8]) -> bool { + let m = self.map.lock().await; + match m.get(key) { + Some(expiry) => *expiry > Instant::now(), + None => false + } + } +} + + pub async fn start_wifi(spawner: &Spawner, flasher: crate::ota::Flasher<'_>, wifi_peripheral: esp_hal::peripherals::WIFI<'static>, @@ -140,9 +182,13 @@ pub async fn start_wifi(spawner: &Spawner, let mut serial = None; + + + let session_store = SessionStore::new(); + join3( - crate::streamer::loop_udp_thing(stack, ecu), - run_tcp_handler(stack, flasher), + crate::streamer::loop_udp_thing(stack, ecu, &session_store), + run_tcp_handler(stack, &session_store, flasher), async { loop { let mdns_thread = mdns::responder( @@ -213,13 +259,8 @@ async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) { } -fn register_session_key(key : &[u8]) { - info!("got session key{:?}", key); -} - - - async fn run_tcp_handler(stack : Stack<'_>, + sessions : &SessionStore, mut flasher : crate::ota::Flasher<'_>) { let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; @@ -248,8 +289,15 @@ async fn run_tcp_handler(stack : Stack<'_>, b"KEY0" => { info!("sessionkey"); if let Ok(_) = socket.read_exact(&mut buf).await { - let (_sig, key) = buf.split_at(64); - register_session_key(&key); + let (key, sig) = buf.split_at(32); + if sessions.register(key, sig).await { + info!("registered? {}", sessions.is_registered(key).await); + info!("write {:?}", socket.write(b"BYE\n").await); + let _ = socket.flush().await; + } else { + warn!("couldn't register key") + } + socket.close(); } }, _ => { diff --git a/notes.md b/notes.md @@ -141,6 +141,7 @@ X as PoC, add the signed thing into the rust binary and verify the signature at X verify signature before flashing - maintain a hash of session keys for udp listener and expire them eventually - update the subscription request parsing to expect a session key, validate it +- udp client needs to send the key - wifi provisioning over ble - add the 12v->5v buck converter, make a cable to plug into diag port - ride