From 653c5d682fb23a57cb39255da0164636ddefb7ab Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Thu, 17 Sep 2020 23:57:46 +0200 Subject: rust: Proper authentication data generation --- rust/src/auth.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 rust/src/auth.rs (limited to 'rust/src/auth.rs') diff --git a/rust/src/auth.rs b/rust/src/auth.rs new file mode 100644 index 0000000..51b4820 --- /dev/null +++ b/rust/src/auth.rs @@ -0,0 +1,84 @@ +// use users::get_current_username; +// use dirs::data_dir; +use std::borrow::Borrow; +use std::ops::Deref; +use std::{fs, io}; +use std::path::{Path, PathBuf}; +use rand::{Rng, CryptoRng}; +use crate::error::{IntoIOError, IntoIOResult}; + +pub struct User(String); +pub struct Pass(String); + +impl Borrow for User { fn borrow(&self) -> &str { &self.0 } } +impl Borrow for Pass { fn borrow(&self) -> &str { &self.0 } } + +impl Deref for User { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } +impl Deref for Pass { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } + +static PASSWORD_FILE_SUBDIR: &str = "tomsg_client"; // ~/.local/share/PASSWORD_FILE_SUBDIR/... +static PASSWORD_FILE_SUBDIR_HOME: &str = ".tomsg_client"; // ~/PASSWORD_FILE_SUBDIR_HOME/... +static PASSWORD_FILE_NAME: &str = "password.txt"; + +pub fn get_auth_info() -> io::Result<(User, Pass)> { + Ok((get_username()?, obtain_password()?)) +} + +fn get_username() -> io::Result { + match users::get_current_username().map(|u| u.to_str().map(|u| u.to_string())) { + Some(Some(u)) => Ok(User(u)), + Some(None) => Err("Username not valid UTF-8".ioerr()), + None => Err("Cannot obtain username".ioerr()), + } +} + +fn obtain_password() -> io::Result { + let dir = storage_directory()?; + if let Ok(pass) = read_password(&dir) { + return Ok(pass); + } + + let pass = generate_password(); + write_password(&dir, &pass) + .map_err(|e| format!("Cannot write password to home directory: {}", e).ioerr())?; + Ok(pass) +} + +fn read_password(data_dir: &Path) -> io::Result { + fs::read(data_dir.join(PASSWORD_FILE_NAME)) + .and_then(|mut v| { + // Trim final newline if there is one + if v.last() == Some(&b'\n') { + v.pop(); + } + String::from_utf8(v).map(Pass).iores() + }) +} + +fn write_password(data_dir: &Path, pass: &Pass) -> io::Result<()> { + fs::create_dir_all(data_dir)?; + fs::write(data_dir.join(PASSWORD_FILE_NAME), pass.as_bytes()) +} + +fn storage_directory() -> io::Result { + dirs::data_dir() + .map(|mut dir| { dir.push(PASSWORD_FILE_SUBDIR); dir }) + .or_else(|| dirs::home_dir() + .map(|mut dir| { + dir.push(PASSWORD_FILE_SUBDIR_HOME); dir + })) + .ok_or("Cannot determine home directory".ioerr()) +} + +fn generate_password() -> Pass { + Pass(secure_rng() + .sample_iter(rand::distributions::Alphanumeric) + .take(32) + .collect::() + ) +} + +// Wrapper for thread_rng() that statically enforces that the generator is CryptoRng. +fn secure_rng() -> impl Rng + CryptoRng { + rand::thread_rng() +} -- cgit v1.2.3-70-g09d2