Add message signing

All messages are now signed by default with a randomly generated key.
TODO still is keyserver stuff, locking down the config file potentially
(or telling users to do it in the readme?), and doing any sort of
verification
This commit is contained in:
2026-05-31 11:09:15 -07:00
parent 1e67b80d10
commit 0f36ee454e
5 changed files with 277 additions and 10 deletions
+9 -1
View File
@@ -1,4 +1,5 @@
use clap::{Parser,Subcommand,ArgAction};
use ed25519_dalek::SigningKey;
use fedichat::client::{ClientMessage,SignedClientMessage,AuthMethod};
use fedichat::ServerAddr;
use fedichat::state::StatePath;
@@ -181,7 +182,7 @@ impl Command {
}
// If a command needs multiple messages, like an auth message first
// then call this
pub fn generate_messages(self,username: String,token: Option<String>)
pub fn generate_messages(self,username: String,token: Option<String>, key: SigningKey)
-> Result<Vec<SignedClientMessage>,MessageError>
{
let mut messages = Vec::with_capacity(2);
@@ -211,6 +212,11 @@ impl Command {
signature: Box::new([0])});
}
// sign all the messages
for message in messages.iter_mut() {
message.sign(key.clone())?;
}
Ok(messages)
}
}
@@ -223,6 +229,8 @@ pub enum MessageError {
UuidError(#[from] uuid::Error),
#[error("Error during file IO: {0}")]
IoError(#[from] std::io::Error),
#[error("Error while processing signature: {0}")]
Signature(#[from] fedichat::client::SignatureError)
}
+41 -5
View File
@@ -1,4 +1,6 @@
use expanduser::expanduser;
use rand::rngs::OsRng;
use ed25519_dalek::SigningKey;
use serde::{Serialize,Deserialize};
use std::collections::HashMap;
use std::fs::File;
@@ -62,14 +64,41 @@ impl Config {
pub fn insert_token(&mut self,server: &String,username: String, token: String) -> Result<(),ConfigError> {
let _ = self.servers.get_mut(server).ok_or(ConfigError::ServerNotFound(server.clone()))?
.users.insert(username,token);
.users.get_mut(&username).ok_or(ConfigError::UserNotFound(username.clone()))?.token = Some(token);
Ok(())
}
pub fn get_token(&self,server: &String, username: &String) -> Result<Option<String>,ConfigError> {
// TODO: Dont really have to clone the return value probably. Should refactor at some point
Ok(self.servers.get(server).ok_or(ConfigError::ServerNotFound(server.clone()))?
.users.get(username).cloned())
.users.get(username).ok_or(ConfigError::UserNotFound(username.clone()))?.token.clone())
}
// Set up key if user does not exist
//
// If user does exist they are guaranteed to already have a key
//
// Maybe at some point there should be a command to load/save/regenerate keys
pub fn maybe_gen_key(&mut self, server: &String, username: &String) -> Result<(),ConfigError>{
if !self.servers.contains_key(server) {
self.servers.insert(server.to_string(),Server{users: HashMap::new()});
}
let server = self.servers.get_mut(server).ok_or(ConfigError::ServerNotFound(server.clone()))?;
if !server.users.contains_key(username) {
let mut rng = OsRng;
let key = SigningKey::generate(&mut rng);
server.users.insert(username.to_string(),User{token: None, key});
}
Ok(())
}
pub fn get_key(&self,server: &String, username: &String) -> Result<SigningKey,ConfigError>{
Ok(self.servers.get(server).ok_or(ConfigError::ServerNotFound(server.clone()))?
.users.get(username).ok_or(ConfigError::UserNotFound(username.clone()))?.key.clone())
}
}
@@ -78,7 +107,14 @@ impl Config {
#[derive(Serialize,Deserialize)]
pub struct Server {
// user to token mapping
users: HashMap<String,String>
users: HashMap<String,User>
}
// NOTE: Storing keys in the config file means we need to lock down read access
#[derive(Serialize,Deserialize,Clone)]
pub struct User {
pub token: Option<String>,
pub key: ed25519_dalek::SigningKey
}
#[derive(Error,Debug)]
@@ -91,8 +127,8 @@ pub enum ConfigError {
#[error("Error while serializing config file: {0}")]
SerError(#[from] toml::ser::Error),
//#[error("User `{0}` not found in config")]
//UserNotFound(String),
#[error("User `{0}` not found in config")]
UserNotFound(String),
#[error("Server `{0}` not found in config")]
ServerNotFound(String),
+6 -1
View File
@@ -57,10 +57,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Err(e) => return Err(Box::new(e).into())
};
// set homeserver
let target_server = match cli.server {
Some(s) => s,
None => config.default_server.clone()
};
// Generate a key for the specified user if they do not already exist
config.maybe_gen_key(&target_server,&cli.username)?;
let token = config.get_token(&target_server,&cli.username)?;
debug!("Target server is {target_server}");
@@ -83,9 +86,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
if let Command::Fetch{file, ..} = &cli.command {
file_to_write = Some(file.clone());
}
let key = config.get_key(&target_server,&cli.username)?;
// send messages, each time waiting for a response
let messages = cli.command.generate_messages(cli.username.clone(),token)?;
let messages = cli.command.generate_messages(cli.username.clone(),token,key)?;
debug!("Sending commands");