[Fix] Stability
This commit is contained in:
parent
84a56678e0
commit
cf9607b15e
5 changed files with 55 additions and 54 deletions
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
src/main.rs
16
src/main.rs
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue