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(config_file: PathBuf) -> Resu pub fn write_config(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::>()[..] { [ "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 { - 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,13 +34,53 @@ 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, + }, + /// 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, + + #[clap(env = "AEROGRAMME_NEW_PASSWORD")] + maybe_new_password: Option, + + #[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 @@ -81,6 +121,12 @@ enum AccountManagement { }, /// Change password for a given account ChangePassword { + #[clap(env = "AEROGRAMME_OLD_PASSWORD")] + maybe_old_password: Option, + + #[clap(env = "AEROGRAMME_NEW_PASSWORD")] + maybe_new_password: Option, + #[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)?; }, };