[Fix] Stability

This commit is contained in:
Alex Emmet 2026-08-28 13:20:20 +02:00
commit cf9607b15e
No known key found for this signature in database
5 changed files with 55 additions and 54 deletions

View file

@ -31,4 +31,3 @@ uuid = { version = "1.24.0", features = ["v4", "v7"] }
thiserror = "2.0.19" thiserror = "2.0.19"
serde = { version = "1.0.229", features = ["derive"] } serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151" serde_json = "1.0.151"
zeroize = "1.9"

View file

@ -54,6 +54,14 @@ pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) ->
Ok(()) Ok(())
} }
pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> {
sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?")
.bind(key.try_as_bytes()?)
.bind(id.0)
.execute(&pool().await?)
.await?;
Ok(())
}
pub async fn delete_iota(id: IotaId) -> Result<()> { pub async fn delete_iota(id: IotaId) -> Result<()> {
sqlx::query("DELETE FROM iotas WHERE id = ?") sqlx::query("DELETE FROM iotas WHERE id = ?")
.bind(id.0) .bind(id.0)

View file

@ -1,7 +1,8 @@
use crate::error::{IdentityError, Result}; use crate::error::{IdentityError, Result};
use mtp::crypto::{Keyring, PublicKeyBundle}; use mtp::crypto::{Keyring, PublicKeyBundle};
use mtp::files::{ use mtp::files::{
FileError, load_keyring, load_public_key_bundle, save_keyring, save_public_key_bundle, FileError, load_keyring_raw, load_public_key_bundle, save_keyring_raw,
save_public_key_bundle,
}; };
use std::{ use std::{
fs, fs,
@ -22,25 +23,20 @@ pub struct OmegaIdentity {
static PUBLIC_BUNDLE_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); static PUBLIC_BUNDLE_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
impl OmegaIdentity { impl OmegaIdentity {
pub fn load_or_create(passphrase: &[u8]) -> Result<Self> { pub fn load_or_create() -> Result<Self> {
Self::load_or_create_at( Self::load_or_create_at(Path::new(KEYRING_PATH), Path::new(PUBLIC_KEY_PATH))
Path::new(KEYRING_PATH),
Path::new(PUBLIC_KEY_PATH),
passphrase,
)
} }
pub(crate) fn load_or_create_at( pub(crate) fn load_or_create_at(
keyring_path: impl AsRef<Path>, keyring_path: impl AsRef<Path>,
public_key_path: impl AsRef<Path>, public_key_path: impl AsRef<Path>,
passphrase: &[u8],
) -> Result<Self> { ) -> Result<Self> {
let keyring_path = keyring_path.as_ref(); let keyring_path = keyring_path.as_ref();
let public_key_path = public_key_path.as_ref(); let public_key_path = public_key_path.as_ref();
let keyring = match load_keyring(keyring_path, passphrase) { let keyring = match load_keyring_raw(keyring_path) {
Ok(keyring) => keyring, Ok(keyring) => keyring,
Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => { Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => {
return Self::create_at(keyring_path, public_key_path, passphrase); return Self::create_at(keyring_path, public_key_path);
} }
Err(error) => { Err(error) => {
return Err(IdentityError::Storage { return Err(IdentityError::Storage {
@ -72,9 +68,9 @@ impl OmegaIdentity {
Ok(identity) Ok(identity)
} }
fn create_at(keyring_path: &Path, public_key_path: &Path, passphrase: &[u8]) -> Result<Self> { fn create_at(keyring_path: &Path, public_key_path: &Path) -> Result<Self> {
let keyring = Keyring::generate(); let keyring = Keyring::generate();
save_keyring(&keyring, keyring_path, passphrase).map_err(|error| { save_keyring_raw(&keyring, keyring_path).map_err(|error| {
IdentityError::Storage { IdentityError::Storage {
path: keyring_path.to_path_buf(), path: keyring_path.to_path_buf(),
source: error, source: error,
@ -188,9 +184,7 @@ mod tests {
let directory = test_directory(); let directory = test_directory();
let keyring_path = directory.join("omega.mk"); let keyring_path = directory.join("omega.mk");
let public_key_path = directory.join("omega.mpkb"); let public_key_path = directory.join("omega.mpkb");
let passphrase = b"test-passphrase"; let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
.expect("create identity"); .expect("create identity");
let first_bundle = first.public_key_bundle().try_as_bytes().expect("bundle"); let first_bundle = first.public_key_bundle().try_as_bytes().expect("bundle");
@ -199,9 +193,8 @@ mod tests {
assert_eq!(&keyring_bytes[..4], b"MTMK"); assert_eq!(&keyring_bytes[..4], b"MTMK");
assert_eq!(&bundle_bytes[..4], b"MPKB"); assert_eq!(&bundle_bytes[..4], b"MPKB");
let restarted = let restarted = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) .expect("reload identity");
.expect("reload identity");
assert_eq!( assert_eq!(
restarted restarted
.public_key_bundle() .public_key_bundle()
@ -227,8 +220,7 @@ mod tests {
let public_key_path = directory.join("omega.mpkb"); let public_key_path = directory.join("omega.mpkb");
fs::write(&keyring_path, b"not-a-keyring").expect("write invalid keyring"); fs::write(&keyring_path, b"not-a-keyring").expect("write invalid keyring");
let result = let result = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path);
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"test-passphrase");
assert!(result.is_err()); assert!(result.is_err());
assert!(!public_key_path.exists()); assert!(!public_key_path.exists());
@ -240,16 +232,13 @@ mod tests {
let directory = test_directory(); let directory = test_directory();
let keyring_path = directory.join("omega.mk"); let keyring_path = directory.join("omega.mk");
let public_key_path = directory.join("omega.mpkb"); let public_key_path = directory.join("omega.mpkb");
let passphrase = b"test-passphrase"; OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
.expect("create identity"); .expect("create identity");
let original_keyring = fs::read(&keyring_path).expect("read keyring"); let original_keyring = fs::read(&keyring_path).expect("read keyring");
fs::remove_file(&public_key_path).expect("remove bundle"); fs::remove_file(&public_key_path).expect("remove bundle");
let repaired = let repaired = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) .expect("repair bundle");
.expect("repair bundle");
assert_eq!( assert_eq!(
fs::read(&keyring_path).expect("read keyring"), fs::read(&keyring_path).expect("read keyring"),
@ -271,16 +260,14 @@ mod tests {
let directory = test_directory(); let directory = test_directory();
let keyring_path = directory.join("omega.mk"); let keyring_path = directory.join("omega.mk");
let public_key_path = directory.join("omega.mpkb"); let public_key_path = directory.join("omega.mpkb");
let passphrase = b"test-passphrase"; OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
.expect("create identity"); .expect("create identity");
let other_keyring = Keyring::generate(); let other_keyring = Keyring::generate();
save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path) save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path)
.expect("save mismatched bundle"); .expect("save mismatched bundle");
assert!(matches!( assert!(matches!(
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase), OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path),
Err(crate::OmegaError::Identity( Err(crate::OmegaError::Identity(
IdentityError::PublicBundleMismatch { .. } IdentityError::PublicBundleMismatch { .. }
)) ))
@ -290,24 +277,28 @@ mod tests {
} }
#[test] #[test]
fn wrong_passphrase_does_not_replace_existing_keyring() { fn existing_raw_keyring_is_reloaded_without_a_passphrase() {
let directory = test_directory(); let directory = test_directory();
let keyring_path = directory.join("omega.mk"); let keyring_path = directory.join("omega.mk");
let public_key_path = directory.join("omega.mpkb"); let public_key_path = directory.join("omega.mpkb");
let passphrase = b"test-passphrase";
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
.expect("create identity"); .expect("create identity");
let original_keyring = fs::read(&keyring_path).expect("read keyring"); let original_keyring = fs::read(&keyring_path).expect("read keyring");
assert!( let reloaded = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path)
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"wrong-passphrase") .expect("reload identity");
.is_err()
);
assert_eq!( assert_eq!(
fs::read(&keyring_path).expect("read keyring"), fs::read(&keyring_path).expect("read keyring"),
original_keyring original_keyring
); );
assert_eq!(
reloaded
.public_key_bundle()
.try_as_bytes()
.expect("bundle"),
first.public_key_bundle().try_as_bytes().expect("bundle")
);
fs::remove_dir_all(directory).expect("remove test directory"); fs::remove_dir_all(directory).expect("remove test directory");
} }

View file

@ -25,7 +25,6 @@ use std::env;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
use tokio::time::interval; use tokio::time::interval;
use zeroize::Zeroizing;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -38,18 +37,6 @@ async fn main() {
log_in!("Incoming messages"); log_in!("Incoming messages");
log_out!("Outgoing messages"); log_out!("Outgoing messages");
let identity_secret = match env::var("OMEGA_IDENTITY_SECRET") {
Ok(secret) if !secret.is_empty() => secret,
Ok(_) => {
log!("[FATAL] OMEGA_IDENTITY_SECRET must not be empty");
return;
}
Err(error) => {
log!("[FATAL] Unable to load OMEGA_IDENTITY_SECRET: {}", error);
return;
}
};
let identity_secret = Zeroizing::new(identity_secret);
let config = match OmegaConfig::from_env() { let config = match OmegaConfig::from_env() {
Ok(config) => config, Ok(config) => config,
Err(error) => { Err(error) => {
@ -61,14 +48,13 @@ async fn main() {
log!("[FATAL] Omega rate-limit configuration was initialized more than once"); log!("[FATAL] Omega rate-limit configuration was initialized more than once");
return; return;
} }
let identity = match identity::OmegaIdentity::load_or_create(identity_secret.as_bytes()) { let identity = match identity::OmegaIdentity::load_or_create() {
Ok(identity) => identity, Ok(identity) => identity,
Err(error) => { Err(error) => {
log!("[FATAL] Omega identity initialization failed: {}", error); log!("[FATAL] Omega identity initialization failed: {}", error);
return; return;
} }
}; };
drop(identity_secret);
let state = OmegaState::new(identity, config); let state = OmegaState::new(identity, config);
log!("Started"); log!("Started");

View file

@ -764,8 +764,9 @@ pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std:
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{DispatchClass, OmikronConnection}; use super::{DispatchClass, OmikronConnection, parse_bind_address};
use mtp::codec::{CommunicationType, CommunicationValue}; use mtp::codec::{CommunicationType, CommunicationValue};
use std::net::{IpAddr, Ipv4Addr};
#[test] #[test]
fn relay_dispatch_is_not_on_the_ordered_state_lane() { fn relay_dispatch_is_not_on_the_ordered_state_lane() {
@ -776,6 +777,14 @@ mod tests {
); );
} }
#[test]
fn parses_configured_bind_address() {
assert_eq!(
parse_bind_address(Some("10.200.2.0")),
Ok(IpAddr::V4(Ipv4Addr::new(10, 200, 2, 0)))
);
}
#[test] #[test]
fn presence_lifecycle_dispatch_is_ordered() { fn presence_lifecycle_dispatch_is_ordered() {
let value = CommunicationValue::new(CommunicationType::UserConnected); let value = CommunicationValue::new(CommunicationType::UserConnected);
@ -806,4 +815,12 @@ mod tests {
&CommunicationValue::new(CommunicationType::GetUserData).with_id(1) &CommunicationValue::new(CommunicationType::GetUserData).with_id(1)
)); ));
} }
#[test]
fn defaults_bind_address_to_all_interfaces() {
assert_eq!(
parse_bind_address(None),
Ok(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
);
}
} }