CLI tools
This commit is contained in:
parent
064a1077c8
commit
29561dde41
4 changed files with 146 additions and 14 deletions
|
@ -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<()> {
|
pub fn write_config<T: Serialize>(config_file: PathBuf, config: &T) -> Result<()> {
|
||||||
let mut file = std::fs::OpenOptions::new()
|
let mut file = std::fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
.open(config_file.as_path())?;
|
.open(config_file.as_path())?;
|
||||||
|
|
||||||
file.write_all(toml::to_string(config)?.as_bytes())?;
|
file.write_all(toml::to_string(config)?.as_bytes())?;
|
||||||
|
|
|
@ -109,16 +109,13 @@ impl CryptoRoot {
|
||||||
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
||||||
[ "aero", "cryptoroot", "pass", b64blob ] => {
|
[ "aero", "cryptoroot", "pass", b64blob ] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
if blob.len() < 32 {
|
CryptoKeys::password_open(password, &blob)
|
||||||
bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len());
|
|
||||||
}
|
|
||||||
CryptoKeys::password_open(password, &blob[32..])
|
|
||||||
},
|
},
|
||||||
[ "aero", "cryptoroot", "cleartext", b64blob ] => {
|
[ "aero", "cryptoroot", "cleartext", b64blob ] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
CryptoKeys::deserialize(&blob)
|
CryptoKeys::deserialize(&blob)
|
||||||
},
|
},
|
||||||
[ "aero", "cryptoroot", "incoming", b64blob ] => {
|
[ "aero", "cryptoroot", "incoming", _ ] => {
|
||||||
bail!("incoming cryptoroot does not contain a crypto key!")
|
bail!("incoming cryptoroot does not contain a crypto key!")
|
||||||
},
|
},
|
||||||
[ "aero", "cryptoroot", "keyring", _ ] =>{
|
[ "aero", "cryptoroot", "keyring", _ ] =>{
|
||||||
|
@ -184,8 +181,9 @@ impl CryptoKeys {
|
||||||
|
|
||||||
// Password sealed keys serialize/deserialize
|
// Password sealed keys serialize/deserialize
|
||||||
pub fn password_open(password: &str, blob: &[u8]) -> Result<Self> {
|
pub fn password_open(password: &str, blob: &[u8]) -> Result<Self> {
|
||||||
let kdf_salt = &blob[0..32];
|
let _pubkey = &blob[0..32];
|
||||||
let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[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)?;
|
let keys = Self::deserialize(&password_openned)?;
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
|
|
|
@ -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)?;
|
let keys = cr.crypto_keys(password)?;
|
||||||
|
|
||||||
tracing::debug!(user=%username, "logged");
|
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()?;
|
let public_key = cr.public_key()?;
|
||||||
|
|
||||||
Ok(PublicCredentials {
|
Ok(PublicCredentials {
|
||||||
|
|
142
src/main.rs
142
src/main.rs
|
@ -34,13 +34,53 @@ struct Args {
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum Command {
|
enum Command {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
|
/// A daemon to be run by the end user, on a personal device
|
||||||
Companion(CompanionCommand),
|
Companion(CompanionCommand),
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
|
/// A daemon to be run by the service provider, on a server
|
||||||
Provider(ProviderCommand),
|
Provider(ProviderCommand),
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
/// Specific tooling, should not be part of a normal workflow, for debug & experimenting only
|
||||||
|
Tools(ToolsCommand),
|
||||||
//Test,
|
//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)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum CompanionCommand {
|
enum CompanionCommand {
|
||||||
/// Runs the IMAP proxy
|
/// Runs the IMAP proxy
|
||||||
|
@ -81,6 +121,12 @@ enum AccountManagement {
|
||||||
},
|
},
|
||||||
/// Change password for a given account
|
/// Change password for a given account
|
||||||
ChangePassword {
|
ChangePassword {
|
||||||
|
#[clap(env = "AEROGRAMME_OLD_PASSWORD")]
|
||||||
|
maybe_old_password: Option<String>,
|
||||||
|
|
||||||
|
#[clap(env = "AEROGRAMME_NEW_PASSWORD")]
|
||||||
|
maybe_new_password: Option<String>,
|
||||||
|
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
login: String
|
login: String
|
||||||
},
|
},
|
||||||
|
@ -110,7 +156,7 @@ async fn main() -> Result<()> {
|
||||||
let server = Server::from_companion_config(config).await?;
|
let server = Server::from_companion_config(config).await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
},
|
},
|
||||||
CompanionCommand::Reload { pid } => {
|
CompanionCommand::Reload { pid: _pid } => {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
},
|
},
|
||||||
CompanionCommand::Wizard => {
|
CompanionCommand::Wizard => {
|
||||||
|
@ -143,18 +189,72 @@ async fn main() -> Result<()> {
|
||||||
(Command::Companion(_), AnyConfig::Provider(_)) => {
|
(Command::Companion(_), AnyConfig::Provider(_)) => {
|
||||||
panic!("Your want to run a 'Companion' command but your configuration file has role '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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -> Result<()> {
|
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 {
|
match cmd {
|
||||||
AccountManagement::Add { login, setup } => {
|
AccountManagement::Add { login, setup } => {
|
||||||
tracing::debug!(user=login, "will-create");
|
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");
|
tracing::debug!(user=login, "loaded setup entry");
|
||||||
|
|
||||||
let password = match stp.clear_password {
|
let password = match stp.clear_password {
|
||||||
|
@ -173,6 +273,7 @@ fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -
|
||||||
let crypto_root = match root {
|
let crypto_root = match root {
|
||||||
Command::Provider(_) => CryptoRoot::create_pass(&password, &crypto_keys)?,
|
Command::Provider(_) => CryptoRoot::create_pass(&password, &crypto_keys)?,
|
||||||
Command::Companion(_) => CryptoRoot::create_cleartext(&crypto_keys),
|
Command::Companion(_) => CryptoRoot::create_cleartext(&crypto_keys),
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let hash = hash_password(password.as_str()).context("unable to hash password")?;
|
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);
|
ulist.remove(login);
|
||||||
write_config(users.clone(), &ulist)?;
|
write_config(users.clone(), &ulist)?;
|
||||||
},
|
},
|
||||||
AccountManagement::ChangePassword { login } => {
|
AccountManagement::ChangePassword { maybe_old_password, maybe_new_password, login } => {
|
||||||
unimplemented!();
|
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)?;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue