diff options
Diffstat (limited to 'src/datastructures.rs')
-rw-r--r-- | src/datastructures.rs | 195 |
1 files changed, 184 insertions, 11 deletions
diff --git a/src/datastructures.rs b/src/datastructures.rs index 2a19087..bb91cfb 100644 --- a/src/datastructures.rs +++ b/src/datastructures.rs @@ -19,16 +19,56 @@ */ use anyhow::Result; -use sha2::Digest; use std::borrow::Cow; use std::fs::read_to_string; use std::path::Path; use url::form_urlencoded; +use std::fmt::Formatter; +use rand::Rng; +use serde::{Serialize, Deserialize}; +use argon2::{ + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Argon2 +}; +use rand_core::OsRng; const DEFAULT_CONFIG_LOCATION: &str = "/etc/cgitrc"; const DEFAULT_COOKIE_TTL: u64 = 1200; const DEFAULT_DATABASE_LOCATION: &str = "/etc/cgit/auth.db"; pub const CACHE_DIR: &str = "/var/cache/cgit"; +pub type RandIntType = u32; +pub const MINIMUM_SECRET_LENGTH: usize = 8; + +pub fn get_current_timestamp() -> u64 { + let start = std::time::SystemTime::now(); + let since_the_epoch = start + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards"); + since_the_epoch.as_secs() +} + +pub fn rand_int() -> RandIntType { + let mut rng = rand::thread_rng(); + rng.gen() +} + +pub fn rand_str(len: usize) -> String { + const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789"; + let mut rng = rand::thread_rng(); + + let password: String = (0..len) + .map(|_| { + let idx = rng.gen_range(0, CHARSET.len()); + CHARSET[idx] as char + }) + .collect(); + + password +} + + #[derive(Debug, Clone)] pub struct Config { @@ -36,6 +76,7 @@ pub struct Config { database: String, //access_node: hashmap, pub bypass_root: bool, + //secret: String, } impl Default for Config { @@ -44,6 +85,7 @@ impl Default for Config { cookie_ttl: DEFAULT_COOKIE_TTL, database: DEFAULT_DATABASE_LOCATION.to_string(), bypass_root: false, + //secret: Default::default(), } } } @@ -58,6 +100,7 @@ impl Config { let mut cookie_ttl: u64 = DEFAULT_COOKIE_TTL; let mut database: &str = "/etc/cgit/auth.db"; let mut bypass_root: bool = false; + let mut secret: &str = ""; for line in file.lines() { let line = line.trim(); if !line.contains('=') || !line.starts_with("cgit-simple-auth-") { @@ -74,6 +117,7 @@ impl Config { "cookie-ttl" => cookie_ttl = value.parse().unwrap_or(DEFAULT_COOKIE_TTL), "database" => database = value, "bypass-root" => bypass_root = value.to_lowercase().eq("true"), + "secret" => secret = value, _ => {} } } @@ -81,12 +125,23 @@ impl Config { cookie_ttl, database: database.to_string(), bypass_root, + //secret: secret.to_string(), } } pub fn get_database_location(&self) -> &str { self.database.as_str() } + +/* pub fn get_secret_warning(&self) -> &str { + if self.secret.is_empty() { + r#"<span color="red">Warning: You should specify secret in your cgitrc file.</span>"# + } else if self.secret.len() < MINIMUM_SECRET_LENGTH { + r#"<span color="yellow">Warning: You should set key length more than MINIMUM_SECRET_LENGTH.</span>"# + } else { + "" + } + }*/ } #[derive(Debug, Clone, Default)] @@ -103,10 +158,13 @@ impl FormData { } } - pub fn get_string_sha256_value(s: &str) -> Result<String> { - let mut hasher = sha2::Sha256::new(); - hasher.update(s.as_bytes()); - Ok(format!("{:x}", hasher.finalize())) + pub fn get_string_argon2_hash(s: &str) -> Result<String> { + let passwd = s.as_bytes(); + let salt = SaltString::generate(&mut OsRng); + + let argon2_alg = Argon2::default(); + + Ok(argon2_alg.hash_password_simple(passwd, salt.as_ref()).unwrap().to_string()) } pub fn set_password(&mut self, password: String) { @@ -114,6 +172,11 @@ impl FormData { self.hash = Default::default(); } + pub fn verify_password(&self, password_hash: &PasswordHash) -> bool { + let argon2_alg = Argon2::default(); + argon2_alg.verify_password(self.password.as_bytes(), password_hash).is_ok() + } + pub fn set_user(&mut self, user: String) { self.user = user } @@ -122,20 +185,20 @@ impl FormData { &self.user } - pub fn get_password_sha256(&self) -> Result<String> { - Self::get_string_sha256_value(&self.password) + pub fn get_password_argon2(&self) -> Result<String> { + Self::get_string_argon2_hash(&self.password) } #[allow(dead_code)] - pub fn get_password_sha256_cache(&mut self) -> Result<String> { + pub fn get_password_argon2_cache(&mut self) -> Result<String> { if self.hash.is_empty() { - self.hash = self.get_password_sha256()?; + self.hash = self.get_password_argon2()?; } Ok(self.hash.clone()) } #[allow(dead_code)] - pub fn get_sha256_without_calc(&self) -> &String { + pub fn get_argon2_without_calc(&self) -> &String { &self.hash } } @@ -151,7 +214,6 @@ impl From<&[u8]> for FormData { } Cow::Borrowed("password") => { data.set_password(f.1.to_string()); - data.get_password_sha256_cache().unwrap(); } _ => {} } @@ -171,3 +233,114 @@ impl From<String> for FormData { Self::from(&s) } } + +#[derive(Serialize, Deserialize)] +struct IvFile { + iv: String, + timestamp: u64, +} + +pub struct Cookie { + timestamp: u64, + randint: RandIntType, + body: String, +} + +impl Cookie { + fn new(randint: RandIntType, body: &String) -> Self { + Self { + timestamp: get_current_timestamp(), + randint, + body: body.clone() + } + } + + pub fn load_from_request(cookies: &str) -> Result<Option<Self>> { + let mut cookie_self = None; + for cookie in cookies.split(';').map(|x| x.trim()) { + let (key, value) = cookie.split_once('=').unwrap(); + if key.eq("cgit_auth") { + let value = base64::decode(value).unwrap_or_default(); + let value = std::str::from_utf8(&value).unwrap_or(""); + + if !value.contains(';') { + break; + } + + let (key, value) = value.split_once(';').unwrap(); + + let (timestamp, randint) = key.split_once("_").unwrap_or(("0", "")); + + cookie_self = Some(Self{ + timestamp: timestamp.parse()?, + randint: randint.parse()?, + body: value.to_string(), + }); + break + } + } + Ok(cookie_self) + } + + pub fn eq_body(&self, s: &str) -> bool { + self.body.eq(s) + } + + pub fn get_key(&self) -> String { + format!("{}_{}", self.timestamp, self.randint) + } +} + + +impl std::fmt::Display for Cookie { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let s = format!("{}_{}; {}", self.timestamp, self.randint, self.body); + write!(f, "{}", base64::encode(s)) + } +} + +/* +pub struct CookieOrigin { + timestamp: u64, + randint: RandIntType, + user: String, + rand_str: String, +} + +impl CookieOrigin { + pub fn new(randint: RandIntType, user: &String, rand_str: &String) -> Self { + Self { + timestamp: get_current_timestamp(), + randint, + user: user.clone(), + rand_str: rand_str.clone(), + } + } + + pub async fn to_cookie(&self, cfg: &Config) -> Result<Cookie> { + let mut file = File::open(Path::new(CACHE_DIR).join(DEFAULT_IV_FILE_NAME)).await?; + let mut context = String::new(); + file.read_to_string(&mut context).await?; + let iv_file: IvFile = serde_json::from_str(&context)?; + let key = cfg.secret.to_hex(); + let iv = iv_file.iv.to_hex(); + + let mut cipher = Blowfish::new(key); + + cipher.write_all().awa + + let mut buffer = [0u8, 128]; + + let plain_text = format!("{}, {}", self.user, self.rand_str); + + let pos = plain_text.len(); + + buffer[..pos].copy_from_slice(plain_text.as_bytes()); + + let b = Block::from_mut_slice(&mut buffer); + + + Ok(()) + } +} +*/
\ No newline at end of file |