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:
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