summaryrefslogtreecommitdiff
path: root/src/passwords.rs
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-04-09 11:54:19 +0300
committerLars Wirzenius <liw@liw.fi>2021-04-09 17:17:35 +0300
commitd0b0245edbb2f6ed8285358d83b98f3334bf1b12 (patch)
tree988f72832b8a5015f4dbfb49473eba022d089666 /src/passwords.rs
parent2d6c1c81bfb1c0be8dfaced1c70e825e46c66430 (diff)
downloadobnam2-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.rs86
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),
+}