aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2023-12-13 18:04:04 +0100
committerQuentin Dufour <quentin@deuxfleurs.fr>2023-12-13 18:04:04 +0100
commit29561dde41b402362f8baa3d9cd87a07f743b9fd (patch)
tree3d2698eb2c3547d923bf6468e65614eb7fc73dcf
parent064a1077c8c66fe8d3ee71f831c930e1ddfbc34a (diff)
downloadaerogramme-29561dde41b402362f8baa3d9cd87a07f743b9fd.tar.gz
aerogramme-29561dde41b402362f8baa3d9cd87a07f743b9fd.zip
CLI tools
-rw-r--r--src/config.rs2
-rw-r--r--src/login/mod.rs12
-rw-r--r--src/login/static_provider.rs4
-rw-r--r--src/main.rs142
4 files changed, 146 insertions, 14 deletions
diff --git a/src/config.rs b/src/config.rs
index eae50f5..e50cd68 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -155,6 +155,8 @@ pub fn read_config<T: serde::de::DeserializeOwned>(config_file: PathBuf) -> Resu
pub fn write_config<T: Serialize>(config_file: PathBuf, config: &T) -> Result<()> {
let mut file = std::fs::OpenOptions::new()
.write(true)
+ .create(true)
+ .truncate(true)
.open(config_file.as_path())?;
file.write_all(toml::to_string(config)?.as_bytes())?;
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 3d7a49f..9e0c437 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -109,16 +109,13 @@ impl CryptoRoot {
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
[ "aero", "cryptoroot", "pass", b64blob ] => {
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
- if blob.len() < 32 {
- bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len());
- }
- CryptoKeys::password_open(password, &blob[32..])
+ CryptoKeys::password_open(password, &blob)
},
[ "aero", "cryptoroot", "cleartext", b64blob ] => {
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
CryptoKeys::deserialize(&blob)
},
- [ "aero", "cryptoroot", "incoming", b64blob ] => {
+ [ "aero", "cryptoroot", "incoming", _ ] => {
bail!("incoming cryptoroot does not contain a crypto key!")
},
[ "aero", "cryptoroot", "keyring", _ ] =>{
@@ -184,8 +181,9 @@ impl CryptoKeys {
// Password sealed keys serialize/deserialize
pub fn password_open(password: &str, blob: &[u8]) -> Result<Self> {
- let kdf_salt = &blob[0..32];
- let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[32..])?;
+ let _pubkey = &blob[0..32];
+ let kdf_salt = &blob[32..64];
+ let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[64..])?;
let keys = Self::deserialize(&password_openned)?;
Ok(keys)
diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs
index 178d97e..85d55ef 100644
--- a/src/login/static_provider.rs
+++ b/src/login/static_provider.rs
@@ -81,7 +81,7 @@ impl LoginProvider for StaticLoginProvider {
}),
};
- let cr = CryptoRoot(user.crypto_root);
+ let cr = CryptoRoot(user.crypto_root.clone());
let keys = cr.crypto_keys(password)?;
tracing::debug!(user=%username, "logged");
@@ -106,7 +106,7 @@ impl LoginProvider for StaticLoginProvider {
}),
};
- let cr = CryptoRoot(user.crypto_root);
+ let cr = CryptoRoot(user.crypto_root.clone());
let public_key = cr.public_key()?;
Ok(PublicCredentials {
diff --git a/src/main.rs b/src/main.rs
index 3b5f474..2beaf21 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,14 +34,54 @@ struct Args {
#[derive(Subcommand, Debug)]
enum Command {
#[clap(subcommand)]
+ /// A daemon to be run by the end user, on a personal device
Companion(CompanionCommand),
#[clap(subcommand)]
+ /// A daemon to be run by the service provider, on a server
Provider(ProviderCommand),
+
+ #[clap(subcommand)]
+ /// Specific tooling, should not be part of a normal workflow, for debug & experimenting only
+ Tools(ToolsCommand),
//Test,
}
#[derive(Subcommand, Debug)]
+enum ToolsCommand {
+ /// Manage crypto roots
+ #[clap(subcommand)]
+ CryptoRoot(CryptoRootCommand),
+}
+
+#[derive(Subcommand, Debug)]
+enum CryptoRootCommand {
+ /// Generate a new crypto-root protected with a password
+ New {
+ #[clap(env = "AEROGRAMME_PASSWORD")]
+ maybe_password: Option<String>,
+ },
+ /// Generate a new clear text crypto-root, store it securely!
+ NewClearText,
+ /// Change the password of a crypto key
+ ChangePassword {
+ #[clap(env = "AEROGRAMME_OLD_PASSWORD")]
+ maybe_old_password: Option<String>,
+
+ #[clap(env = "AEROGRAMME_NEW_PASSWORD")]
+ maybe_new_password: Option<String>,
+
+ #[clap(short, long, env = "AEROGRAMME_CRYPTO_ROOT")]
+ crypto_root: String,
+ },
+ /// From a given crypto-key, derive one containing only the public key
+ DeriveIncoming {
+ #[clap(short, long, env = "AEROGRAMME_CRYPTO_ROOT")]
+ crypto_root: String,
+ },
+}
+
+#[derive(Subcommand, Debug)]
enum CompanionCommand {
/// Runs the IMAP proxy
Daemon,
@@ -81,6 +121,12 @@ enum AccountManagement {
},
/// Change password for a given account
ChangePassword {
+ #[clap(env = "AEROGRAMME_OLD_PASSWORD")]
+ maybe_old_password: Option<String>,
+
+ #[clap(env = "AEROGRAMME_NEW_PASSWORD")]
+ maybe_new_password: Option<String>,
+
#[clap(short, long)]
login: String
},
@@ -110,7 +156,7 @@ async fn main() -> Result<()> {
let server = Server::from_companion_config(config).await?;
server.run().await?;
},
- CompanionCommand::Reload { pid } => {
+ CompanionCommand::Reload { pid: _pid } => {
unimplemented!();
},
CompanionCommand::Wizard => {
@@ -143,18 +189,72 @@ async fn main() -> Result<()> {
(Command::Companion(_), AnyConfig::Provider(_)) => {
panic!("Your want to run a 'Companion' command but your configuration file has role 'Provider'.");
},
+ (Command::Tools(subcommand), _) => match subcommand {
+ ToolsCommand::CryptoRoot(crcommand) => {
+ match crcommand {
+ CryptoRootCommand::New { maybe_password } => {
+ let password = match maybe_password {
+ Some(pwd) => pwd.clone(),
+ None => {
+ let password = rpassword::prompt_password("Enter password: ")?;
+ let password_confirm = rpassword::prompt_password("Confirm password: ")?;
+ if password != password_confirm {
+ bail!("Passwords don't match.");
+ }
+ password
+ }
+ };
+ let crypto_keys = CryptoKeys::init();
+ let cr = CryptoRoot::create_pass(&password, &crypto_keys)?;
+ println!("{}", cr.0);
+ },
+ CryptoRootCommand::NewClearText => {
+ let crypto_keys = CryptoKeys::init();
+ let cr = CryptoRoot::create_cleartext(&crypto_keys);
+ println!("{}", cr.0);
+ },
+ CryptoRootCommand::ChangePassword { maybe_old_password, maybe_new_password, crypto_root } => {
+ let old_password = match maybe_old_password {
+ Some(pwd) => pwd.to_string(),
+ None => rpassword::prompt_password("Enter old password: ")?,
+ };
+
+ let new_password = match maybe_new_password {
+ Some(pwd) => pwd.to_string(),
+ None => {
+ let password = rpassword::prompt_password("Enter new password: ")?;
+ let password_confirm = rpassword::prompt_password("Confirm new password: ")?;
+ if password != password_confirm {
+ bail!("Passwords don't match.");
+ }
+ password
+ }
+ };
+
+ let keys = CryptoRoot(crypto_root.to_string()).crypto_keys(&old_password)?;
+ let cr = CryptoRoot::create_pass(&new_password, &keys)?;
+ println!("{}", cr.0);
+ },
+ CryptoRootCommand::DeriveIncoming { crypto_root } => {
+ let pubkey = CryptoRoot(crypto_root.to_string()).public_key()?;
+ let cr = CryptoRoot::create_incoming(&pubkey);
+ println!("{}", cr.0);
+ },
+ }
+ },
+ }
}
Ok(())
}
fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -> Result<()> {
- let mut ulist: UserList = read_config(users.clone())?;
+ let mut ulist: UserList = read_config(users.clone()).context(format!("'{:?}' must be a user database", users))?;
match cmd {
AccountManagement::Add { login, setup } => {
tracing::debug!(user=login, "will-create");
- let stp: SetupEntry = read_config(setup.clone())?;
+ let stp: SetupEntry = read_config(setup.clone()).context(format!("'{:?}' must be a setup file", setup))?;
tracing::debug!(user=login, "loaded setup entry");
let password = match stp.clear_password {
@@ -173,6 +273,7 @@ fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -
let crypto_root = match root {
Command::Provider(_) => CryptoRoot::create_pass(&password, &crypto_keys)?,
Command::Companion(_) => CryptoRoot::create_cleartext(&crypto_keys),
+ _ => unreachable!(),
};
let hash = hash_password(password.as_str()).context("unable to hash password")?;
@@ -191,8 +292,39 @@ fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -
ulist.remove(login);
write_config(users.clone(), &ulist)?;
},
- AccountManagement::ChangePassword { login } => {
- unimplemented!();
+ AccountManagement::ChangePassword { maybe_old_password, maybe_new_password, login } => {
+ let mut user = ulist.remove(login).context("user must exist first")?;
+
+ let old_password = match maybe_old_password {
+ Some(pwd) => pwd.to_string(),
+ None => rpassword::prompt_password("Enter old password: ")?,
+ };
+
+ if !verify_password(&old_password, &user.password)? {
+ bail!(format!("invalid password for login {}", login));
+ }
+
+ let crypto_keys = CryptoRoot(user.crypto_root).crypto_keys(&old_password)?;
+
+ let new_password = match maybe_new_password {
+ Some(pwd) => pwd.to_string(),
+ None => {
+ let password = rpassword::prompt_password("Enter new password: ")?;
+ let password_confirm = rpassword::prompt_password("Confirm new password: ")?;
+ if password != password_confirm {
+ bail!("Passwords don't match.");
+ }
+ password
+ }
+ };
+ let new_hash = hash_password(&new_password)?;
+ let new_crypto_root = CryptoRoot::create_pass(&new_password, &crypto_keys)?;
+
+ user.password = new_hash;
+ user.crypto_root = new_crypto_root.0;
+
+ ulist.insert(login.clone(), user);
+ write_config(users.clone(), &ulist)?;
},
};