From 82a5d9469b56c76778fdcc6a2c1ce75f6fc6ff76 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:48:46 +0200 Subject: [PATCH 1/2] [Fix] Connectivity --- Cargo.lock | 1 - Cargo.toml | 10 +--- README.md | 10 +--- mtp-type-maps | 2 +- src/bin/migrate_raw_keyring.rs | 23 --------- src/identity.rs | 93 ++++++---------------------------- src/main.rs | 17 +------ src/rho/client_connection.rs | 35 ++++++++++++- src/rho/iota_connection.rs | 9 +++- src/rho/relay_router.rs | 40 ++++++++++++++- src/rho/rho_connection.rs | 39 ++++++++++---- 11 files changed, 131 insertions(+), 148 deletions(-) delete mode 100644 src/bin/migrate_raw_keyring.rs diff --git a/Cargo.lock b/Cargo.lock index b30f3a7..988bd38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2030,7 +2030,6 @@ dependencies = [ "tokio", "trust-dns-resolver", "uuid", - "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b758cd8..8595400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ "client", "crypto", "files", + "raw", ] } mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" } @@ -33,12 +34,3 @@ livekit-protocol = "=0.7.10" thiserror = "2.0.19" trust-dns-resolver = "0.23.2" serde_json = "1.0.151" -zeroize = "1.9" - -[features] -raw-migration = ["mtp/raw"] - -[[bin]] -name = "migrate_raw_keyring" -path = "src/bin/migrate_raw_keyring.rs" -required-features = ["raw-migration"] diff --git a/README.md b/README.md index 1ca8ee3..9d560dc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The Omikron is only used when the Iota is in Centralized and Hybrid mode, or whe ## Configuration -`OMIKRON_IDENTITY_SECRET` is required. Provision it through the deployment's secret environment before starting Omikron. Omikron uses it to load `omikron.mk` as a protected keyring and fails startup if the secret or existing identity cannot be loaded. +Omikron stores its keyring unencrypted in `omikron.mk` and its public key bundle in `omikron.mpkb`. If the keyring does not exist, Omikron generates it on startup. Rho connection budgets can be configured with: @@ -19,11 +19,3 @@ Rho connection budgets can be configured with: semaphore and to the MTP WebServer admission semaphore. This means the same budget limits transport handshakes and authenticated Rho sessions instead of only limiting connections after authentication. - -To migrate an existing raw `omikron.mk`, provision `OMIKRON_IDENTITY_SECRET` and run: - -```sh -cargo run --features raw-migration --bin migrate_raw_keyring -``` - -The normal Omikron binary does not enable raw keyring support. diff --git a/mtp-type-maps b/mtp-type-maps index f3c5037..f4e45aa 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit f3c5037b0a099ae5389486eec77471bd5addab4a +Subproject commit f4e45aa3a3ad0e3c3a257f66857b904a1af7901c diff --git a/src/bin/migrate_raw_keyring.rs b/src/bin/migrate_raw_keyring.rs deleted file mode 100644 index bdd82d4..0000000 --- a/src/bin/migrate_raw_keyring.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[path = "../identity.rs"] -mod identity; - -use identity::{ - KEYRING_PATH, PUBLIC_KEY_PATH, identity_secret_from_environment, migrate_raw_keyring, -}; - -fn main() { - let secret = match identity_secret_from_environment() { - Ok(secret) => secret, - Err(error) => { - eprintln!("Unable to load Omikron identity secret: {error}"); - std::process::exit(1); - } - }; - - if let Err(error) = migrate_raw_keyring(secret.as_slice(), KEYRING_PATH, PUBLIC_KEY_PATH) { - eprintln!("Unable to migrate Omikron identity: {error}"); - std::process::exit(1); - } - - println!("Migrated {KEYRING_PATH} to protected keyring storage"); -} diff --git a/src/identity.rs b/src/identity.rs index c0459c2..b52d8cc 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,37 +1,23 @@ -use std::{env, io::ErrorKind, path::Path}; +use std::{io::ErrorKind, path::Path}; use mtp::crypto::Keyring; -use mtp::files::{FileError, load_keyring, save_keyring, save_public_key_bundle}; -use zeroize::Zeroizing; +use mtp::files::{FileError, load_keyring_raw, save_keyring_raw, save_public_key_bundle}; pub const KEYRING_PATH: &str = "./omikron.mk"; pub const PUBLIC_KEY_PATH: &str = "./omikron.mpkb"; -const IDENTITY_SECRET_ENV: &str = "OMIKRON_IDENTITY_SECRET"; -pub fn identity_secret_from_environment() -> Result>, String> { - let secret = env::var(IDENTITY_SECRET_ENV).map_err(|_| { - format!("{IDENTITY_SECRET_ENV} must be set before Omikron networking starts") - })?; - if secret.trim().is_empty() { - return Err(format!("{IDENTITY_SECRET_ENV} must not be empty")); - } - Ok(Zeroizing::new(secret.into_bytes())) -} - -#[allow(dead_code)] pub fn load_or_create_keyring( - passphrase: &[u8], keyring_path: impl AsRef, public_key_path: impl AsRef, ) -> Result { let keyring_path = keyring_path.as_ref(); let public_key_path = public_key_path.as_ref(); - match load_keyring(keyring_path, passphrase) { + match load_keyring_raw(keyring_path) { Ok(keyring) => Ok(keyring), Err(FileError::Io(error)) if error.kind() == ErrorKind::NotFound => { let keyring = Keyring::generate(); - save_keyring(&keyring, keyring_path, passphrase).map_err(|error| { + save_keyring_raw(&keyring, keyring_path).map_err(|error| { format!("unable to persist Omikron keyring at {keyring_path:?}: {error}") })?; save_public_key_bundle(&keyring.public_key_bundle(), public_key_path).map_err( @@ -41,10 +27,7 @@ pub fn load_or_create_keyring( ) }, )?; - eprintln!( - "Generated new protected keyring at {}", - keyring_path.display() - ); + eprintln!("Generated new keyring at {}", keyring_path.display()); Ok(keyring) } Err(error) => Err(format!( @@ -54,29 +37,11 @@ pub fn load_or_create_keyring( } } -#[cfg(feature = "raw-migration")] -#[allow(dead_code)] -pub fn migrate_raw_keyring( - passphrase: &[u8], - keyring_path: impl AsRef, - public_key_path: impl AsRef, -) -> Result { - use mtp::files::load_keyring_raw; - - let keyring = load_keyring_raw(keyring_path.as_ref()) - .map_err(|error| format!("raw keyring migration failed: {error}"))?; - save_keyring(&keyring, keyring_path.as_ref(), passphrase) - .map_err(|error| format!("protected keyring write failed: {error}"))?; - save_public_key_bundle(&keyring.public_key_bundle(), public_key_path.as_ref()) - .map_err(|error| format!("public key bundle write failed: {error}"))?; - Ok(keyring) -} - #[cfg(test)] mod tests { use super::*; use std::{ - fs, + env, fs, time::{SystemTime, UNIX_EPOCH}, }; @@ -111,11 +76,15 @@ mod tests { #[test] fn missing_identity_is_created_and_can_be_loaded_again() { let paths = TestPaths::new(); - let passphrase = b"test identity secret"; - let first = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap(); + let first = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap(); let first_public = first.public_key_bundle().try_as_bytes().unwrap(); - let second = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap(); + let stored = load_keyring_raw(&paths.keyring).unwrap(); + let second = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap(); + assert_eq!( + stored.public_key_bundle().try_as_bytes().unwrap(), + first_public + ); assert_eq!( second.public_key_bundle().try_as_bytes().unwrap(), first_public @@ -127,9 +96,7 @@ mod tests { let paths = TestPaths::new(); fs::write(&paths.keyring, b"not a keyring").unwrap(); - let error = - load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key) - .unwrap_err(); + let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err(); assert!(error.contains("unable to load existing Omikron identity")); } @@ -138,9 +105,7 @@ mod tests { let paths = TestPaths::new(); fs::create_dir(&paths.keyring).unwrap(); - let error = - load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key) - .unwrap_err(); + let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err(); assert!(error.contains("unable to load existing Omikron identity")); } @@ -149,33 +114,7 @@ mod tests { let paths = TestPaths::new(); fs::create_dir(&paths.public_key).unwrap(); - let error = - load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key) - .unwrap_err(); + let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err(); assert!(error.contains("unable to persist Omikron public key bundle")); } - - #[cfg(feature = "raw-migration")] - #[test] - fn raw_identity_migration_rewrites_protected_storage() { - use mtp::files::{load_keyring, save_keyring_raw}; - - let paths = TestPaths::new(); - let original = Keyring::generate(); - save_keyring_raw(&original, &paths.keyring).unwrap(); - - let migrated = - migrate_raw_keyring(b"test identity secret", &paths.keyring, &paths.public_key) - .unwrap(); - let loaded = load_keyring(&paths.keyring, b"test identity secret").unwrap(); - - assert_eq!( - migrated.public_key_bundle().try_as_bytes().unwrap(), - original.public_key_bundle().try_as_bytes().unwrap() - ); - assert_eq!( - loaded.public_key_bundle().try_as_bytes().unwrap(), - original.public_key_bundle().try_as_bytes().unwrap() - ); - } } diff --git a/src/main.rs b/src/main.rs index ffc3a70..f7769fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,9 +24,7 @@ use crate::{ app_state::AppState, calls::{call_manager::CallManager, call_util::LiveKitService}, config::Config, - identity::{ - KEYRING_PATH, PUBLIC_KEY_PATH, identity_secret_from_environment, load_or_create_keyring, - }, + identity::{KEYRING_PATH, PUBLIC_KEY_PATH, load_or_create_keyring}, omega::omega_connection::{OmegaConnection, start_task_cleanup_loop}, rho::rho_manager::RhoManager, rho::server::start, @@ -50,18 +48,7 @@ async fn main() { } }; - let identity_secret = match identity_secret_from_environment() { - Ok(secret) => secret, - Err(error) => { - eprintln!("Unable to load Omikron identity secret: {error}"); - return; - } - }; - let keyring = match load_or_create_keyring( - &identity_secret, - KEYRING_PATH, - PUBLIC_KEY_PATH, - ) { + let keyring = match load_or_create_keyring(KEYRING_PATH, PUBLIC_KEY_PATH) { Ok(keyring) => keyring, Err(error) => { eprintln!("Unable to load Omikron keyring: {error}"); diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs index d87eea1..2899c2a 100644 --- a/src/rho/client_connection.rs +++ b/src/rho/client_connection.rs @@ -17,6 +17,13 @@ use tokio::sync::RwLock; use trust_dns_resolver::TokioAsyncResolver; use uuid::Uuid; +fn authenticated_peer_control_request( + cv: &CommunicationValue, + user_id: u64, +) -> CommunicationValue { + cv.clone().with_sender(user_id) +} + pub struct ClientConnection { pub state: Arc, pub user_id: u64, @@ -210,7 +217,10 @@ impl ClientConnection { .await; return; }; - match rho.await_peer_control(&cv.with_sender(self.user_id)).await { + match rho + .await_peer_control(&authenticated_peer_control_request(&cv, self.user_id)) + .await + { Ok(response) => self.send_message(&response).await, Err(_) => { self.send_error_response(message_id, CommunicationType::ErrorInternal) @@ -968,6 +978,29 @@ impl ClientConnection { } } +#[cfg(test)] +mod tests { + use super::authenticated_peer_control_request; + use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; + + #[test] + fn peer_control_requests_replace_a_client_supplied_sender() { + let request = CommunicationValue::new(CommunicationType::SyncedSettingSet) + .with_sender(99) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(99)); + + let authenticated = authenticated_peer_control_request(&request, 7); + + assert_eq!(authenticated.sender(), Some(7)); + assert_eq!( + authenticated + .get_data(DataType::UserId) + .and_then(|value| value.as_number()), + Some(99) + ); + } +} + // Implement Clone to make it easier to work with Arc impl Clone for ClientConnection { fn clone(&self) -> Self { diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs index 51d89fd..813131b 100755 --- a/src/rho/iota_connection.rs +++ b/src/rho/iota_connection.rs @@ -302,6 +302,13 @@ impl IotaConnection { return; } + log_cv_in!(PrintType::Iota, cv); + + if cv.is_type(CommunicationType::SyncedSettingChanged) { + self.forward_to_client(cv).await; + return; + } + let msg_id = message_id; if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { if (task)(self.clone(), cv.clone()) { @@ -309,8 +316,6 @@ impl IotaConnection { } } - log_cv_in!(PrintType::Iota, cv); - let cv = if cv.is_type(CommunicationType::ClientStateSync) { self.add_call_state(cv).await } else { diff --git a/src/rho/relay_router.rs b/src/rho/relay_router.rs index 97a5384..19e46f8 100644 --- a/src/rho/relay_router.rs +++ b/src/rho/relay_router.rs @@ -95,6 +95,10 @@ pub fn message_security_class(frame: &CommunicationValue) -> MessageSecurityClas } else if frame.is_type(CommunicationType::GetChatSecret) || frame.is_type(CommunicationType::MessageGet) || frame.is_type(CommunicationType::MessagesGet) + || frame.is_type(CommunicationType::SyncedSettingSet) + || frame.is_type(CommunicationType::SyncedSettingGet) + || frame.is_type(CommunicationType::SyncedSettingDelete) + || frame.is_type(CommunicationType::SyncedSettingsList) { MessageSecurityClass::AuthenticatedPeerControl } else { @@ -296,7 +300,8 @@ mod tests { use mtp::codec::{CommunicationType, CommunicationValue, DataValue}; use super::{ - RelayRouteError, RelaySource, RouteTarget, prepare_frame, validate_source_next_hop, + MessageSecurityClass, RelayRouteError, RelaySource, RouteTarget, message_security_class, + prepare_frame, validate_source_next_hop, }; fn wire(target: RouteTarget) -> u64 { @@ -418,4 +423,37 @@ mod tests { }; assert_eq!(routed.payload(), response.payload()); } + + #[test] + fn synchronized_setting_requests_use_authenticated_peer_control() { + for setting_type in [ + CommunicationType::SyncedSettingSet, + CommunicationType::SyncedSettingGet, + CommunicationType::SyncedSettingDelete, + CommunicationType::SyncedSettingsList, + ] { + let frame = CommunicationValue::new(setting_type); + assert_eq!( + message_security_class(&frame), + MessageSecurityClass::AuthenticatedPeerControl + ); + } + } + + #[test] + fn synchronized_setting_requests_are_not_relay_only() { + for setting_type in [ + CommunicationType::SyncedSettingSet, + CommunicationType::SyncedSettingGet, + CommunicationType::SyncedSettingDelete, + CommunicationType::SyncedSettingsList, + CommunicationType::SyncedSettingChanged, + ] { + let frame = CommunicationValue::new(setting_type); + assert_ne!( + message_security_class(&frame), + MessageSecurityClass::RelayOnly + ); + } + } } diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs index c933acf..6d79c4a 100644 --- a/src/rho/rho_connection.rs +++ b/src/rho/rho_connection.rs @@ -18,6 +18,16 @@ pub struct RhoConnection { app_connections: DashMap<(u64, String, Uuid), Arc>, } +fn message_targets_client(cv: &CommunicationValue, user_id: u64, session_id: u64) -> bool { + if cv.receiver() != Some(user_id) { + return false; + } + let Some(target_session_id) = cv.get_data(DataType::SessionId).as_number() else { + return true; + }; + target_session_id == i128::from(session_id) +} + impl RhoConnection { /// Create a new RhoConnection pub async fn new(iota_connection: Arc, user_ids: Vec) -> Self { @@ -254,25 +264,19 @@ impl RhoConnection { /// Send message from Iota to specific client pub async fn message_to_client(&self, cv: CommunicationValue) { let connections = self.get_client_connections().await; - let Some(receiver_id) = cv.receiver() else { + if cv.receiver().is_none() { log_err!( 0, crate::util::logger::PrintType::General, "Discarded message without an MTP receiver" ); return; - }; - let session_id = cv.get_data(DataType::SessionId).as_number(); + } for connection in connections.iter() { - if connection.user_id != receiver_id { + if !message_targets_client(&cv, connection.user_id, connection.session_id) { continue; } - if let Some(session_id) = session_id { - if connection.session_id as i128 != session_id { - continue; - } - } connection.clone().send_message(&cv).await; } } @@ -357,3 +361,20 @@ impl RhoConnection { self.client_connections.len() } } + +#[cfg(test)] +mod tests { + use super::message_targets_client; + use mtp::codec::{CommunicationType, CommunicationValue, DataType}; + + #[test] + fn sessionless_setting_change_targets_all_devices_for_one_user() { + let event = + CommunicationValue::new(CommunicationType::SyncedSettingChanged).with_receiver(7); + + assert!(message_targets_client(&event, 7, 1)); + assert!(message_targets_client(&event, 7, 2)); + assert!(!message_targets_client(&event, 8, 1)); + assert!(event.get_data(DataType::SessionId).is_none()); + } +} From f2fb75a099063c97ba494b44b13bcccabcae5d6d Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Sat, 29 Aug 2026 14:01:56 +0300 Subject: [PATCH 2/2] chore(deps): update rust crate livekit-api to 0.6.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8595400..ed73254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ tokio = { version = "1.53.0", features = ["full"] } log = "0.4" dotenv = "0.15.0" strum_macros = "0.28.0" -livekit-api = { version = "0.5.6", features = ["rustls-tls-native-roots"] } +livekit-api = { version = "0.6.0", features = ["rustls-tls-native-roots"] } livekit-protocol = "=0.7.10" thiserror = "2.0.19" trust-dns-resolver = "0.23.2"