From ff5e08ffdc85166a2eae34920f379b9487b26728 Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 25 Nov 2025 18:37:52 +0100 Subject: [PATCH 01/16] feat(connd): profile encryption using a static key --- Cargo.lock | 19 ++ Cargo.toml | 2 + orb-connd/Cargo.toml | 6 + orb-connd/src/key_material/mod.rs | 11 ++ orb-connd/src/key_material/static_key.rs | 13 ++ orb-connd/src/key_material/trustzone.rs | 13 ++ orb-connd/src/lib.rs | 28 ++- orb-connd/src/main.rs | 6 +- orb-connd/src/network_manager/mod.rs | 39 +++- orb-connd/src/profile_store/mod.rs | 240 +++++++++++++++++++++++ orb-connd/src/service/mod.rs | 102 ++++++++-- orb-connd/tests/fixture.rs | 2 + 12 files changed, 441 insertions(+), 40 deletions(-) create mode 100644 orb-connd/src/key_material/mod.rs create mode 100644 orb-connd/src/key_material/static_key.rs create mode 100644 orb-connd/src/key_material/trustzone.rs create mode 100644 orb-connd/src/profile_store/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 47c8235a..5e89755a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2040,6 +2040,19 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.42" @@ -7945,11 +7958,14 @@ dependencies = [ name = "orb-connd" version = "0.1.0" dependencies = [ + "async-tempfile", "async-trait", "base64 0.22.1", "bon", + "chacha20poly1305", "chrono", "color-eyre", + "dashmap 6.1.0", "derive_more 2.0.1", "dogstatsd", "flume", @@ -7964,8 +7980,10 @@ dependencies = [ "orb-telemetry", "p256 0.13.2", "prelude", + "rand 0.8.5", "regex", "rusty_network_manager", + "secrecy 0.8.0", "serde", "serde_json", "test-utils", @@ -7974,6 +7992,7 @@ dependencies = [ "tokio-stream", "tracing", "tracing-subscriber", + "uuid", "zbus", ] diff --git a/Cargo.toml b/Cargo.toml index ac40259b..b0ee9403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ bon = "3.5.1" bytes = "1.7.1" camino = "1.1.6" cc = "1.2.16" +chacha20poly1305 = "0.10.1" chrono = "0.4.40" clap = { version = "4.5", features = ["derive", "env"] } clap-num = "1.2.0" @@ -159,6 +160,7 @@ tracing-journald = "0.3.0" tracing-opentelemetry = { version = "0.28", default-features = false } tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2.5.4" +uuid = "1.18.1" wiremock = "0.6.4" zbus = { version = "4.4.0", default-features = false, features = ["tokio"] } zbus_systemd = "0.25600.0" diff --git a/orb-connd/Cargo.toml b/orb-connd/Cargo.toml index 3d3a2df5..2df1c44f 100644 --- a/orb-connd/Cargo.toml +++ b/orb-connd/Cargo.toml @@ -38,8 +38,14 @@ p256.workspace = true nom.workspace = true prelude.workspace = true hex.workspace = true +secrecy.workspace = true +chacha20poly1305.workspace = true +rand.workspace = true +dashmap.workspace = true +uuid = { workspace = true, features = ["v4"] } [dev-dependencies] +async-tempfile.workspace = true test-utils.workspace = true nix = { workspace = true, features = ["socket"] } test-with.workspace = true diff --git a/orb-connd/src/key_material/mod.rs b/orb-connd/src/key_material/mod.rs new file mode 100644 index 00000000..d6596e77 --- /dev/null +++ b/orb-connd/src/key_material/mod.rs @@ -0,0 +1,11 @@ +use async_trait::async_trait; +use color_eyre::Result; +use secrecy::SecretVec; + +pub mod static_key; +pub mod trustzone; + +#[async_trait] +pub trait KeyMaterial: Send + Sync + 'static { + async fn fetch(&self) -> Result>; +} diff --git a/orb-connd/src/key_material/static_key.rs b/orb-connd/src/key_material/static_key.rs new file mode 100644 index 00000000..137ce84c --- /dev/null +++ b/orb-connd/src/key_material/static_key.rs @@ -0,0 +1,13 @@ +use super::KeyMaterial; +use async_trait::async_trait; +use color_eyre::Result; +use secrecy::SecretVec; + +pub struct StaticKey(pub Vec); + +#[async_trait] +impl KeyMaterial for StaticKey { + async fn fetch(&self) -> Result> { + Ok(self.0.clone().into()) + } +} diff --git a/orb-connd/src/key_material/trustzone.rs b/orb-connd/src/key_material/trustzone.rs new file mode 100644 index 00000000..a189e5e9 --- /dev/null +++ b/orb-connd/src/key_material/trustzone.rs @@ -0,0 +1,13 @@ +use super::KeyMaterial; +use async_trait::async_trait; +use color_eyre::Result; +use secrecy::SecretVec; + +pub struct TrustZone; + +#[async_trait] +impl KeyMaterial for TrustZone { + async fn fetch(&self) -> Result> { + todo!("theo, u impl here") + } +} diff --git a/orb-connd/src/lib.rs b/orb-connd/src/lib.rs index 710d60a1..e179caa6 100644 --- a/orb-connd/src/lib.rs +++ b/orb-connd/src/lib.rs @@ -1,5 +1,6 @@ use color_eyre::eyre::{ContextCompat, Result}; use derive_more::Display; +use key_material::KeyMaterial; use modem_manager::ModemManager; use network_manager::NetworkManager; use orb_info::orb_os_release::OrbOsRelease; @@ -13,16 +14,19 @@ use tokio::{ }; use tokio::{task, time}; use tracing::error; -use tracing::{info, warn}; +use tracing::info; +pub mod key_material; pub mod modem_manager; pub mod network_manager; pub mod service; pub mod statsd; pub mod telemetry; -mod utils; pub mod wpa_ctrl; +mod profile_store; +mod utils; + #[bon::builder(finish_fn = run)] pub async fn program( sysfs: impl AsRef, @@ -33,6 +37,7 @@ pub async fn program( statsd_client: impl StatsdClient, modem_manager: impl ModemManager, connect_timeout: Duration, + key_material: impl KeyMaterial, ) -> Result { let sysfs = sysfs.as_ref().to_path_buf(); let modem_manager: Arc = Arc::new(modem_manager); @@ -50,21 +55,10 @@ pub async fn program( os_release.release_type, cap, connect_timeout, - ); - - connd.setup_default_profiles().await?; - - if let Err(e) = connd.import_wpa_conf(&usr_persistent).await { - warn!("failed to import legacy wpa config {e}"); - } - - if let Err(e) = connd.ensure_networking_enabled().await { - warn!("failed to ensure networking is enabled {e}"); - } - - if let Err(e) = connd.ensure_nm_state_below_max_size(usr_persistent).await { - warn!("failed to ensure nm state below max size: {e}"); - } + usr_persistent, + key_material, + ) + .await?; let mut tasks = vec![connd.spawn()]; diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index 676a7b08..727130d2 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -1,7 +1,8 @@ use color_eyre::eyre::Result; use orb_connd::{ - modem_manager::cli::ModemManagerCli, network_manager::NetworkManager, - statsd::dd::DogstatsdClient, wpa_ctrl::cli::WpaCli, + key_material::static_key::StaticKey, modem_manager::cli::ModemManagerCli, + network_manager::NetworkManager, statsd::dd::DogstatsdClient, + wpa_ctrl::cli::WpaCli, }; use orb_info::orb_os_release::OrbOsRelease; use std::time::Duration; @@ -29,6 +30,7 @@ async fn main() -> Result<()> { .statsd_client(DogstatsdClient::new()) .modem_manager(ModemManagerCli) .connect_timeout(Duration::from_secs(15)) + .key_material(StaticKey(b"test".into())) .run() .await?; diff --git a/orb-connd/src/network_manager/mod.rs b/orb-connd/src/network_manager/mod.rs index e7bee9c4..d569c9b3 100644 --- a/orb-connd/src/network_manager/mod.rs +++ b/orb-connd/src/network_manager/mod.rs @@ -14,9 +14,11 @@ use rusty_network_manager::{ AccessPointProxy, ActiveProxy, DeviceProxy, NM80211ApFlags, NM80211ApSecurityFlags, NetworkManagerProxy, SettingsConnectionProxy, SettingsProxy, WirelessProxy, }; +use serde::{Deserialize, Serialize}; use std::{collections::HashMap, sync::Arc, time::Duration}; use tokio::{fs, time}; use tracing::warn; +use uuid::Uuid; use zbus::zvariant::{Array, ObjectPath, OwnedObjectPath, OwnedValue, Value}; use crate::wpa_ctrl::WpaCtrl; @@ -279,15 +281,19 @@ impl NetworkManager { ssid: &str, sec: WifiSec, psk: &str, + #[builder(default = false)] persist: bool, #[builder(default = true)] autoconnect: bool, #[builder(default = 0)] priority: i32, #[builder(default = false)] hidden: bool, #[builder(default = -1)] max_autoconnect_retries: i64, // -1 here means apply gobal default - ) -> Result { + ) -> Result { self.remove_profile(id).await?; + let uuid = Uuid::new_v4().to_string(); + let connection = HashMap::from_iter([ kv("id", id), + kv("uuid", uuid.as_str()), kv("type", "802-11-wireless"), kv("autoconnect", autoconnect), kv("autoconnect-priority", priority), @@ -300,22 +306,39 @@ impl NetworkManager { kv("hidden", hidden), ]); - let sec = HashMap::from_iter([kv("key-mgmt", sec.as_nm_str()), kv("psk", psk)]); + let secv = + HashMap::from_iter([kv("key-mgmt", sec.as_nm_str()), kv("psk", psk)]); let ipv4 = HashMap::from_iter([kv("method", "auto")]); let ipv6 = HashMap::from_iter([kv("method", "ignore")]); let settings = HashMap::from_iter([ ("connection", connection), ("802-11-wireless", wifi), - ("802-11-wireless-security", sec), + ("802-11-wireless-security", secv), ("ipv4", ipv4), ("ipv6", ipv6), ]); let sp = SettingsProxy::new(&self.conn).await?; - let path = sp.add_connection(settings).await?; + let path = if persist { + sp.add_connection(settings).await? + } else { + sp.add_connection_unsaved(settings).await? + }; - Ok(path) + let profile = WifiProfile { + id: id.into(), + uuid, + ssid: ssid.into(), + sec, + psk: psk.into(), + autoconnect, + priority, + hidden, + path: path.to_string(), + }; + + Ok(profile) } /// Adds a cellular profile ensuring id uniqueness @@ -351,7 +374,7 @@ impl NetworkManager { ]); let sp = SettingsProxy::new(&self.conn).await?; - sp.add_connection(settings).await?; + sp.add_connection_unsaved(settings).await?; Ok(()) } @@ -531,7 +554,7 @@ pub enum NetworkKind { Cellular, // "gsm" } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct WifiProfile { pub id: String, pub uuid: String, @@ -742,7 +765,7 @@ impl From for ApCap { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Hash, Serialize, Deserialize)] pub enum WifiSec { /// No protection (or RSN IE present but no auth/key-mgmt required). Open, diff --git a/orb-connd/src/profile_store/mod.rs b/orb-connd/src/profile_store/mod.rs new file mode 100644 index 00000000..486b1205 --- /dev/null +++ b/orb-connd/src/profile_store/mod.rs @@ -0,0 +1,240 @@ +use crate::{key_material::KeyMaterial, network_manager::WifiProfile}; +use chacha20poly1305::{aead::Aead, AeadCore, Key, KeyInit, XChaCha20Poly1305, XNonce}; +use color_eyre::{ + eyre::{bail, ensure, eyre, Context}, + Result, +}; +use dashmap::{mapref::one::Ref, DashMap}; +use rand::rngs::OsRng; +use secrecy::{ExposeSecret, SecretVec}; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use tokio::fs; + +pub struct ProfileStore { + key: Result>, + store_path: PathBuf, + profiles: Arc>, +} + +impl ProfileStore { + const FILENAME: &str = "nmprofiles"; + + pub async fn from_store( + store_path: impl Into, + key_material: &impl KeyMaterial, + ) -> Self { + Self { + key: key_material.fetch().await, + store_path: store_path.into(), + profiles: Arc::new(DashMap::new()), + } + } + + pub async fn import(&self) -> Result<()> { + let secret = match &self.key { + Err(e) => bail!("failed to retrieve key material: {e}"), + Ok(v) => v, + }; + + let path = self.store_path.join(Self::FILENAME); + let mut bytes = fs::read(&path) + .await + .wrap_err_with(|| format!("failed to read profile store at {path:?}"))?; + + ensure!( + bytes.len() >= 24, + "profile store is too short ({} bytes), should be at least 24 bytes", + bytes.len() + ); + + let contents = bytes.split_off(24); + let nonce = bytes; + + let json = decrypt(contents, nonce, secret.expose_secret())?; + let profiles: HashMap = serde_json::from_slice(&json)?; + + for (key, value) in profiles { + self.profiles.insert(key, value); + } + + Ok(()) + } + + pub async fn commit(&self) -> Result<()> { + let secret = match &self.key { + Err(e) => bail!("failed to retrieve key material: {e}"), + Ok(v) => v, + }; + + let profiles: HashMap<_, _> = self + .profiles + .iter() + .map(|entry| (entry.key().clone(), entry.value().clone())) + .collect(); + + let json = serde_json::to_vec(&profiles)?; + let bytes = encrypt(json, secret.expose_secret())?; + + let path = self.store_path.join(Self::FILENAME); + fs::write(path, bytes).await?; + + Ok(()) + } + + pub fn insert(&self, profile: WifiProfile) { + self.profiles.insert(profile.ssid.clone(), profile); + } + + pub fn remove(&self, ssid: &str) -> Option { + self.profiles.remove(ssid).map(|(_, value)| value) + } + + pub fn get(&self, ssid: &str) -> Option> { + self.profiles.get(ssid) + } + + pub fn values(&self) -> Vec { + self.profiles.iter().map(|x| x.value().clone()).collect() + } +} + +fn decrypt(bytes: Vec, mut nonce: Vec, secret: &[u8]) -> Result> { + ensure!( + nonce.len() == 24, + "none len must be 24 bytes, instead is {}", + nonce.len() + ); + + ensure!( + secret.len() == 32, + "secret len must be 32 bytes, instead is {}", + secret.len() + ); + + let nonce = XNonce::from_mut_slice(&mut nonce); + + let secret = Key::from_slice(secret); + let cipher = XChaCha20Poly1305::new(secret); + + let plaintext = cipher + .decrypt(nonce, bytes.as_slice()) + .map_err(|e| eyre!("failed to decrypt profiles: {e:?}"))?; + + Ok(plaintext) +} + +fn encrypt(bytes: Vec, secret: &[u8]) -> Result> { + ensure!( + secret.len() == 32, + "secret len must be 32 bytes, instead is {}", + secret.len() + ); + + let mut rng = OsRng; + let nonce = XChaCha20Poly1305::generate_nonce(&mut rng); + let secret = Key::from_slice(secret); + + let cipher = XChaCha20Poly1305::new(secret); + let ciphertext = cipher + .encrypt(&nonce, bytes.as_slice()) + .map_err(|e| eyre!("failed to encrypt profiles: {e:?}"))?; + + let mut out = Vec::new(); + out.extend_from_slice(nonce.as_slice()); + out.extend(ciphertext); + + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::network_manager::WifiSec; + use async_tempfile::TempDir; + use secrecy::SecretVec; + use std::path::Path; + + const TEST_KEY: [u8; 32] = [0x42; 32]; + + // Helper to build a ProfileStore with a static key, no KeyMaterial needed. + fn make_store(dir: &Path) -> ProfileStore { + ProfileStore { + key: Ok(SecretVec::new(TEST_KEY.to_vec())), + store_path: dir.to_path_buf(), + profiles: Arc::new(DashMap::new()), + } + } + + #[tokio::test] + async fn it_imports_and_commits_wifi_profiles() { + // Arrange + let tmpdir = TempDir::new().await.unwrap(); + let tmpdirpath = tmpdir.to_path_buf(); + + let expected = [ + WifiProfile { + id: "test-ssid1".into(), + ssid: "test-ssid1".into(), + uuid: String::new(), + sec: WifiSec::Wpa2Psk, + psk: "12345678".into(), + autoconnect: true, + priority: 0, + hidden: false, + path: String::new(), + }, + WifiProfile { + id: "test-ssid2".into(), + ssid: "test-ssid2".into(), + uuid: String::new(), + sec: WifiSec::Wpa3Sae, + psk: "12345678".into(), + autoconnect: true, + priority: 1, + hidden: false, + path: String::new(), + }, + WifiProfile { + id: "test-ssid3".into(), + ssid: "test-ssid3".into(), + uuid: String::new(), + sec: WifiSec::Wpa2Psk, + psk: "12345678".into(), + autoconnect: true, + priority: 2, + hidden: false, + path: String::new(), + }, + ]; + + let store = make_store(&tmpdirpath); + + for profile in &expected { + store.insert(profile.clone()); + } + + // Act + store.commit().await.unwrap(); + + // Second store: load from disk and import + let store = make_store(&tmpdirpath); + store.import().await.unwrap(); + + // Assert + let mut actual: Vec<_> = store.values(); + actual.sort_by_key(|p| p.priority); + assert_eq!(actual, expected); + + // Act: remove some profiles + store.remove("test-ssid2"); + store.remove("test-ssid3"); + store.commit().await.unwrap(); + + // Assert + let store = make_store(&tmpdirpath); + store.import().await.unwrap(); + + let actual: Vec<_> = store.values(); + assert_eq!(actual, vec![expected[0].clone()]); + } +} diff --git a/orb-connd/src/service/mod.rs b/orb-connd/src/service/mod.rs index 1824ac89..aba38dc6 100644 --- a/orb-connd/src/service/mod.rs +++ b/orb-connd/src/service/mod.rs @@ -1,6 +1,8 @@ +use crate::key_material::KeyMaterial; use crate::network_manager::{ AccessPoint, ActiveConnState, NetworkManager, WifiProfile, WifiSec, }; +use crate::profile_store::{self, ProfileStore}; use crate::utils::{IntoZResult, State}; use crate::OrbCapabilities; use async_trait::async_trait; @@ -39,9 +41,11 @@ pub struct ConndService { cap: OrbCapabilities, magic_qr_applied_at: State>, connect_timeout: Duration, + profile_store: ProfileStore, } impl ConndService { + const NM_FOLDER: &str = "network-manager"; const DEFAULT_CELLULAR_PROFILE: &str = "cellular"; const DEFAULT_CELLULAR_APN: &str = "em"; const DEFAULT_CELLULAR_IFACE: &str = "cdc-wdm0"; @@ -51,21 +55,53 @@ impl ConndService { const MAGIC_QR_TIMESPAN_MIN: i64 = 10; const NM_STATE_MAX_SIZE_KB: u64 = 1024; - pub fn new( + pub async fn new( session_dbus: zbus::Connection, nm: NetworkManager, release: OrbRelease, cap: OrbCapabilities, connect_timeout: Duration, - ) -> Self { - Self { + usr_persistent: impl AsRef, + key_material: impl KeyMaterial, + ) -> Result { + let usr_persistent = usr_persistent.as_ref(); + + let profile_store = ProfileStore::from_store( + &usr_persistent.join(Self::NM_FOLDER), + &key_material, + ) + .await; + + let connd = Self { session_dbus, nm, release, cap, magic_qr_applied_at: State::new(DateTime::default()), connect_timeout, + profile_store, + }; + + // this must be called before setting up default profiles! + if let Err(e) = connd.import_profiles().await { + error!("connd failed to import saved profiles: {e}"); } + + connd.setup_default_profiles().await?; + + if let Err(e) = connd.import_wpa_conf(&usr_persistent).await { + warn!("failed to import legacy wpa config {e}"); + } + + if let Err(e) = connd.ensure_networking_enabled().await { + warn!("failed to ensure networking is enabled {e}"); + } + + if let Err(e) = connd.ensure_nm_state_below_max_size(usr_persistent).await { + warn!("failed to ensure nm state below max size: {e}"); + } + + Ok(connd) } pub fn spawn(self) -> JoinHandle> { @@ -145,6 +181,22 @@ impl ConndService { Ok(()) } + pub async fn import_profiles(&self) -> Result<()> { + // this first part of the function is importing old unencrypted NM profiles + // stored on disk and deleting them. other nm calls to store profiles will only store + // them in memory going forward + let old_profiles = self.nm.list_wifi_profiles().await?; + for profile in old_profiles { + self.nm.remove_profile(&profile.id).await?; + self.profile_store.insert(profile); + } + + self.profile_store.import().await?; + self.profile_store.commit().await?; + + Ok(()) + } + pub async fn import_wpa_conf(&self, wpa_conf_dir: impl AsRef) -> Result<()> { let wpa_conf_path = wpa_conf_dir.as_ref().join("wpa_supplicant-wlan0.conf"); match File::open(&wpa_conf_path).await { @@ -180,7 +232,7 @@ impl ConndService { &self, usr_persistent: impl AsRef, ) -> Result<()> { - let nm_dir = usr_persistent.as_ref().join("network-manager"); + let nm_dir = usr_persistent.as_ref().join(Self::NM_FOLDER); let dir_size_kb = async || -> Result { let mut total_bytes = 0u64; let mut stack = vec![nm_dir.clone()]; @@ -204,14 +256,14 @@ impl ConndService { let dir_size = dir_size_kb().await?; if dir_size < Self::NM_STATE_MAX_SIZE_KB { - info!("/usr/persistent/network-manager is below 1024kB. current size {dir_size}kB"); + info!("{nm_dir:?} is below 1024kB. current size {dir_size}kB"); return Ok(()); } - warn!("/usr/persistent/network-manager is above 1024kB. current size {dir_size}. attempting to reduce size"); + warn!("{nm_dir:?} is above 1024kB. current size {dir_size}kB. attempting to reduce size"); // remove excess wifi profiles - let mut wifi_profiles = self.nm.list_wifi_profiles().await?; + let mut wifi_profiles = self.profile_store.values(); wifi_profiles.sort_by_key(|p| p.priority); let profiles_to_keep = 2; @@ -223,13 +275,13 @@ impl ConndService { } self.nm.remove_profile(&profile.id).await?; + self.profile_store.remove(&profile.ssid); } + self.profile_store.commit().await?; + // remove dhcp leases and seen-bssids - let varlib = usr_persistent - .as_ref() - .join("network-manager") - .join("varlib"); + let varlib = usr_persistent.as_ref().join(Self::NM_FOLDER).join("varlib"); let seen_bssids = varlib.join("seen-bssids"); let mut to_delete = vec![seen_bssids]; @@ -330,7 +382,8 @@ impl ConndT for ConndService { let prio = self.get_next_priority().await.into_z()?; - self.nm + let profile = self + .nm .wifi_profile(&ssid) .ssid(&ssid) .sec(sec) @@ -342,6 +395,13 @@ impl ConndT for ConndService { .await .into_z()?; + self.profile_store.insert(profile); + if let Err(e) = self.profile_store.commit().await { + error!( + "failed to commit profile store when removing wifi profile. err: {e}" + ); + } + info!("profile for ssid: {ssid}, saved successfully"); Ok(()) @@ -355,6 +415,13 @@ impl ConndT for ConndService { self.nm.remove_profile(&ssid).await.into_z()?; + self.profile_store.remove(&ssid); + if let Err(e) = self.profile_store.commit().await { + error!( + "failed to commit profile store when removing wifi profile. err: {e}" + ); + } + Ok(()) } @@ -542,13 +609,22 @@ impl ConndT for ConndService { .await .into_z()?; + let path = profile.path.clone(); + + self.profile_store.insert(profile); + if let Err(e) = self.profile_store.commit().await { + error!( + "failed to commit profile store when removing wifi profile. err: {e}" + ); + } + for ap in aps { if ap.ssid == ssid { info!("connecting to ap {ap:?}"); self.nm .connect_to_wifi( - &profile, + &path, Self::DEFAULT_WIFI_IFACE, self.connect_timeout, ) diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index 4e396fcb..075a2a8d 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -4,6 +4,7 @@ use color_eyre::Result; use mockall::mock; use nix::libc; use orb_connd::{ + key_material::static_key::StaticKey, modem_manager::{ connection_state::ConnectionState, Location, Modem, ModemId, ModemInfo, ModemManager, Signal, SimId, SimInfo, @@ -113,6 +114,7 @@ impl Fixture { .usr_persistent(usr_persistent.clone()) .session_bus(conn.clone()) .connect_timeout(Duration::from_secs(1)) + .key_material(StaticKey([0x42; 32].into())) .run() .await .unwrap(); From b5a6d9692e821d24e9d90b31a3dc34378ee17b4a Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 25 Nov 2025 18:54:25 +0100 Subject: [PATCH 02/16] wip --- orb-connd/tests/fixture.rs | 23 ++++++++++++++--------- prelude/src/future/callback.rs | 27 +++++++++++++++++++++++++++ prelude/src/future/mod.rs | 2 +- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index 075a2a8d..f23d7bf5 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -17,7 +17,7 @@ use orb_connd::{ }; use orb_connd_dbus::ConndProxy; use orb_info::orb_os_release::{OrbOsPlatform, OrbOsRelease, OrbRelease}; -use prelude::future::Callback; +use prelude::future::{Callback, CallbackOnce}; use std::{env, path::PathBuf, time::Duration}; use test_utils::docker::{self, Container}; use tokio::{fs, task::JoinHandle, time}; @@ -28,14 +28,19 @@ pub struct Fixture { pub nm: NetworkManager, pub container: Container, conn: zbus::Connection, - program_handles: Vec>>, + program_handles: Option>>>, + run_fn: CallbackOnce<(), Vec>>>, pub sysfs: PathBuf, pub usr_persistent: PathBuf, } impl Drop for Fixture { fn drop(&mut self) { - for handle in &self.program_handles { + let Some(handles) = self.program_handles.take() else { + return; + }; + + for handle in &handles { handle.abort(); } } @@ -100,7 +105,7 @@ impl Fixture { wpa_ctrl.unwrap_or_else(default_mock_wpa_cli), ); - let program_handles = program() + let program = program() .os_release(OrbOsRelease { release_type: release, orb_os_platform_type: platform, @@ -114,10 +119,9 @@ impl Fixture { .usr_persistent(usr_persistent.clone()) .session_bus(conn.clone()) .connect_timeout(Duration::from_secs(1)) - .key_material(StaticKey([0x42; 32].into())) - .run() - .await - .unwrap(); + .key_material(StaticKey([0x42; 32].into())); + + let run_fn = CallbackOnce::new(async move |_| program.run().await.unwrap()); let secs = if env::var("GITHUB_ACTIONS").is_ok() { 5 @@ -130,7 +134,8 @@ impl Fixture { Self { nm, conn, - program_handles, + program_handles: None, + run_fn, container, sysfs, usr_persistent, diff --git a/prelude/src/future/callback.rs b/prelude/src/future/callback.rs index ce6b59b7..b6e1295a 100644 --- a/prelude/src/future/callback.rs +++ b/prelude/src/future/callback.rs @@ -33,6 +33,33 @@ where } } +pub struct CallbackOnce( + Box BoxFuture + Sync + 'static>, +); + +impl CallbackOnce { + pub fn new(f: F) -> CallbackOnce + where + F: FnOnce(Args) -> Fut + Sync + 'static, + Fut: Future + Send + 'static, + Args: Send + 'static, + Ret: 'static, + { + let f = Box::new(f); + CallbackOnce(Box::new(move |args: Args| Box::pin(f(args)))) + } +} + +impl CallbackOnce +where + Args: 'static, + Ret: 'static, +{ + pub fn call(self, args: Args) -> BoxFuture { + (self.0)(args) + } +} + #[cfg(test)] mod tests { use super::Callback; diff --git a/prelude/src/future/mod.rs b/prelude/src/future/mod.rs index 1badf050..d04b9625 100644 --- a/prelude/src/future/mod.rs +++ b/prelude/src/future/mod.rs @@ -1,4 +1,4 @@ mod callback; mod state; -pub use callback::Callback; +pub use callback::{Callback, CallbackOnce}; pub use state::State; From ae9d5812fb110de58729434798958c4f08c5bbb1 Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 25 Nov 2025 19:02:11 +0100 Subject: [PATCH 03/16] Revert "wip" This reverts commit b5a6d9692e821d24e9d90b31a3dc34378ee17b4a. --- orb-connd/tests/fixture.rs | 23 +++++++++-------------- prelude/src/future/callback.rs | 27 --------------------------- prelude/src/future/mod.rs | 2 +- 3 files changed, 10 insertions(+), 42 deletions(-) diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index f23d7bf5..075a2a8d 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -17,7 +17,7 @@ use orb_connd::{ }; use orb_connd_dbus::ConndProxy; use orb_info::orb_os_release::{OrbOsPlatform, OrbOsRelease, OrbRelease}; -use prelude::future::{Callback, CallbackOnce}; +use prelude::future::Callback; use std::{env, path::PathBuf, time::Duration}; use test_utils::docker::{self, Container}; use tokio::{fs, task::JoinHandle, time}; @@ -28,19 +28,14 @@ pub struct Fixture { pub nm: NetworkManager, pub container: Container, conn: zbus::Connection, - program_handles: Option>>>, - run_fn: CallbackOnce<(), Vec>>>, + program_handles: Vec>>, pub sysfs: PathBuf, pub usr_persistent: PathBuf, } impl Drop for Fixture { fn drop(&mut self) { - let Some(handles) = self.program_handles.take() else { - return; - }; - - for handle in &handles { + for handle in &self.program_handles { handle.abort(); } } @@ -105,7 +100,7 @@ impl Fixture { wpa_ctrl.unwrap_or_else(default_mock_wpa_cli), ); - let program = program() + let program_handles = program() .os_release(OrbOsRelease { release_type: release, orb_os_platform_type: platform, @@ -119,9 +114,10 @@ impl Fixture { .usr_persistent(usr_persistent.clone()) .session_bus(conn.clone()) .connect_timeout(Duration::from_secs(1)) - .key_material(StaticKey([0x42; 32].into())); - - let run_fn = CallbackOnce::new(async move |_| program.run().await.unwrap()); + .key_material(StaticKey([0x42; 32].into())) + .run() + .await + .unwrap(); let secs = if env::var("GITHUB_ACTIONS").is_ok() { 5 @@ -134,8 +130,7 @@ impl Fixture { Self { nm, conn, - program_handles: None, - run_fn, + program_handles, container, sysfs, usr_persistent, diff --git a/prelude/src/future/callback.rs b/prelude/src/future/callback.rs index b6e1295a..ce6b59b7 100644 --- a/prelude/src/future/callback.rs +++ b/prelude/src/future/callback.rs @@ -33,33 +33,6 @@ where } } -pub struct CallbackOnce( - Box BoxFuture + Sync + 'static>, -); - -impl CallbackOnce { - pub fn new(f: F) -> CallbackOnce - where - F: FnOnce(Args) -> Fut + Sync + 'static, - Fut: Future + Send + 'static, - Args: Send + 'static, - Ret: 'static, - { - let f = Box::new(f); - CallbackOnce(Box::new(move |args: Args| Box::pin(f(args)))) - } -} - -impl CallbackOnce -where - Args: 'static, - Ret: 'static, -{ - pub fn call(self, args: Args) -> BoxFuture { - (self.0)(args) - } -} - #[cfg(test)] mod tests { use super::Callback; diff --git a/prelude/src/future/mod.rs b/prelude/src/future/mod.rs index d04b9625..1badf050 100644 --- a/prelude/src/future/mod.rs +++ b/prelude/src/future/mod.rs @@ -1,4 +1,4 @@ mod callback; mod state; -pub use callback::{Callback, CallbackOnce}; +pub use callback::Callback; pub use state::State; From 4e3530ac0cc151740ad2c97b7bdd58afa8215aec Mon Sep 17 00:00:00 2001 From: vmenge Date: Wed, 26 Nov 2025 01:54:38 +0100 Subject: [PATCH 04/16] clippy --- orb-connd/src/service/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orb-connd/src/service/mod.rs b/orb-connd/src/service/mod.rs index aba38dc6..dcccef02 100644 --- a/orb-connd/src/service/mod.rs +++ b/orb-connd/src/service/mod.rs @@ -2,7 +2,7 @@ use crate::key_material::KeyMaterial; use crate::network_manager::{ AccessPoint, ActiveConnState, NetworkManager, WifiProfile, WifiSec, }; -use crate::profile_store::{self, ProfileStore}; +use crate::profile_store::ProfileStore; use crate::utils::{IntoZResult, State}; use crate::OrbCapabilities; use async_trait::async_trait; From b97b162f20d044e767a6826f13fb1a8fbc275543 Mon Sep 17 00:00:00 2001 From: vmenge Date: Wed, 26 Nov 2025 14:03:01 +0100 Subject: [PATCH 05/16] test(orb-connd): profile storage and importing --- orb-connd/src/key_material/static_key.rs | 1 + orb-connd/src/lib.rs | 2 +- orb-connd/src/profile_store/mod.rs | 12 ++- orb-connd/src/service/mod.rs | 25 ++++- orb-connd/tests/connd_service.rs | 26 ++--- orb-connd/tests/fixture.rs | 30 ++++-- orb-connd/tests/profile_store.rs | 124 +++++++++++++++++++++++ 7 files changed, 190 insertions(+), 30 deletions(-) create mode 100644 orb-connd/tests/profile_store.rs diff --git a/orb-connd/src/key_material/static_key.rs b/orb-connd/src/key_material/static_key.rs index 137ce84c..c412df95 100644 --- a/orb-connd/src/key_material/static_key.rs +++ b/orb-connd/src/key_material/static_key.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use color_eyre::Result; use secrecy::SecretVec; +#[derive(Clone)] pub struct StaticKey(pub Vec); #[async_trait] diff --git a/orb-connd/src/lib.rs b/orb-connd/src/lib.rs index e179caa6..a24edf9a 100644 --- a/orb-connd/src/lib.rs +++ b/orb-connd/src/lib.rs @@ -19,12 +19,12 @@ use tracing::info; pub mod key_material; pub mod modem_manager; pub mod network_manager; +pub mod profile_store; pub mod service; pub mod statsd; pub mod telemetry; pub mod wpa_ctrl; -mod profile_store; mod utils; #[bon::builder(finish_fn = run)] diff --git a/orb-connd/src/profile_store/mod.rs b/orb-connd/src/profile_store/mod.rs index ee93d935..6b7e7bb3 100644 --- a/orb-connd/src/profile_store/mod.rs +++ b/orb-connd/src/profile_store/mod.rs @@ -1,13 +1,13 @@ use crate::{key_material::KeyMaterial, network_manager::WifiProfile}; use chacha20poly1305::{aead::Aead, AeadCore, Key, KeyInit, XChaCha20Poly1305, XNonce}; use color_eyre::{ - eyre::{bail, ensure, eyre, Context}, + eyre::{bail, ensure, eyre}, Result, }; use dashmap::DashMap; use rand::rngs::OsRng; use secrecy::{ExposeSecret, SecretVec}; -use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use std::{collections::HashMap, io::ErrorKind, path::PathBuf, sync::Arc}; use tokio::fs; pub struct ProfileStore { @@ -37,9 +37,11 @@ impl ProfileStore { }; let path = self.store_path.join(Self::FILENAME); - let mut bytes = fs::read(&path) - .await - .wrap_err_with(|| format!("failed to read profile store at {path:?}"))?; + let mut bytes = match fs::read(&path).await { + Err(e) if e.kind() == ErrorKind::NotFound => return Ok(()), + Err(e) => bail!("failed to read profile store at {path:?}, err: {e}"), + Ok(bytes) => bytes, + }; ensure!( bytes.len() >= 24, diff --git a/orb-connd/src/service/mod.rs b/orb-connd/src/service/mod.rs index dcccef02..e0b4f174 100644 --- a/orb-connd/src/service/mod.rs +++ b/orb-connd/src/service/mod.rs @@ -83,13 +83,13 @@ impl ConndService { }; // this must be called before setting up default profiles! - if let Err(e) = connd.import_profiles().await { + if let Err(e) = connd.import_stored_profiles().await { error!("connd failed to import saved profiles: {e}"); } connd.setup_default_profiles().await?; - if let Err(e) = connd.import_wpa_conf(&usr_persistent).await { + if let Err(e) = connd.import_legacy_wpa_conf(&usr_persistent).await { warn!("failed to import legacy wpa config {e}"); } @@ -181,7 +181,7 @@ impl ConndService { Ok(()) } - pub async fn import_profiles(&self) -> Result<()> { + pub async fn import_stored_profiles(&self) -> Result<()> { // this first part of the function is importing old unencrypted NM profiles // stored on disk and deleting them. other nm calls to store profiles will only store // them in memory going forward @@ -192,12 +192,27 @@ impl ConndService { } self.profile_store.import().await?; - self.profile_store.commit().await?; + + let mut profiles = self.profile_store.values(); + profiles.sort_by_key(|p| p.priority); + + for profile in profiles { + self.add_wifi_profile( + profile.ssid.clone(), + profile.sec.to_string(), + profile.psk.clone(), + profile.hidden, + ) + .await?; + } Ok(()) } - pub async fn import_wpa_conf(&self, wpa_conf_dir: impl AsRef) -> Result<()> { + pub async fn import_legacy_wpa_conf( + &self, + wpa_conf_dir: impl AsRef, + ) -> Result<()> { let wpa_conf_path = wpa_conf_dir.as_ref().join("wpa_supplicant-wlan0.conf"); match File::open(&wpa_conf_path).await { Ok(file) => { diff --git a/orb-connd/tests/connd_service.rs b/orb-connd/tests/connd_service.rs index 887ceecd..25f63be8 100644 --- a/orb-connd/tests/connd_service.rs +++ b/orb-connd/tests/connd_service.rs @@ -4,7 +4,7 @@ use orb_connd::{network_manager::WifiSec, OrbCapabilities}; use orb_connd_dbus::{ConnectionState, WifiProfile}; use orb_info::orb_os_release::{OrbOsPlatform, OrbRelease}; use prelude::future::Callback; -use std::{path::PathBuf, time::Duration}; +use std::time::Duration; use tokio::{fs, time}; use tokio_stream::wrappers::ReadDirStream; @@ -355,8 +355,8 @@ async fn it_wipes_dhcp_leases_and_seen_bssids_if_too_big() { // Arrange let fx = Fixture::platform(OrbOsPlatform::Pearl) .release(OrbRelease::Prod) - .arrange(Callback::new(async |usr_persistent: PathBuf| { - let varlib = usr_persistent.join("network-manager").join("varlib"); + .arrange(Callback::new(async |ctx: fixture::Ctx| { + let varlib = ctx.usr_persistent.join("network-manager").join("varlib"); fs::create_dir_all(&varlib).await.unwrap(); // we create a file thats 2mb in size, which puts us @@ -550,7 +550,7 @@ async fn it_imports_wpa_conf_with_hex_encoded_ssid() { // Arrange let fx = Fixture::platform(OrbOsPlatform::Pearl) .release(OrbRelease::Dev) - .arrange(Callback::new(async |usr_persistent: PathBuf| { + .arrange(Callback::new(async |ctx: fixture::Ctx| { // Create wpa_supplicant config with hex-encoded SSID // SSID "546573744e6574776f726b" is hex for "TestNetwork" let wpa_conf_content = r#"ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev @@ -562,7 +562,7 @@ network={ } "#; fs::write( - usr_persistent.join("wpa_supplicant-wlan0.conf"), + ctx.usr_persistent.join("wpa_supplicant-wlan0.conf"), wpa_conf_content, ) .await @@ -596,7 +596,7 @@ async fn it_imports_wpa_conf_with_quoted_ssid() { // Arrange let fx = Fixture::platform(OrbOsPlatform::Pearl) .release(OrbRelease::Dev) - .arrange(Callback::new(async |usr_persistent: PathBuf| { + .arrange(Callback::new(async |ctx: fixture::Ctx| { // Create wpa_supplicant config with quoted SSID let wpa_conf_content = r#"ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev @@ -607,7 +607,7 @@ network={ } "#; fs::write( - usr_persistent.join("wpa_supplicant-wlan0.conf"), + ctx.usr_persistent.join("wpa_supplicant-wlan0.conf"), wpa_conf_content, ) .await @@ -642,7 +642,7 @@ async fn it_handles_invalid_wpa_conf_gracefully() { { let fx = Fixture::platform(OrbOsPlatform::Pearl) .release(OrbRelease::Dev) - .arrange(Callback::new(async |usr_persistent: PathBuf| { + .arrange(Callback::new(async |ctx: fixture::Ctx| { let wpa_conf_content = r#"ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev network={ key_mgmt=WPA-PSK @@ -651,7 +651,7 @@ network={ } "#; fs::write( - usr_persistent.join("wpa_supplicant-wlan0.conf"), + ctx.usr_persistent.join("wpa_supplicant-wlan0.conf"), wpa_conf_content, ) .await @@ -671,7 +671,7 @@ network={ { let fx = Fixture::platform(OrbOsPlatform::Pearl) .release(OrbRelease::Dev) - .arrange(Callback::new(async |usr_persistent: PathBuf| { + .arrange(Callback::new(async |ctx: fixture::Ctx| { let wpa_conf_content = r#"ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev network={ key_mgmt=WPA-PSK @@ -680,7 +680,7 @@ network={ } "#; fs::write( - usr_persistent.join("wpa_supplicant-wlan0.conf"), + ctx.usr_persistent.join("wpa_supplicant-wlan0.conf"), wpa_conf_content, ) .await @@ -698,7 +698,7 @@ network={ { let fx = Fixture::platform(OrbOsPlatform::Pearl) .release(OrbRelease::Dev) - .arrange(Callback::new(async |usr_persistent: PathBuf| { + .arrange(Callback::new(async |ctx: fixture::Ctx| { let long_ssid = "a".repeat(33); let wpa_conf_content = format!( r#"ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev @@ -710,7 +710,7 @@ network={{ "# ); fs::write( - usr_persistent.join("wpa_supplicant-wlan0.conf"), + ctx.usr_persistent.join("wpa_supplicant-wlan0.conf"), wpa_conf_content, ) .await diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index 075a2a8d..e6a2e54b 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -31,6 +31,7 @@ pub struct Fixture { program_handles: Vec>>, pub sysfs: PathBuf, pub usr_persistent: PathBuf, + pub static_key: StaticKey, } impl Drop for Fixture { @@ -41,6 +42,12 @@ impl Drop for Fixture { } } +pub struct Ctx { + pub usr_persistent: PathBuf, + pub nm: NetworkManager, + pub static_key: StaticKey, +} + #[bon] impl Fixture { #[builder(start_fn = platform, finish_fn = run)] @@ -51,7 +58,7 @@ impl Fixture { modem_manager: Option, statsd: Option, wpa_ctrl: Option, - arrange: Option>, + arrange: Option>, #[builder(default = false)] log: bool, ) -> Self { if log { @@ -61,8 +68,10 @@ impl Fixture { let container = setup_container().await; let sysfs = container.tempdir.path().join("sysfs"); let usr_persistent = container.tempdir.path().join("usr_persistent"); + let network_manager_folder = usr_persistent.join("network-manager"); fs::create_dir_all(&sysfs).await.unwrap(); fs::create_dir_all(&usr_persistent).await.unwrap(); + fs::create_dir_all(&network_manager_folder).await.unwrap(); if cap == OrbCapabilities::CellularAndWifi { let stats = sysfs @@ -81,10 +90,6 @@ impl Fixture { time::sleep(Duration::from_secs(1)).await; - if let Some(arrange_cb) = arrange { - arrange_cb.call(usr_persistent.clone()).await; - } - let dbus_socket = container.tempdir.path().join("socket"); let dbus_socket = format!("unix:path={}", dbus_socket.display()); let addr: Address = dbus_socket.parse().unwrap(); @@ -100,6 +105,18 @@ impl Fixture { wpa_ctrl.unwrap_or_else(default_mock_wpa_cli), ); + let static_key = StaticKey([0x42; 32].into()); + + if let Some(arrange_cb) = arrange { + let ctx = Ctx { + usr_persistent: usr_persistent.clone(), + nm: nm.clone(), + static_key: static_key.clone(), + }; + + arrange_cb.call(ctx).await; + } + let program_handles = program() .os_release(OrbOsRelease { release_type: release, @@ -114,7 +131,7 @@ impl Fixture { .usr_persistent(usr_persistent.clone()) .session_bus(conn.clone()) .connect_timeout(Duration::from_secs(1)) - .key_material(StaticKey([0x42; 32].into())) + .key_material(static_key.clone()) .run() .await .unwrap(); @@ -134,6 +151,7 @@ impl Fixture { container, sysfs, usr_persistent, + static_key, } } diff --git a/orb-connd/tests/profile_store.rs b/orb-connd/tests/profile_store.rs new file mode 100644 index 00000000..55a56a12 --- /dev/null +++ b/orb-connd/tests/profile_store.rs @@ -0,0 +1,124 @@ +use fixture::Fixture; +use orb_connd::{ + network_manager::{WifiProfile, WifiSec}, + profile_store::ProfileStore, + OrbCapabilities, +}; +use orb_info::orb_os_release::{OrbOsPlatform, OrbRelease}; +use prelude::future::Callback; +use uuid::Uuid; + +mod fixture; + +#[cfg_attr(target_os = "macos", test_with::no_env(GITHUB_ACTIONS))] +#[tokio::test] +async fn it_imports_persisted_nm_profiles_and_deletes_them_on_startup() { + // Arrange + let fx = Fixture::platform(OrbOsPlatform::Pearl) + .cap(OrbCapabilities::WifiOnly) + .release(OrbRelease::Prod) + .arrange(Callback::new(async |ctx: fixture::Ctx| { + // prepopulate with persisted nm profiles + ctx.nm + .wifi_profile("imported") + .ssid("imported") + .sec(WifiSec::Wpa2Psk) + .psk("1234567890") + .persist(true) + .add() + .await + .unwrap(); + })) + .run() + .await; + + let connd = fx.connd().await; + + // Assert profile is still in nm + let profiles = connd.list_wifi_profiles().await.unwrap(); + let imported_profile = profiles.iter().find(|p| p.ssid == "imported"); + + assert!(imported_profile.is_some(), "{profiles:?}"); + + // Assert profile is in store + let store_path = fx.usr_persistent.join("network-manager"); + let profile_store = ProfileStore::from_store(store_path, &fx.static_key).await; + profile_store.import().await.unwrap(); + + let ssids: Vec<_> = profile_store.values().into_iter().map(|p| p.ssid).collect(); + assert_eq!(vec!["imported"], ssids); + + // Assert profile is no longer on disk + let out = fx + .container + .exec(&["ls", "/etc/NetworkManager/system-connections/"]) + .await + .stdout; + + let out = String::from_utf8_lossy(&out); + assert!(out.trim().is_empty(), "{out}"); +} + +#[cfg_attr(target_os = "macos", test_with::no_env(GITHUB_ACTIONS))] +#[tokio::test] +async fn it_adds_removes_and_imports_encrypted_profiles_on_startup() { + // Arrage + let fx = Fixture::platform(OrbOsPlatform::Pearl) + .cap(OrbCapabilities::WifiOnly) + .release(OrbRelease::Prod) + .arrange(Callback::new(async |ctx: fixture::Ctx| { + // prepopulate with encrypted profiles + let store_path = ctx.usr_persistent.join("network-manager"); + + let profile_store = + ProfileStore::from_store(store_path, &ctx.static_key).await; + + profile_store.insert(WifiProfile { + id: "imported".into(), + uuid: Uuid::new_v4().to_string(), + ssid: "imported".into(), + sec: WifiSec::Wpa2Psk, + psk: "1234567890".into(), + autoconnect: false, + priority: 0, + hidden: false, + path: String::new(), + }); + + profile_store.commit().await.unwrap(); + })) + .run() + .await; + + let connd = fx.connd().await; + + // Assert profile was imported + let imported_profile = connd + .list_wifi_profiles() + .await + .unwrap() + .into_iter() + .find(|p| p.ssid == "imported"); + + assert!(imported_profile.is_some()); + + // Act: remove imported, add new profile + connd.remove_wifi_profile("imported".into()).await.unwrap(); + connd + .add_wifi_profile( + "new_profile".into(), + "Wpa2Psk".into(), + "1234567890".into(), + false, + ) + .await + .unwrap(); + + // Assert: store reflects changes + let store_path = fx.usr_persistent.join("network-manager"); + let profile_store = ProfileStore::from_store(store_path, &fx.static_key).await; + profile_store.import().await.unwrap(); + + let ssids: Vec<_> = profile_store.values().into_iter().map(|p| p.ssid).collect(); + assert_eq!(vec!["new_profile"], ssids); +} From 15f1748852a2c279ee34bfb35f7777b4bd034ab2 Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 16 Dec 2025 16:50:22 +0100 Subject: [PATCH 06/16] feat(connd): subcommand for connd or securestoraged --- orb-connd/debian/worldcoin-connd.service | 2 +- orb-connd/src/connectivity_daemon.rs | 2 -- orb-connd/src/main.rs | 16 ++++++++++------ orb-connd/src/secure_storage/subprocess.rs | 1 - 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/orb-connd/debian/worldcoin-connd.service b/orb-connd/debian/worldcoin-connd.service index 52d55402..58113498 100644 --- a/orb-connd/debian/worldcoin-connd.service +++ b/orb-connd/debian/worldcoin-connd.service @@ -12,7 +12,7 @@ WorkingDirectory=/home/worldcoin Type=exec Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/tmp/worldcoin_bus_socket Environment=RUST_BACKTRACE=1 -ExecStart=/usr/local/bin/orb-connd +ExecStart=/usr/local/bin/orb-connd connd SyslogIdentifier=worldcoin-connd Restart=always RestartSec=3 diff --git a/orb-connd/src/connectivity_daemon.rs b/orb-connd/src/connectivity_daemon.rs index e4930608..78dbbf6d 100644 --- a/orb-connd/src/connectivity_daemon.rs +++ b/orb-connd/src/connectivity_daemon.rs @@ -1,7 +1,6 @@ use crate::modem_manager::ModemManager; use crate::network_manager::NetworkManager; use crate::profile_store::ProfileStore; -use crate::secure_storage::SecureStorage; use crate::service::ConndService; use crate::statsd::StatsdClient; use crate::{telemetry, OrbCapabilities, Tasks}; @@ -10,7 +9,6 @@ use orb_info::orb_os_release::OrbOsRelease; use std::time::Duration; use std::{path::Path, sync::Arc}; use tokio::{task, time}; -use tokio_util::sync::CancellationToken; use tracing::error; use tracing::{info, warn}; diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index 8eb7f7a0..3e8e7f06 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -6,7 +6,7 @@ use orb_connd::{ wpa_ctrl::cli::WpaCli, }; use orb_info::orb_os_release::OrbOsRelease; -use orb_secure_storage_ca::{in_memory::InMemoryBackend, optee::OpteeBackend}; +use orb_secure_storage_ca::in_memory::InMemoryBackend; use std::time::Duration; use tokio::{ io, @@ -27,11 +27,13 @@ pub enum Command { #[command(name = "connd")] ConnectivityDaemon, #[command(name = "ssd")] - SecureStorageDaemon { in_memory: bool }, + SecureStorageDaemon { + #[arg(long)] + in_memory: Option, + }, } -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { color_eyre::install()?; let tel_flusher = orb_telemetry::TelemetryConfig::new() .with_journald(SYSLOG_IDENTIFIER) @@ -42,10 +44,12 @@ async fn main() -> Result<()> { use Command::*; let result = match args.command { ConnectivityDaemon => connectivity_daemon(), - SecureStorageDaemon { in_memory } => secure_storage_daemon(in_memory), + SecureStorageDaemon { in_memory } => { + secure_storage_daemon(in_memory.unwrap_or_default()) + } }; - tel_flusher.flush().await; + tel_flusher.flush_blocking(); result } diff --git a/orb-connd/src/secure_storage/subprocess.rs b/orb-connd/src/secure_storage/subprocess.rs index 3b107dc4..dd75e100 100644 --- a/orb-connd/src/secure_storage/subprocess.rs +++ b/orb-connd/src/secure_storage/subprocess.rs @@ -69,7 +69,6 @@ fn make_framed_subprocess( current_euid.as_raw() }; - let current_exe = std::env::current_exe().expect("infallible"); let mut child = tokio::process::Command::new(exe_path.as_ref()) .arg("ssd") .arg(format!("--in-memory={in_memory}")) From 8a7810976be9d7cf5335678fb0091b159fb82905 Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 16 Dec 2025 17:12:59 +0100 Subject: [PATCH 07/16] tests are running --- Cargo.lock | 1 + Cargo.toml | 1 + orb-connd/Cargo.toml | 1 + orb-connd/src/connectivity_daemon.rs | 6 ++--- orb-connd/src/main.rs | 16 +++++++++---- orb-connd/src/profile_store/mod.rs | 23 +++++------------- orb-connd/tests/fixture.rs | 36 +++++++++++++++++++--------- orb-connd/tests/profile_store.rs | 11 +++------ 8 files changed, 52 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b03c68d2..23b10d95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8124,6 +8124,7 @@ dependencies = [ "dashmap", "derive_more 2.1.0", "dogstatsd", + "escargot", "flume", "futures", "hex 0.4.3", diff --git a/Cargo.toml b/Cargo.toml index a0bfc70f..5d9120c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,6 +111,7 @@ derive_more = { version = "2.1.0", default-features = false, features = [ "from", ] } ed25519-dalek = { version = "2.1.1", default-features = false, features = ["std"] } +escargot = "0.5.15" eyre = "0.6.12" flume = "0.11.1" ftdi-embedded-hal = { version = "0.22.0", features = ["libftd2xx", "libftd2xx-static"] } diff --git a/orb-connd/Cargo.toml b/orb-connd/Cargo.toml index c3d1a523..74748459 100644 --- a/orb-connd/Cargo.toml +++ b/orb-connd/Cargo.toml @@ -57,6 +57,7 @@ zbus.workspace = true [dev-dependencies] async-tempfile.workspace = true +escargot.workspace = true test-utils.workspace = true nix = { workspace = true, features = ["socket"] } test-with.workspace = true diff --git a/orb-connd/src/connectivity_daemon.rs b/orb-connd/src/connectivity_daemon.rs index 78dbbf6d..630ff804 100644 --- a/orb-connd/src/connectivity_daemon.rs +++ b/orb-connd/src/connectivity_daemon.rs @@ -1,6 +1,7 @@ use crate::modem_manager::ModemManager; use crate::network_manager::NetworkManager; use crate::profile_store::ProfileStore; +use crate::secure_storage::SecureStorage; use crate::service::ConndService; use crate::statsd::StatsdClient; use crate::{telemetry, OrbCapabilities, Tasks}; @@ -22,8 +23,7 @@ pub async fn program( statsd_client: impl StatsdClient, modem_manager: impl ModemManager, connect_timeout: Duration, - connd_exe_path: impl AsRef + 'static, - in_memory_secure_storage: bool, + secure_storage: SecureStorage, ) -> Result { let sysfs = sysfs.as_ref().to_path_buf(); let modem_manager: Arc = Arc::new(modem_manager); @@ -35,7 +35,7 @@ pub async fn program( os_release.orb_os_platform_type, os_release.release_type, cap ); - let profile_store = ProfileStore::new(connd_exe_path, in_memory_secure_storage); + let profile_store = ProfileStore::new(secure_storage); let connd = ConndService::new( session_bus.clone(), diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index 3e8e7f06..3dc1f0cd 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -1,8 +1,11 @@ use clap::{Parser, Subcommand}; use color_eyre::eyre::Result; use orb_connd::{ - connectivity_daemon, modem_manager::cli::ModemManagerCli, - network_manager::NetworkManager, secure_storage, statsd::dd::DogstatsdClient, + connectivity_daemon, + modem_manager::cli::ModemManagerCli, + network_manager::NetworkManager, + secure_storage::{self, SecureStorage}, + statsd::dd::DogstatsdClient, wpa_ctrl::cli::WpaCli, }; use orb_info::orb_os_release::OrbOsRelease; @@ -12,6 +15,7 @@ use tokio::{ io, signal::unix::{self, SignalKind}, }; +use tokio_util::sync::CancellationToken; use tracing::{info, warn}; const SYSLOG_IDENTIFIER: &str = "worldcoin-connd"; @@ -66,6 +70,10 @@ fn connectivity_daemon() -> Result<()> { WpaCli::new(os_release.orb_os_platform_type), ); + let cancel_token = CancellationToken::new(); + let secure_storage = + SecureStorage::new(std::env::current_exe()?, false, cancel_token.clone()); + let tasks = connectivity_daemon::program() .sysfs("/sys") .usr_persistent("/usr/persistent") @@ -75,8 +83,7 @@ fn connectivity_daemon() -> Result<()> { .statsd_client(DogstatsdClient::new()) .modem_manager(ModemManagerCli) .connect_timeout(Duration::from_secs(15)) - .in_memory_secure_storage(false) - .connd_exe_path(std::env::current_exe()?) + .secure_storage(secure_storage) .run() .await?; @@ -90,6 +97,7 @@ fn connectivity_daemon() -> Result<()> { info!("aborting tasks and exiting gracefully"); + cancel_token.cancel(); for handle in tasks { handle.abort(); } diff --git a/orb-connd/src/profile_store/mod.rs b/orb-connd/src/profile_store/mod.rs index 4a50eced..91212fbc 100644 --- a/orb-connd/src/profile_store/mod.rs +++ b/orb-connd/src/profile_store/mod.rs @@ -1,4 +1,7 @@ -use crate::{network_manager::WifiProfile, secure_storage::SecureStorage}; +use crate::{ + network_manager::WifiProfile, + secure_storage::{self, SecureStorage}, +}; use color_eyre::Result; use dashmap::DashMap; use std::{collections::HashMap, path::Path, sync::Arc}; @@ -6,30 +9,16 @@ use tokio_util::sync::CancellationToken; pub struct ProfileStore { secure_storage: SecureStorage, - cancel_token: CancellationToken, profiles: Arc>, } -impl Drop for ProfileStore { - fn drop(&mut self) { - self.cancel_token.cancel(); - } -} - impl ProfileStore { const KEY: &str = "nmprofiles"; - pub fn new(connd_exe_path: impl AsRef + 'static, in_memory: bool) -> Self { - let cancel_token = CancellationToken::new(); - + pub fn new(secure_storage: SecureStorage) -> Self { Self { - secure_storage: SecureStorage::new( - connd_exe_path, - in_memory, - cancel_token.clone(), - ), + secure_storage, profiles: Arc::new(DashMap::new()), - cancel_token, } } diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index 3ba80fa4..a99e1914 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -1,19 +1,17 @@ use async_trait::async_trait; use bon::bon; use color_eyre::Result; +use escargot::CargoBuild; use mockall::mock; use nix::libc; use orb_connd::{ -<<<<<<< HEAD - key_material::static_key::StaticKey, -======= - main_daemon::program, ->>>>>>> 3b4fb45ec44293bc19021416e72ab60e169b441b + connectivity_daemon::program, modem_manager::{ connection_state::ConnectionState, Location, Modem, ModemId, ModemInfo, ModemManager, Signal, SimId, SimInfo, }, network_manager::NetworkManager, + secure_storage::{self, SecureStorage}, statsd::StatsdClient, wpa_ctrl::WpaCtrl, OrbCapabilities, @@ -21,6 +19,7 @@ use orb_connd::{ use orb_connd_dbus::ConndProxy; use orb_info::orb_os_release::{OrbOsPlatform, OrbOsRelease, OrbRelease}; use prelude::future::Callback; +use tokio_util::sync::CancellationToken; use std::{env, path::PathBuf, time::Duration}; use test_utils::docker::{self, Container}; use tokio::{fs, task::JoinHandle, time}; @@ -34,11 +33,14 @@ pub struct Fixture { program_handles: Vec>>, pub sysfs: PathBuf, pub usr_persistent: PathBuf, - pub static_key: StaticKey, + pub secure_storage: SecureStorage, + pub secure_storage_cancel_token: CancellationToken } impl Drop for Fixture { fn drop(&mut self) { + self.secure_storage_cancel_token.cancel(); + for handle in &self.program_handles { handle.abort(); } @@ -48,7 +50,7 @@ impl Drop for Fixture { pub struct Ctx { pub usr_persistent: PathBuf, pub nm: NetworkManager, - pub static_key: StaticKey, + pub secure_storage: SecureStorage, } #[bon] @@ -108,13 +110,24 @@ impl Fixture { wpa_ctrl.unwrap_or_else(default_mock_wpa_cli), ); - let static_key = StaticKey([0x42; 32].into()); + let run = CargoBuild::new() + .bin("orb-connd") + .current_target() + .current_release() + .manifest_path(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml")) + .run() + .unwrap(); + + let connd_exe_path = run.path().to_path_buf(); + + let cancel_token = CancellationToken::new(); + let secure_storage = SecureStorage::new(connd_exe_path, true, cancel_token.clone()); if let Some(arrange_cb) = arrange { let ctx = Ctx { usr_persistent: usr_persistent.clone(), nm: nm.clone(), - static_key: static_key.clone(), + secure_storage: secure_storage.clone(), }; arrange_cb.call(ctx).await; @@ -134,7 +147,7 @@ impl Fixture { .usr_persistent(usr_persistent.clone()) .session_bus(conn.clone()) .connect_timeout(Duration::from_secs(1)) - .key_material(static_key.clone()) + .secure_storage(secure_storage.clone()) .run() .await .unwrap(); @@ -154,7 +167,8 @@ impl Fixture { container, sysfs, usr_persistent, - static_key, + secure_storage, + secure_storage_cancel_token: cancel_token } } diff --git a/orb-connd/tests/profile_store.rs b/orb-connd/tests/profile_store.rs index 55a56a12..9eca4ab4 100644 --- a/orb-connd/tests/profile_store.rs +++ b/orb-connd/tests/profile_store.rs @@ -41,8 +41,7 @@ async fn it_imports_persisted_nm_profiles_and_deletes_them_on_startup() { assert!(imported_profile.is_some(), "{profiles:?}"); // Assert profile is in store - let store_path = fx.usr_persistent.join("network-manager"); - let profile_store = ProfileStore::from_store(store_path, &fx.static_key).await; + let profile_store = ProfileStore::new(fx.secure_storage.clone()); profile_store.import().await.unwrap(); let ssids: Vec<_> = profile_store.values().into_iter().map(|p| p.ssid).collect(); @@ -68,10 +67,7 @@ async fn it_adds_removes_and_imports_encrypted_profiles_on_startup() { .release(OrbRelease::Prod) .arrange(Callback::new(async |ctx: fixture::Ctx| { // prepopulate with encrypted profiles - let store_path = ctx.usr_persistent.join("network-manager"); - - let profile_store = - ProfileStore::from_store(store_path, &ctx.static_key).await; + let profile_store = ProfileStore::new(ctx.secure_storage); profile_store.insert(WifiProfile { id: "imported".into(), @@ -115,8 +111,7 @@ async fn it_adds_removes_and_imports_encrypted_profiles_on_startup() { .unwrap(); // Assert: store reflects changes - let store_path = fx.usr_persistent.join("network-manager"); - let profile_store = ProfileStore::from_store(store_path, &fx.static_key).await; + let profile_store = ProfileStore::new(fx.secure_storage.clone()); profile_store.import().await.unwrap(); let ssids: Vec<_> = profile_store.values().into_iter().map(|p| p.ssid).collect(); From 96783115981f016d2c7692776a00bbaf9b5ed590 Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 16 Dec 2025 17:41:53 +0100 Subject: [PATCH 08/16] tests go brr --- orb-connd/Cargo.toml | 2 +- orb-connd/src/profile_store/mod.rs | 16 ++++++++-------- orb-connd/src/service/mod.rs | 2 +- orb-connd/tests/fixture.rs | 12 +++++++----- orb-connd/tests/profile_store.rs | 5 +++++ 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/orb-connd/Cargo.toml b/orb-connd/Cargo.toml index 74748459..0f7ff661 100644 --- a/orb-connd/Cargo.toml +++ b/orb-connd/Cargo.toml @@ -35,7 +35,7 @@ orb-connd-dbus.workspace = true orb-info = { workspace = true, features = ["orb-os-release", "async"] } orb-secure-storage-ca = { workspace = true, default-features = false, features = [ "backend-in-memory", - "backend-optee" + "backend-optee", ] } orb-telemetry.workspace = true p256.workspace = true diff --git a/orb-connd/src/profile_store/mod.rs b/orb-connd/src/profile_store/mod.rs index 91212fbc..2fb69aca 100644 --- a/orb-connd/src/profile_store/mod.rs +++ b/orb-connd/src/profile_store/mod.rs @@ -1,11 +1,7 @@ -use crate::{ - network_manager::WifiProfile, - secure_storage::{self, SecureStorage}, -}; +use crate::{network_manager::WifiProfile, secure_storage::SecureStorage}; use color_eyre::Result; use dashmap::DashMap; -use std::{collections::HashMap, path::Path, sync::Arc}; -use tokio_util::sync::CancellationToken; +use std::{collections::HashMap, sync::Arc}; pub struct ProfileStore { secure_storage: SecureStorage, @@ -24,8 +20,12 @@ impl ProfileStore { pub async fn import(&self) -> Result<()> { let bytes = self.secure_storage.get(Self::KEY.into()).await?; - let profiles: HashMap = - ciborium::de::from_reader(bytes.as_slice())?; + + let profiles: HashMap = if bytes.is_empty() { + HashMap::default() + } else { + ciborium::de::from_reader(bytes.as_slice())? + }; for (key, value) in profiles { self.profiles.insert(key, value); diff --git a/orb-connd/src/service/mod.rs b/orb-connd/src/service/mod.rs index 40055ae0..c805173a 100644 --- a/orb-connd/src/service/mod.rs +++ b/orb-connd/src/service/mod.rs @@ -61,7 +61,7 @@ impl ConndService { cap: OrbCapabilities, connect_timeout: Duration, usr_persistent: impl AsRef, - profile_store: ProfileStore + profile_store: ProfileStore, ) -> Result { let usr_persistent = usr_persistent.as_ref(); diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index a99e1914..da5c9455 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -11,7 +11,7 @@ use orb_connd::{ ModemManager, Signal, SimId, SimInfo, }, network_manager::NetworkManager, - secure_storage::{self, SecureStorage}, + secure_storage::SecureStorage, statsd::StatsdClient, wpa_ctrl::WpaCtrl, OrbCapabilities, @@ -19,10 +19,10 @@ use orb_connd::{ use orb_connd_dbus::ConndProxy; use orb_info::orb_os_release::{OrbOsPlatform, OrbOsRelease, OrbRelease}; use prelude::future::Callback; -use tokio_util::sync::CancellationToken; use std::{env, path::PathBuf, time::Duration}; use test_utils::docker::{self, Container}; use tokio::{fs, task::JoinHandle, time}; +use tokio_util::sync::CancellationToken; use zbus::Address; #[allow(dead_code)] @@ -34,7 +34,7 @@ pub struct Fixture { pub sysfs: PathBuf, pub usr_persistent: PathBuf, pub secure_storage: SecureStorage, - pub secure_storage_cancel_token: CancellationToken + pub secure_storage_cancel_token: CancellationToken, } impl Drop for Fixture { @@ -47,6 +47,7 @@ impl Drop for Fixture { } } +#[allow(dead_code)] pub struct Ctx { pub usr_persistent: PathBuf, pub nm: NetworkManager, @@ -121,7 +122,8 @@ impl Fixture { let connd_exe_path = run.path().to_path_buf(); let cancel_token = CancellationToken::new(); - let secure_storage = SecureStorage::new(connd_exe_path, true, cancel_token.clone()); + let secure_storage = + SecureStorage::new(connd_exe_path, true, cancel_token.clone()); if let Some(arrange_cb) = arrange { let ctx = Ctx { @@ -168,7 +170,7 @@ impl Fixture { sysfs, usr_persistent, secure_storage, - secure_storage_cancel_token: cancel_token + secure_storage_cancel_token: cancel_token, } } diff --git a/orb-connd/tests/profile_store.rs b/orb-connd/tests/profile_store.rs index 9eca4ab4..c381b684 100644 --- a/orb-connd/tests/profile_store.rs +++ b/orb-connd/tests/profile_store.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use fixture::Fixture; use orb_connd::{ network_manager::{WifiProfile, WifiSec}, @@ -6,6 +8,7 @@ use orb_connd::{ }; use orb_info::orb_os_release::{OrbOsPlatform, OrbRelease}; use prelude::future::Callback; +use tokio::time; use uuid::Uuid; mod fixture; @@ -34,6 +37,8 @@ async fn it_imports_persisted_nm_profiles_and_deletes_them_on_startup() { let connd = fx.connd().await; + time::sleep(Duration::from_secs(5)).await; + // Assert profile is still in nm let profiles = connd.list_wifi_profiles().await.unwrap(); let imported_profile = profiles.iter().find(|p| p.ssid == "imported"); From f7c268e7fe8fd17b6665ba566d75034d4580ca5b Mon Sep 17 00:00:00 2001 From: vmenge Date: Tue, 16 Dec 2025 17:44:50 +0100 Subject: [PATCH 09/16] remove useless sleep --- orb-connd/tests/profile_store.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/orb-connd/tests/profile_store.rs b/orb-connd/tests/profile_store.rs index c381b684..9eca4ab4 100644 --- a/orb-connd/tests/profile_store.rs +++ b/orb-connd/tests/profile_store.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use fixture::Fixture; use orb_connd::{ network_manager::{WifiProfile, WifiSec}, @@ -8,7 +6,6 @@ use orb_connd::{ }; use orb_info::orb_os_release::{OrbOsPlatform, OrbRelease}; use prelude::future::Callback; -use tokio::time; use uuid::Uuid; mod fixture; @@ -37,8 +34,6 @@ async fn it_imports_persisted_nm_profiles_and_deletes_them_on_startup() { let connd = fx.connd().await; - time::sleep(Duration::from_secs(5)).await; - // Assert profile is still in nm let profiles = connd.list_wifi_profiles().await.unwrap(); let imported_profile = profiles.iter().find(|p| p.ssid == "imported"); From 35bff69af206d3aea51319a92fe56227d9a80d55 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Tue, 16 Dec 2025 13:29:59 -0800 Subject: [PATCH 10/16] implement todo --- orb-connd/src/main.rs | 17 +++++++++++------ orb-connd/src/secure_storage/subprocess.rs | 1 - 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index 3dc1f0cd..98aa8bc7 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use color_eyre::eyre::Result; +use color_eyre::eyre::{Context, Result}; use orb_connd::{ connectivity_daemon, modem_manager::cli::ModemManagerCli, @@ -9,7 +9,7 @@ use orb_connd::{ wpa_ctrl::cli::WpaCli, }; use orb_info::orb_os_release::OrbOsRelease; -use orb_secure_storage_ca::in_memory::InMemoryBackend; +use orb_secure_storage_ca::{in_memory::InMemoryBackend, optee::OpteeBackend}; use std::time::Duration; use tokio::{ io, @@ -109,14 +109,19 @@ fn connectivity_daemon() -> Result<()> { fn secure_storage_daemon(in_memory: bool) -> Result<()> { let rt = tokio::runtime::Builder::new_current_thread().build()?; + let io = io::join(io::stdin(), io::stdout()); + if in_memory { let mut ctx = orb_secure_storage_ca::in_memory::InMemoryContext::default(); - rt.block_on(secure_storage::subprocess::entry::( - io::join(io::stdin(), io::stdout()), - &mut ctx, + io, &mut ctx, )) } else { - todo!() + let mut ctx = + orb_secure_storage_ca::reexported_crates::optee_teec::Context::new() + .wrap_err("failed to initialize optee context")?; + rt.block_on(secure_storage::subprocess::entry::( + io, &mut ctx, + )) } } diff --git a/orb-connd/src/secure_storage/subprocess.rs b/orb-connd/src/secure_storage/subprocess.rs index dd75e100..032b93ca 100644 --- a/orb-connd/src/secure_storage/subprocess.rs +++ b/orb-connd/src/secure_storage/subprocess.rs @@ -22,7 +22,6 @@ pub(super) fn spawn( cancel: CancellationToken, ) -> SecureStorage { let mut framed_pipes = make_framed_subprocess(exe_path, in_memory); - // TODO: perhaps this should always be 1 or 0? let (request_tx, mut request_rx) = mpsc::channel::(request_queue_size); let cancel_clone = cancel.clone(); From 37630933927765a8adfe1ff29005e06cc2269ba3 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Tue, 16 Dec 2025 13:31:07 -0800 Subject: [PATCH 11/16] mark connd as unsupported on macos --- orb-connd/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/orb-connd/Cargo.toml b/orb-connd/Cargo.toml index 0f7ff661..1e142902 100644 --- a/orb-connd/Cargo.toml +++ b/orb-connd/Cargo.toml @@ -64,6 +64,9 @@ test-with.workspace = true mockall.workspace = true tokio-stream = { workspace = true, features = ["fs"] } +[package.metadata.orb] +unsupported_targets = ["aarch64-apple-darwin", "x86_64-apple-darwin"] + [package.metadata.deb] maintainer-scripts = "debian/" assets = [ From c58b173de1c3723bbd636b7f4d15b8cce393c3f2 Mon Sep 17 00:00:00 2001 From: vmenge Date: Wed, 17 Dec 2025 15:33:29 +0100 Subject: [PATCH 12/16] address review changes --- orb-connd/debian/worldcoin-connd.service | 2 +- orb-connd/src/main.rs | 17 ++++++++--------- orb-connd/src/profile_store/mod.rs | 13 ++++++++++--- orb-connd/src/secure_storage/mod.rs | 8 ++------ orb-connd/src/secure_storage/subprocess.rs | 10 +++++----- orb-connd/tests/fixture.rs | 8 +++----- orb-connd/tests/profile_store.rs | 2 +- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/orb-connd/debian/worldcoin-connd.service b/orb-connd/debian/worldcoin-connd.service index 58113498..52d55402 100644 --- a/orb-connd/debian/worldcoin-connd.service +++ b/orb-connd/debian/worldcoin-connd.service @@ -12,7 +12,7 @@ WorkingDirectory=/home/worldcoin Type=exec Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/tmp/worldcoin_bus_socket Environment=RUST_BACKTRACE=1 -ExecStart=/usr/local/bin/orb-connd connd +ExecStart=/usr/local/bin/orb-connd SyslogIdentifier=worldcoin-connd Restart=always RestartSec=3 diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index 98aa8bc7..d52460a9 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -23,15 +23,14 @@ const SYSLOG_IDENTIFIER: &str = "worldcoin-connd"; #[derive(Parser, Debug)] pub struct Args { #[command(subcommand)] - pub command: Command, + pub command: Option, } -#[derive(Subcommand, Debug)] +#[derive(Subcommand, Debug, Default)] pub enum Command { - #[command(name = "connd")] + #[default] ConnectivityDaemon, - #[command(name = "ssd")] - SecureStorageDaemon { + SecureStorageWorker { #[arg(long)] in_memory: Option, }, @@ -46,10 +45,10 @@ fn main() -> Result<()> { let args = Args::parse(); use Command::*; - let result = match args.command { + let result = match args.command.unwrap_or_default() { ConnectivityDaemon => connectivity_daemon(), - SecureStorageDaemon { in_memory } => { - secure_storage_daemon(in_memory.unwrap_or_default()) + SecureStorageWorker { in_memory } => { + secure_storage_worker(in_memory.unwrap_or_default()) } }; @@ -106,7 +105,7 @@ fn connectivity_daemon() -> Result<()> { }) } -fn secure_storage_daemon(in_memory: bool) -> Result<()> { +fn secure_storage_worker(in_memory: bool) -> Result<()> { let rt = tokio::runtime::Builder::new_current_thread().build()?; let io = io::join(io::stdin(), io::stdout()); diff --git a/orb-connd/src/profile_store/mod.rs b/orb-connd/src/profile_store/mod.rs index 2fb69aca..f674407e 100644 --- a/orb-connd/src/profile_store/mod.rs +++ b/orb-connd/src/profile_store/mod.rs @@ -1,5 +1,5 @@ use crate::{network_manager::WifiProfile, secure_storage::SecureStorage}; -use color_eyre::Result; +use color_eyre::{eyre::Context, Result}; use dashmap::DashMap; use std::{collections::HashMap, sync::Arc}; @@ -19,7 +19,11 @@ impl ProfileStore { } pub async fn import(&self) -> Result<()> { - let bytes = self.secure_storage.get(Self::KEY.into()).await?; + let bytes = self + .secure_storage + .get(Self::KEY.into()) + .await + .wrap_err("failed trying to import from secure storage")?; let profiles: HashMap = if bytes.is_empty() { HashMap::default() @@ -44,7 +48,10 @@ impl ProfileStore { let mut bytes = Vec::new(); ciborium::ser::into_writer(&profiles, &mut bytes)?; - self.secure_storage.put(Self::KEY.into(), bytes).await?; + self.secure_storage + .put(Self::KEY.into(), bytes) + .await + .wrap_err("failed trying to commit to secure storage")?; Ok(()) } diff --git a/orb-connd/src/secure_storage/mod.rs b/orb-connd/src/secure_storage/mod.rs index 20f8a18d..b86359a9 100644 --- a/orb-connd/src/secure_storage/mod.rs +++ b/orb-connd/src/secure_storage/mod.rs @@ -4,7 +4,7 @@ pub mod subprocess; use self::messages::{Request, Response}; use color_eyre::eyre::{Context, Result}; -use std::{path::Path, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use tokio::sync::{mpsc, oneshot}; use tokio_util::sync::{CancellationToken, DropGuard}; @@ -23,11 +23,7 @@ pub struct SecureStorage { } impl SecureStorage { - pub fn new( - exe_path: impl AsRef + 'static, - in_memory: bool, - cancel: CancellationToken, - ) -> Self { + pub fn new(exe_path: PathBuf, in_memory: bool, cancel: CancellationToken) -> Self { self::subprocess::spawn(exe_path, in_memory, 1, cancel) } diff --git a/orb-connd/src/secure_storage/subprocess.rs b/orb-connd/src/secure_storage/subprocess.rs index 032b93ca..be4554ca 100644 --- a/orb-connd/src/secure_storage/subprocess.rs +++ b/orb-connd/src/secure_storage/subprocess.rs @@ -6,7 +6,7 @@ use color_eyre::eyre::Result; use futures::{Sink, SinkExt as _, Stream, TryStreamExt as _}; use orb_secure_storage_ca::BackendT; use std::io::Result as IoResult; -use std::path::Path; +use std::path::PathBuf; use std::{process::Stdio, sync::Arc}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::sync::mpsc; @@ -16,7 +16,7 @@ use tracing::{info, warn}; type SsClient = Arc>>; pub(super) fn spawn( - exe_path: impl AsRef + 'static, + exe_path: PathBuf, in_memory: bool, request_queue_size: usize, cancel: CancellationToken, @@ -57,7 +57,7 @@ pub(super) fn spawn( } fn make_framed_subprocess( - exe_path: impl AsRef, + exe_path: PathBuf, in_memory: bool, ) -> impl Stream> + Sink { let current_euid = rustix::process::geteuid(); @@ -68,8 +68,8 @@ fn make_framed_subprocess( current_euid.as_raw() }; - let mut child = tokio::process::Command::new(exe_path.as_ref()) - .arg("ssd") + let mut child = tokio::process::Command::new(exe_path) + .arg("secure-storage-worker") .arg(format!("--in-memory={in_memory}")) .uid(child_euid) .stdin(Stdio::piped()) diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index da5c9455..8e4cd2a6 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -111,19 +111,17 @@ impl Fixture { wpa_ctrl.unwrap_or_else(default_mock_wpa_cli), ); - let run = CargoBuild::new() + let built_connd = CargoBuild::new() .bin("orb-connd") .current_target() .current_release() - .manifest_path(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml")) + .manifest_path(env!("CARGO_MANIFEST_PATH")) .run() .unwrap(); - let connd_exe_path = run.path().to_path_buf(); - let cancel_token = CancellationToken::new(); let secure_storage = - SecureStorage::new(connd_exe_path, true, cancel_token.clone()); + SecureStorage::new(built_connd.path().into(), true, cancel_token.clone()); if let Some(arrange_cb) = arrange { let ctx = Ctx { diff --git a/orb-connd/tests/profile_store.rs b/orb-connd/tests/profile_store.rs index 9eca4ab4..21bd3ae9 100644 --- a/orb-connd/tests/profile_store.rs +++ b/orb-connd/tests/profile_store.rs @@ -61,7 +61,7 @@ async fn it_imports_persisted_nm_profiles_and_deletes_them_on_startup() { #[cfg_attr(target_os = "macos", test_with::no_env(GITHUB_ACTIONS))] #[tokio::test] async fn it_adds_removes_and_imports_encrypted_profiles_on_startup() { - // Arrage + // Arrange let fx = Fixture::platform(OrbOsPlatform::Pearl) .cap(OrbCapabilities::WifiOnly) .release(OrbRelease::Prod) From a9a7c92296c5a677020dc8382808e1449fb06bce Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Wed, 17 Dec 2025 10:11:33 -0800 Subject: [PATCH 13/16] Apply suggestion from @TheButlah --- orb-connd/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index d52460a9..ebd78c7f 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -110,6 +110,7 @@ fn secure_storage_worker(in_memory: bool) -> Result<()> { let io = io::join(io::stdin(), io::stdout()); + // in_memory is only used for integration tests if in_memory { let mut ctx = orb_secure_storage_ca::in_memory::InMemoryContext::default(); rt.block_on(secure_storage::subprocess::entry::( From 7a2314af4ffa3a6a975350a461ef62653bc9d26c Mon Sep 17 00:00:00 2001 From: vmenge Date: Mon, 22 Dec 2025 15:17:16 +0100 Subject: [PATCH 14/16] fix(connd): remove duplicated startup logic --- orb-connd/src/connectivity_daemon.rs | 16 +--------------- orb-connd/src/service/mod.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/orb-connd/src/connectivity_daemon.rs b/orb-connd/src/connectivity_daemon.rs index 630ff804..2f0eb508 100644 --- a/orb-connd/src/connectivity_daemon.rs +++ b/orb-connd/src/connectivity_daemon.rs @@ -11,7 +11,7 @@ use std::time::Duration; use std::{path::Path, sync::Arc}; use tokio::{task, time}; use tracing::error; -use tracing::{info, warn}; +use tracing::info; #[bon::builder(finish_fn = run)] pub async fn program( @@ -48,20 +48,6 @@ pub async fn program( ) .await?; - connd.setup_default_profiles().await?; - - if let Err(e) = connd.import_legacy_wpa_conf(&usr_persistent).await { - warn!("failed to import legacy wpa config {e}"); - } - - if let Err(e) = connd.ensure_networking_enabled().await { - warn!("failed to ensure networking is enabled {e}"); - } - - if let Err(e) = connd.ensure_nm_state_below_max_size(usr_persistent).await { - warn!("failed to ensure nm state below max size: {e}"); - } - let mut tasks = vec![connd.spawn()]; if let OrbCapabilities::CellularAndWifi = cap { diff --git a/orb-connd/src/service/mod.rs b/orb-connd/src/service/mod.rs index c805173a..56e69c92 100644 --- a/orb-connd/src/service/mod.rs +++ b/orb-connd/src/service/mod.rs @@ -179,14 +179,24 @@ impl ConndService { // stored on disk and deleting them. other nm calls to store profiles will only store // them in memory going forward let old_profiles = self.nm.list_wifi_profiles().await?; - for profile in old_profiles { + + info!( + "importing {} profiles stored in nm to secure storage", + old_profiles.len() + ); + + for profile in &old_profiles { self.nm.remove_profile(&profile.id).await?; - self.profile_store.insert(profile); + self.profile_store.insert(profile.clone()); } + // import profiles from secure storage + // overlapped ones will overwrite imported ones from NM self.profile_store.import().await?; let mut profiles = self.profile_store.values(); + // this is a shitty hacky workaround because add_wifi_profile doesn't take in priority + // TODO: fix later @vmenge profiles.sort_by_key(|p| p.priority); for profile in profiles { @@ -434,6 +444,8 @@ impl ConndT for ConndService { } async fn list_wifi_profiles(&self) -> ZResult> { + info!("listing wifi profiles"); + let active_conns = self .nm .active_connections() From ab78f698050ac741cbe4115bdeb9440e670b4e1a Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Tue, 30 Dec 2025 19:28:43 -0500 Subject: [PATCH 15/16] spawn storage subproc based on scope and username --- Cargo.lock | 11 ++++++++++ orb-connd/Cargo.toml | 1 + orb-connd/src/main.rs | 10 ++++++--- orb-connd/src/secure_storage/mod.rs | 25 ++++++++++++++++++---- orb-connd/src/secure_storage/subprocess.rs | 14 ++++++++---- orb-connd/tests/fixture.rs | 10 ++++++--- 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15dc993f..deb8a066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8157,6 +8157,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid 1.18.1", + "uzers", "zbus", ] @@ -13734,6 +13735,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "uzers" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8275fb1afee25b4111d2dc8b5c505dbbc4afd0b990cb96deb2d88bff8be18d" +dependencies = [ + "libc", + "log", +] + [[package]] name = "v_frame" version = "0.3.9" diff --git a/orb-connd/Cargo.toml b/orb-connd/Cargo.toml index 1e142902..cbb63d1f 100644 --- a/orb-connd/Cargo.toml +++ b/orb-connd/Cargo.toml @@ -54,6 +54,7 @@ tokio-util = { workspace = true, features = ["codec"] } tracing-subscriber.workspace = true tracing.workspace = true zbus.workspace = true +uzers = "0.12.0" [dev-dependencies] async-tempfile.workspace = true diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index ebd78c7f..6db66fb2 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -4,7 +4,7 @@ use orb_connd::{ connectivity_daemon, modem_manager::cli::ModemManagerCli, network_manager::NetworkManager, - secure_storage::{self, SecureStorage}, + secure_storage::{self, ConndStorageScopes, SecureStorage}, statsd::dd::DogstatsdClient, wpa_ctrl::cli::WpaCli, }; @@ -70,8 +70,12 @@ fn connectivity_daemon() -> Result<()> { ); let cancel_token = CancellationToken::new(); - let secure_storage = - SecureStorage::new(std::env::current_exe()?, false, cancel_token.clone()); + let secure_storage = SecureStorage::new( + std::env::current_exe()?, + false, + cancel_token.clone(), + ConndStorageScopes::NmProfiles, + ); let tasks = connectivity_daemon::program() .sysfs("/sys") diff --git a/orb-connd/src/secure_storage/mod.rs b/orb-connd/src/secure_storage/mod.rs index b86359a9..5301d073 100644 --- a/orb-connd/src/secure_storage/mod.rs +++ b/orb-connd/src/secure_storage/mod.rs @@ -10,8 +10,20 @@ use tokio_util::sync::{CancellationToken, DropGuard}; type RequestChannelPayload = (Request, oneshot::Sender); -/// The effective user id for the CA. -const CA_EUID: u32 = 1000; // TODO: Figure this out +/// The complete list of all "use cases" that connd has for storage. Each one gets +/// mapped to a different UID and/or TA. +pub enum ConndStorageScopes { + /// NetworkManager Wifi profiles + NmProfiles, +} + +impl ConndStorageScopes { + const fn as_username(&self) -> &'static str { + match self { + Self::NmProfiles => "orb-ss-connd-nmprofiles", + } + } +} /// Async-friendly handle through which the secure storage can be talked to. /// @@ -23,8 +35,13 @@ pub struct SecureStorage { } impl SecureStorage { - pub fn new(exe_path: PathBuf, in_memory: bool, cancel: CancellationToken) -> Self { - self::subprocess::spawn(exe_path, in_memory, 1, cancel) + pub fn new( + exe_path: PathBuf, + in_memory: bool, + cancel: CancellationToken, + scope: ConndStorageScopes, + ) -> Self { + self::subprocess::spawn(exe_path, in_memory, 1, cancel, scope) } pub async fn get(&self, key: String) -> Result> { diff --git a/orb-connd/src/secure_storage/subprocess.rs b/orb-connd/src/secure_storage/subprocess.rs index be4554ca..8ac982dc 100644 --- a/orb-connd/src/secure_storage/subprocess.rs +++ b/orb-connd/src/secure_storage/subprocess.rs @@ -1,8 +1,8 @@ //! Implementation of secure storage backend using a fork/exec subprocess. use crate::secure_storage::messages::{GetErr, PutErr, Request, Response}; -use crate::secure_storage::{RequestChannelPayload, SecureStorage, CA_EUID}; -use color_eyre::eyre::Result; +use crate::secure_storage::{ConndStorageScopes, RequestChannelPayload, SecureStorage}; +use color_eyre::eyre::{eyre, Result}; use futures::{Sink, SinkExt as _, Stream, TryStreamExt as _}; use orb_secure_storage_ca::BackendT; use std::io::Result as IoResult; @@ -20,8 +20,9 @@ pub(super) fn spawn( in_memory: bool, request_queue_size: usize, cancel: CancellationToken, + scope: ConndStorageScopes, ) -> SecureStorage { - let mut framed_pipes = make_framed_subprocess(exe_path, in_memory); + let mut framed_pipes = make_framed_subprocess(exe_path, in_memory, scope); let (request_tx, mut request_rx) = mpsc::channel::(request_queue_size); let cancel_clone = cancel.clone(); @@ -59,10 +60,15 @@ pub(super) fn spawn( fn make_framed_subprocess( exe_path: PathBuf, in_memory: bool, + scope: ConndStorageScopes, ) -> impl Stream> + Sink { let current_euid = rustix::process::geteuid(); let child_euid = if current_euid.is_root() { - CA_EUID + let child_username = scope.as_username(); + uzers::get_user_by_name(child_username) + .ok_or_else(|| eyre!("username {child_username} doesn't exist")) + .unwrap() + .uid() } else { warn!("current EUID in parent connd process is not root! For this reason we will spawn the subprocess as the same EUID, since we don't have perms to change it. This probably only should be done in integration tests." ); current_euid.as_raw() diff --git a/orb-connd/tests/fixture.rs b/orb-connd/tests/fixture.rs index 8e4cd2a6..8731b17a 100644 --- a/orb-connd/tests/fixture.rs +++ b/orb-connd/tests/fixture.rs @@ -11,7 +11,7 @@ use orb_connd::{ ModemManager, Signal, SimId, SimInfo, }, network_manager::NetworkManager, - secure_storage::SecureStorage, + secure_storage::{ConndStorageScopes, SecureStorage}, statsd::StatsdClient, wpa_ctrl::WpaCtrl, OrbCapabilities, @@ -120,8 +120,12 @@ impl Fixture { .unwrap(); let cancel_token = CancellationToken::new(); - let secure_storage = - SecureStorage::new(built_connd.path().into(), true, cancel_token.clone()); + let secure_storage = SecureStorage::new( + built_connd.path().into(), + true, + cancel_token.clone(), + ConndStorageScopes::NmProfiles, + ); if let Some(arrange_cb) = arrange { let ctx = Ctx { From 7ff368f0681da1f405a54b1c1b90b56ec15a50f1 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Tue, 30 Dec 2025 19:56:13 -0500 Subject: [PATCH 16/16] pass scope argument to subprocess worker --- orb-connd/src/main.rs | 14 ++++++++------ orb-connd/src/secure_storage/mod.rs | 22 +++++++++++++++++++++- orb-connd/src/secure_storage/subprocess.rs | 22 ++++++++++++---------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/orb-connd/src/main.rs b/orb-connd/src/main.rs index 6db66fb2..966652be 100644 --- a/orb-connd/src/main.rs +++ b/orb-connd/src/main.rs @@ -32,7 +32,9 @@ pub enum Command { ConnectivityDaemon, SecureStorageWorker { #[arg(long)] - in_memory: Option, + in_memory: bool, + #[arg(long)] + scope: ConndStorageScopes, }, } @@ -47,8 +49,8 @@ fn main() -> Result<()> { use Command::*; let result = match args.command.unwrap_or_default() { ConnectivityDaemon => connectivity_daemon(), - SecureStorageWorker { in_memory } => { - secure_storage_worker(in_memory.unwrap_or_default()) + SecureStorageWorker { in_memory, scope } => { + secure_storage_worker(in_memory, scope) } }; @@ -109,7 +111,7 @@ fn connectivity_daemon() -> Result<()> { }) } -fn secure_storage_worker(in_memory: bool) -> Result<()> { +fn secure_storage_worker(in_memory: bool, scope: ConndStorageScopes) -> Result<()> { let rt = tokio::runtime::Builder::new_current_thread().build()?; let io = io::join(io::stdin(), io::stdout()); @@ -118,14 +120,14 @@ fn secure_storage_worker(in_memory: bool) -> Result<()> { if in_memory { let mut ctx = orb_secure_storage_ca::in_memory::InMemoryContext::default(); rt.block_on(secure_storage::subprocess::entry::( - io, &mut ctx, + io, &mut ctx, scope, )) } else { let mut ctx = orb_secure_storage_ca::reexported_crates::optee_teec::Context::new() .wrap_err("failed to initialize optee context")?; rt.block_on(secure_storage::subprocess::entry::( - io, &mut ctx, + io, &mut ctx, scope, )) } } diff --git a/orb-connd/src/secure_storage/mod.rs b/orb-connd/src/secure_storage/mod.rs index 5301d073..0bbb47c9 100644 --- a/orb-connd/src/secure_storage/mod.rs +++ b/orb-connd/src/secure_storage/mod.rs @@ -4,7 +4,8 @@ pub mod subprocess; use self::messages::{Request, Response}; use color_eyre::eyre::{Context, Result}; -use std::{path::PathBuf, sync::Arc}; +use orb_secure_storage_ca::StorageDomain; +use std::{fmt::Display, path::PathBuf, sync::Arc}; use tokio::sync::{mpsc, oneshot}; use tokio_util::sync::{CancellationToken, DropGuard}; @@ -12,17 +13,36 @@ type RequestChannelPayload = (Request, oneshot::Sender); /// The complete list of all "use cases" that connd has for storage. Each one gets /// mapped to a different UID and/or TA. +#[derive(Debug, Eq, PartialEq, Clone, Copy, clap::ValueEnum)] pub enum ConndStorageScopes { /// NetworkManager Wifi profiles NmProfiles, } +impl Display for ConndStorageScopes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + ConndStorageScopes::NmProfiles => "nm-profiles", + }; + + f.write_str(s) + } +} + impl ConndStorageScopes { + /// The linux username that should be used for this scope const fn as_username(&self) -> &'static str { match self { Self::NmProfiles => "orb-ss-connd-nmprofiles", } } + + /// The TA storage domain that should be used when interacting with this scope. + const fn as_domain(&self) -> StorageDomain { + match self { + ConndStorageScopes::NmProfiles => StorageDomain::WifiProfiles, + } + } } /// Async-friendly handle through which the secure storage can be talked to. diff --git a/orb-connd/src/secure_storage/subprocess.rs b/orb-connd/src/secure_storage/subprocess.rs index ec6cc04f..9c7de907 100644 --- a/orb-connd/src/secure_storage/subprocess.rs +++ b/orb-connd/src/secure_storage/subprocess.rs @@ -4,7 +4,6 @@ use crate::secure_storage::messages::{GetErr, PutErr, Request, Response}; use crate::secure_storage::{ConndStorageScopes, RequestChannelPayload, SecureStorage}; use color_eyre::eyre::{eyre, Result}; use futures::{Sink, SinkExt as _, Stream, TryStreamExt as _}; -use orb_secure_storage_ca::reexported_crates::orb_secure_storage_proto::StorageDomain; use orb_secure_storage_ca::BackendT; use std::io::Result as IoResult; use std::path::PathBuf; @@ -75,12 +74,16 @@ fn make_framed_subprocess( current_euid.as_raw() }; - let mut child = tokio::process::Command::new(exe_path) - .arg("secure-storage-worker") - .arg(format!("--in-memory={in_memory}")) + let mut cmd = tokio::process::Command::new(exe_path); + cmd.arg("secure-storage-worker") + .args(["--scope", &scope.to_string()]) .uid(child_euid) .stdin(Stdio::piped()) - .stdout(Stdio::piped()) + .stdout(Stdio::piped()); + if in_memory { + cmd.arg("--in-memory"); + } + let mut child = cmd .spawn() .expect("failed to spawn secure storage subprocess"); let stdin = child.stdin.take().unwrap(); @@ -103,6 +106,7 @@ fn make_framed_subprocess( pub async fn entry( io: impl AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static, secure_storage_context: &mut B::Context, + scope: ConndStorageScopes, ) -> Result<()> where B: BackendT + 'static, @@ -126,11 +130,9 @@ where ); // A bit lame to use a mutex just for `spawn_blocking()` but /shrug - let client: SsClient = - Arc::new(std::sync::Mutex::new(orb_secure_storage_ca::Client::new( - secure_storage_context, - StorageDomain::WifiProfiles, - )?)); + let client: SsClient = Arc::new(std::sync::Mutex::new( + orb_secure_storage_ca::Client::new(secure_storage_context, scope.as_domain())?, + )); while let Some(input) = framed.try_next().await? { info!("request: {input:?}");