diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-04-09 11:54:19 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-04-09 17:17:35 +0300 |
commit | d0b0245edbb2f6ed8285358d83b98f3334bf1b12 (patch) | |
tree | 988f72832b8a5015f4dbfb49473eba022d089666 /src/passwords.rs | |
parent | 2d6c1c81bfb1c0be8dfaced1c70e825e46c66430 (diff) | |
download | obnam2-d0b0245edbb2f6ed8285358d83b98f3334bf1b12.tar.gz |
feat: add "obnam init" subcommand
This reads a passphrase and derives two passwords from that, and
stores them next to the configuration file. The passwords aren't yet
used for anything, that will come later.
Diffstat (limited to 'src/passwords.rs')
-rw-r--r-- | src/passwords.rs | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/src/passwords.rs b/src/passwords.rs new file mode 100644 index 0000000..b8ca3f5 --- /dev/null +++ b/src/passwords.rs @@ -0,0 +1,86 @@ +use pbkdf2::{ + password_hash::{PasswordHasher, SaltString}, + Pbkdf2, +}; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use std::io::prelude::Write; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Passwords { + encryption: String, + mac: String, +} + +impl Passwords { + pub fn new(passphrase: &str) -> Self { + Self { + encryption: derive_password(passphrase), + mac: derive_password(passphrase), + } + } + + pub fn load(filename: &Path) -> Result<Self, PasswordError> { + let data = std::fs::read(filename) + .map_err(|err| PasswordError::Read(filename.to_path_buf(), err))?; + serde_yaml::from_slice(&data) + .map_err(|err| PasswordError::Parse(filename.to_path_buf(), err)) + } + + pub fn save(&self, filename: &Path) -> Result<(), PasswordError> { + eprintln!("saving passwords to {:?}", filename); + + let data = serde_yaml::to_string(&self).map_err(PasswordError::Serialize)?; + + let mut file = std::fs::File::create(filename) + .map_err(|err| PasswordError::Write(filename.to_path_buf(), err))?; + let metadata = file + .metadata() + .map_err(|err| PasswordError::Write(filename.to_path_buf(), err))?; + let mut permissions = metadata.permissions(); + + // Make readadable by owner only. We still have the open file + // handle, so we can write the content. + permissions.set_mode(0o400); + std::fs::set_permissions(filename, permissions) + .map_err(|err| PasswordError::Write(filename.to_path_buf(), err))?; + + // Write actual content. + file.write_all(data.as_bytes()) + .map_err(|err| PasswordError::Write(filename.to_path_buf(), err))?; + + Ok(()) + } +} + +pub fn passwords_filename(config_filename: &Path) -> PathBuf { + let mut filename = config_filename.to_path_buf(); + filename.set_file_name("passwords.yaml"); + filename +} + +fn derive_password(passphrase: &str) -> String { + let salt = SaltString::generate(&mut OsRng); + + Pbkdf2 + .hash_password_simple(passphrase.as_bytes(), salt.as_ref()) + .unwrap() + .to_string() +} + +#[derive(Debug, thiserror::Error)] +pub enum PasswordError { + #[error("failed to serialize passwords for saving: {0}")] + Serialize(serde_yaml::Error), + + #[error("failed to save passwords to {0}: {1}")] + Write(PathBuf, std::io::Error), + + #[error("failed to read passwords from {0}: {1}")] + Read(PathBuf, std::io::Error), + + #[error("failed to parse saved passwords from {0}: {1}")] + Parse(PathBuf, serde_yaml::Error), +} |