From c5ed7b895caad6dc4e6d3398fbe3c154a102959c Mon Sep 17 00:00:00 2001
From: KunoiSayami <46131041+KunoiSayami@users.noreply.github.com>
Date: Mon, 10 May 2021 12:58:30 +0800
Subject: refactor: Use argon2 instead of sha256 to store password * style:
Update html file style
---
.github/workflows/build.yml | 3 -
Cargo.lock | 59 ++++++++++++-
Cargo.toml | 5 +-
src/authentication_page.html | 10 +--
src/datastructures.rs | 195 ++++++++++++++++++++++++++++++++++++++++---
src/main.rs | 131 ++++++++++++++---------------
6 files changed, 312 insertions(+), 91 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ae8f4a0..ef5e2be 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,9 +39,6 @@ jobs:
elif [ "$RUNNER_OS" == "macOS" ]; then
BIN='cgit-simple-authentication_darwin_amd64'
mv target/release/cgit-simple-authentication target/release/$BIN
- else
- BIN='cgit-simple-authentication_windows_amd64.exe'
- mv target/release/cgit-simple-authentication.exe target/release/$BIN
fi
echo "::set-output name=bin::target/release/$BIN"
- uses: actions/upload-artifact@v2
diff --git a/Cargo.lock b/Cargo.lock
index 1cf83ae..ceed4a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -59,6 +59,16 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8"
+[[package]]
+name = "argon2"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fa6ffe98a5aacd627ea719b7295646e6c457ff78bc87dff0a8d1e1a00c80557"
+dependencies = [
+ "blake2",
+ "password-hash",
+]
+
[[package]]
name = "arrayvec"
version = "0.5.2"
@@ -226,6 +236,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+[[package]]
+name = "base64ct"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0d27fb6b6f1e43147af148af49d49329413ba781aa0d5e10979831c210173b5"
+
[[package]]
name = "bitflags"
version = "1.2.1"
@@ -244,6 +260,17 @@ dependencies = [
"wyz",
]
+[[package]]
+name = "blake2"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
[[package]]
name = "block-buffer"
version = "0.7.3"
@@ -350,9 +377,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cgit-simple-authentication"
-version = "0.1.5"
+version = "0.2.0"
dependencies = [
"anyhow",
+ "argon2",
"base64",
"clap",
"env_logger",
@@ -361,11 +389,11 @@ dependencies = [
"log4rs",
"openssl",
"rand 0.7.3",
+ "rand_core 0.6.2",
"redis",
"serde",
"serde_derive",
"serde_json",
- "sha2",
"sqlx",
"tokio 1.5.0",
"tokio-stream",
@@ -489,6 +517,16 @@ dependencies = [
"lazy_static",
]
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle",
+]
+
[[package]]
name = "ctor"
version = "0.1.20"
@@ -1287,6 +1325,17 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "password-hash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1a5d4e9c205d2c1ae73b84aab6240e98218c0e72e63b50422cfb2d1ca952282"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.2",
+ "subtle",
+]
+
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -1845,6 +1894,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
[[package]]
name = "syn"
version = "1.0.72"
diff --git a/Cargo.toml b/Cargo.toml
index aaa7f41..f16bad0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cgit-simple-authentication"
-version = "0.1.5"
+version = "0.2.0"
authors = ["KunoiSayami <46131041+KunoiSayami@users.noreply.github.com>"]
edition = "2018"
@@ -19,10 +19,11 @@ clap = "2.33"
handlebars = "3.4"
url = "2.1"
redis = { version = "0.17", features = ["tokio-comp"] }
-sha2 = "0.9"
base64 = "0.13"
log4rs = "1"
tokio-stream = "0.1"
+argon2 = "0.2"
+rand_core = "0.6"
[target.aarch64-unknown-linux-musl.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
\ No newline at end of file
diff --git a/src/authentication_page.html b/src/authentication_page.html
index 73ec85a..c62dce0 100644
--- a/src/authentication_page.html
+++ b/src/authentication_page.html
@@ -1,9 +1,9 @@
Authentication Required
-
\ No newline at end of file
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#"Warning: You should specify secret in your cgitrc file."#
+ } else if self.secret.len() < MINIMUM_SECRET_LENGTH {
+ r#"Warning: You should set key length more than MINIMUM_SECRET_LENGTH."#
+ } else {
+ ""
+ }
+ }*/
}
#[derive(Debug, Clone, Default)]
@@ -103,10 +158,13 @@ impl FormData {
}
}
- pub fn get_string_sha256_value(s: &str) -> Result {
- 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 {
+ 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 {
- Self::get_string_sha256_value(&self.password)
+ pub fn get_password_argon2(&self) -> Result {
+ Self::get_string_argon2_hash(&self.password)
}
#[allow(dead_code)]
- pub fn get_password_sha256_cache(&mut self) -> Result {
+ pub fn get_password_argon2_cache(&mut self) -> Result {
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 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