From 6a5c58ee79fc9b4714784ef136a377bc71e6d01d Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 18:56:23 +0700 Subject: Add support for hashed password (sha256) --- Cargo.lock | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/args.rs | 53 ++++++++++++++++---------------- src/auth.rs | 34 +++++++++++++++++++-- src/main.rs | 2 +- stale_outputs_checked | 0 6 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 stale_outputs_checked diff --git a/Cargo.lock b/Cargo.lock index c9c3406..b16b8ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,25 @@ dependencies = [ "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "brotli-sys" version = "0.3.2" @@ -319,6 +338,11 @@ name = "build_const" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.1" @@ -517,6 +541,14 @@ name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "digest" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dirs" version = "1.0.5" @@ -652,6 +684,11 @@ dependencies = [ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "filetime" version = "0.2.4" @@ -741,6 +778,14 @@ dependencies = [ "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "generic-array" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "globset" version = "0.4.3" @@ -787,6 +832,11 @@ dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hostname" version = "0.1.5" @@ -1106,6 +1156,7 @@ dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1117,6 +1168,7 @@ dependencies = [ "rexpect 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "select 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1283,6 +1335,11 @@ dependencies = [ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "opaque-debug" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "openssl" version = "0.10.20" @@ -1875,6 +1932,17 @@ name = "sha1" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "sha2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "signal-hook" version = "0.1.8" @@ -2417,6 +2485,11 @@ dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "typenum" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ucd-util" version = "0.1.3" @@ -2692,10 +2765,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d75255892aeb580d3c566f213a2b6fdc1c66667839f45719ee1d30ebf2aea591" "checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" "checksum brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" "checksum bstr 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6c8203ca06c502958719dae5f653a79e0cc6ba808ed02beffbf27d09610f2143" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum bytesize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "716960a18f978640f25101b5cbf1c6f6b0d3192fab36a2d98ca96f0ecbe41010" @@ -2719,6 +2795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" "checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" @@ -2735,6 +2812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum escargot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ceb9adbf9874d5d028b5e4c5739d22b71988252b25c9c98fe7cf9738bee84597" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646" "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" "checksum float-cmp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "134a8fa843d80a51a5b77d36d42bc2def9edcb0262c914861d08129fd1926600" @@ -2747,10 +2825,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" "checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +"checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum globset 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef4feaabe24a0a658fd9cf4a9acf6ed284f045c77df0f49020ba3245cfb7b454" "checksum globwalk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "89fa2e29859da05acd066bd45996f05c271b271d7ec4a781f909682328f65d25" "checksum h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "85ab6286db06040ddefb71641b50017c06874614001a134b423783e2db2920bd" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" "checksum html5ever 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a49d5001dd1bddf042ea41ed4e0a671d50b1bf187e66b349d7ec613bdce4ad90" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" @@ -2804,6 +2884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" +"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum openssl 0.10.20 (registry+https://github.com/rust-lang/crates.io-index)" = "5a0d6b781aac4ac1bd6cafe2a2f0ad8c16ae8e1dd5184822a16c50139f8838d9" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.43 (registry+https://github.com/rust-lang/crates.io-index)" = "33c86834957dd5b915623e94f2f4ab2c70dd8f6b70679824155d5ae21dbd495d" @@ -2871,6 +2952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d48f9f99cd749a2de71d29da5f948de7f2764cc5a9d7f3c97e3514d4ee6eabf2" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum signal-hook 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "97a47ae722318beceb0294e6f3d601205a1e6abaa4437d9d33e3a212233e3021" "checksum simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e95345f185d5adeb8ec93459d2dc99654e294cc6ccf5b75414d8ea262de9a13" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" @@ -2922,6 +3004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum trust-dns-resolver 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8a9f877f7a1ad821ab350505e1f1b146a4960402991787191d6d8cab2ce2de2c" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" "checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" "checksum unicase 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41d17211f887da8e4a70a45b9536f26fc5de166b81e2d5d80de4a17fd22553bd" diff --git a/Cargo.toml b/Cargo.toml index 7772233..7dc6052 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,8 @@ failure = "0.1.5" log = "0.4.6" strum = "0.15.0" strum_macros = "0.15.0" +sha2 = "0.8.0" +hex = "0.3.2" [dev-dependencies] assert_cmd = "0.11" diff --git a/src/args.rs b/src/args.rs index 825a4ac..404225a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -39,7 +39,7 @@ struct CLIArgs { /// Set authentication (username:password) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] - auth: Option<(String, String)>, + auth: Option, /// Generate a random 6-hexdigit route #[structopt(long = "random-route")] @@ -76,36 +76,42 @@ fn parse_interface(src: &str) -> Result { } /// Checks wether the auth string is valid, i.e. it follows the syntax username:password -fn parse_auth(src: &str) -> Result<(String, String), String> { +fn parse_auth(src: &str) -> Result { let mut split = src.splitn(2, ':'); + let errmsg = "Invalid credentials string, expected format is username:password".to_owned(); let username = match split.next() { Some(username) => username, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - let password = match split.next() { + let second_part = match split.next() { // This allows empty passwords, as the spec does not forbid it Some(password) => password, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - // To make it Windows-compatible, the password needs to be shorter than 255 characters. - // After 255 characters, Windows will truncate the value. - // As for the username, the spec does not mention a limit in length - if password.len() > 255 { - return Err("Password length cannot exceed 255 characters".to_owned()); - } + let password = match split.next() { + Some(hash) => match second_part { + "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), + _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) + }, + None => { + // To make it Windows-compatible, the password needs to be shorter than 255 characters. + // After 255 characters, Windows will truncate the value. + // As for the username, the spec does not mention a limit in length + if second_part.len() > 255 { + return Err("Password length cannot exceed 255 characters".to_owned()); + } + + auth::RequiredAuthPassword::Plain(second_part.to_owned()) + }, + }; - Ok((username.to_owned(), password.to_owned())) + Ok(auth::RequiredAuth { + username: username.to_owned(), + password, + }) } /// Parses the command line arguments @@ -121,11 +127,6 @@ pub fn parse_args() -> crate::MiniserveConfig { ] }; - let auth = match args.auth { - Some((username, password)) => Some(auth::BasicAuthParams { username, password }), - None => None, - }; - let random_route = if args.random_route { Some(nanoid::custom(6, &ROUTE_ALPHABET)) } else { @@ -141,7 +142,7 @@ pub fn parse_args() -> crate::MiniserveConfig { path: args.path.unwrap_or_else(|| PathBuf::from(".")), port: args.port, interfaces, - auth, + auth: args.auth, path_explicitly_chosen, no_symlinks: args.no_symlinks, random_route, diff --git a/src/auth.rs b/src/auth.rs index 10e7a4a..6aed8cf 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; +use sha2::{Sha256, Digest}; pub struct Auth; @@ -16,6 +17,19 @@ pub struct BasicAuthParams { pub password: String, } +#[derive(Clone, Debug)] +pub enum RequiredAuthPassword { + Plain(String), + Sha256(String), +} + +#[derive(Clone, Debug)] +/// Authentication structure to match BasicAuthParams against +pub struct RequiredAuth { + pub username: String, + pub password: RequiredAuthPassword, +} + /// Decode a HTTP basic auth string into a tuple of username and password. pub fn parse_basic_auth( authorization_header: &header::HeaderValue, @@ -34,6 +48,22 @@ pub fn parse_basic_auth( }) } +pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> bool { + if basic_auth.username != required_auth.username { + return false; + } + + match &required_auth.password { + RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, + RequiredAuthPassword::Sha256(password_hash) => { + let mut hasher = Sha256::new(); + hasher.input(basic_auth.password); + let received_hash = hex::encode(hasher.result()); + received_hash == *password_hash + } + } +} + impl Middleware for Auth { fn response( &self, @@ -51,9 +81,7 @@ impl Middleware for Auth { )))); } }; - if auth_req.username != required_auth.username - || auth_req.password != required_auth.password - { + if match_auth(auth_req, required_auth) { let new_resp = HttpResponse::Unauthorized().finish(); return Ok(Response::Done(new_resp)); } diff --git a/src/main.rs b/src/main.rs index 42a43b5..96e05c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ pub struct MiniserveConfig { pub interfaces: Vec, /// Enable HTTP basic authentication - pub auth: Option, + pub auth: Option, /// If false, miniserve will serve the current working directory pub path_explicitly_chosen: bool, diff --git a/stale_outputs_checked b/stale_outputs_checked new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From 090958451244c54fa0abe5791a12bedc26674c41 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 19:36:59 +0700 Subject: Add support for sha-512 --- src/args.rs | 1 + src/auth.rs | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/args.rs b/src/args.rs index 404225a..9c96fd7 100644 --- a/src/args.rs +++ b/src/args.rs @@ -94,6 +94,7 @@ fn parse_auth(src: &str) -> Result { let password = match split.next() { Some(hash) => match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), + "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) }, None => { diff --git a/src/auth.rs b/src/auth.rs index 6aed8cf..94a2fda 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; -use sha2::{Sha256, Digest}; +use sha2::{Sha256, Sha512, Digest}; pub struct Auth; @@ -21,6 +21,7 @@ pub struct BasicAuthParams { pub enum RequiredAuthPassword { Plain(String), Sha256(String), + Sha512(String), } #[derive(Clone, Debug)] @@ -55,15 +56,18 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> match &required_auth.password { RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, - RequiredAuthPassword::Sha256(password_hash) => { - let mut hasher = Sha256::new(); - hasher.input(basic_auth.password); - let received_hash = hex::encode(hasher.result()); - received_hash == *password_hash - } + RequiredAuthPassword::Sha256(password_hash) => compare_hash::(basic_auth.password, password_hash), + RequiredAuthPassword::Sha512(password_hash) => compare_hash::(basic_auth.password, password_hash), } } +pub fn compare_hash(password: String, hash: &String) -> bool { + let mut hasher = T::new(); + hasher.input(password); + let received_hash = hex::encode(hasher.result()); + received_hash == *hash +} + impl Middleware for Auth { fn response( &self, -- cgit v1.2.3 From 4c0ac05831ddc6933647a12cdbb56d9d8523b681 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 20:16:48 +0700 Subject: Create get_hash_hex --- src/auth.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 94a2fda..48e36ce 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -62,10 +62,13 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } pub fn compare_hash(password: String, hash: &String) -> bool { + get_hash_hex::(password) == *hash +} + +fn get_hash_hex(text: String) -> String { let mut hasher = T::new(); - hasher.input(password); - let received_hash = hex::encode(hasher.result()); - received_hash == *hash + hasher.input(text); + hex::encode(hasher.result()) } impl Middleware for Auth { -- cgit v1.2.3 From ad2ad76e6b4e285e0d33d14c556d8d60137b9d23 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 20:30:17 +0700 Subject: Fix false positive --- src/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth.rs b/src/auth.rs index 48e36ce..c53d26a 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -88,7 +88,7 @@ impl Middleware for Auth { )))); } }; - if match_auth(auth_req, required_auth) { + if !match_auth(auth_req, required_auth) { let new_resp = HttpResponse::Unauthorized().finish(); return Ok(Response::Done(new_resp)); } -- cgit v1.2.3 From 57d923329adaae200c5595e3dcb04e207460d59c Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 21:13:38 +0700 Subject: Fix parse_auth and add some tests --- src/args.rs | 46 +++++++++++++++++++++++++++++- src/auth.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/src/args.rs b/src/args.rs index 9c96fd7..c7a4917 100644 --- a/src/args.rs +++ b/src/args.rs @@ -77,7 +77,7 @@ fn parse_interface(src: &str) -> Result { /// Checks wether the auth string is valid, i.e. it follows the syntax username:password fn parse_auth(src: &str) -> Result { - let mut split = src.splitn(2, ':'); + let mut split = src.splitn(3, ':'); let errmsg = "Invalid credentials string, expected format is username:password".to_owned(); let username = match split.next() { @@ -152,3 +152,47 @@ pub fn parse_args() -> crate::MiniserveConfig { file_upload: args.file_upload, } } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_required_auth (username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { + use auth::*; + use RequiredAuthPassword::*; + + RequiredAuth { + username: username.to_owned(), + password: match encrypt { + "plain" => Plain(password.to_owned()), + "sha256" => Sha256(password.to_owned()), + "sha512" => Sha512(password.to_owned()), + _ => panic!("Unknown encryption type") + }, + } + } + + #[test] + fn parse_auth_plain() { + assert_eq!( + parse_auth("username:password").unwrap(), + create_required_auth("username", "password", "plain") + ); + } + + #[test] + fn parse_auth_sha256() { + assert_eq!( + parse_auth("username:sha256:hash").unwrap(), + create_required_auth("username", "hash", "sha256") + ); + } + + #[test] + fn parse_auth_sha512() { + assert_eq!( + parse_auth("username:sha512:hash").unwrap(), + create_required_auth("username", "hash", "sha512") + ); + } +} \ No newline at end of file diff --git a/src/auth.rs b/src/auth.rs index c53d26a..e96a0ce 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -17,14 +17,14 @@ pub struct BasicAuthParams { pub password: String, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum RequiredAuthPassword { Plain(String), Sha256(String), Sha512(String), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] /// Authentication structure to match BasicAuthParams against pub struct RequiredAuth { pub username: String, @@ -65,7 +65,7 @@ pub fn compare_hash(password: String, hash: &String) -> bool { get_hash_hex::(password) == *hash } -fn get_hash_hex(text: String) -> String { +pub fn get_hash_hex(text: String) -> String { let mut hasher = T::new(); hasher.input(text); hex::encode(hasher.result()) @@ -105,3 +105,91 @@ impl Middleware for Auth { Ok(Response::Done(resp)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_hash_hex_sha256() { + let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned(); + let received = get_hash_hex::("abc".to_owned()); + assert_eq!(expectation, received); + } + + #[test] + fn get_hash_hex_sha512() { + let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f".to_owned(); + let received = get_hash_hex::("abc".to_owned()); + assert_eq!(expectation, received); + } + + fn create_auth_params (username: &str, password: &str) -> BasicAuthParams { + BasicAuthParams { + username: username.to_owned(), + password: password.to_owned(), + } + } + + fn create_required_auth (username: &str, password: &str, encrypt: &str) -> RequiredAuth { + use RequiredAuthPassword::*; + + RequiredAuth { + username: username.to_owned(), + password: match encrypt { + "plain" => Plain(password.to_owned()), + "sha256" => Sha256(get_hash_hex::(password.to_owned())), + "sha512" => Sha512(get_hash_hex::(password.to_owned())), + _ => panic!("Unknown encryption type") + }, + } + } + + #[test] + fn match_auth_plain_password_should_pass() { + assert!(match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hello there", "plain"), + )); + } + + #[test] + fn match_auth_plain_password_should_fail() { + assert!(!match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hi!", "plain"), + )); + } + + #[test] + fn match_auth_sha256_password_should_pass() { + assert!(match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hello there", "sha256"), + )); + } + + #[test] + fn match_auth_sha256_password_should_fail() { + assert!(!match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hi!", "sha256"), + )); + } + + #[test] + fn match_auth_sha512_password_should_pass() { + assert!(match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hello there", "sha512"), + )); + } + + #[test] + fn match_auth_sha512_password_should_fail() { + assert!(!match_auth( + create_auth_params("obi", "hello there"), + &create_required_auth("obi", "hi!", "sha512"), + )); + } +} -- cgit v1.2.3 From 8a548fd1eba41ecca91928b855eb4241352ba1d8 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 22:44:31 +0700 Subject: Resolve https://github.com/svenstaro/miniserve/pull/76#discussion_r277011948 --- src/args.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/args.rs b/src/args.rs index c7a4917..d26fb41 100644 --- a/src/args.rs +++ b/src/args.rs @@ -91,22 +91,21 @@ fn parse_auth(src: &str) -> Result { None => return Err(errmsg), }; - let password = match split.next() { - Some(hash) => match second_part { + let password = if let Some(hash) = split.next() { + match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) - }, - None => { - // To make it Windows-compatible, the password needs to be shorter than 255 characters. - // After 255 characters, Windows will truncate the value. - // As for the username, the spec does not mention a limit in length - if second_part.len() > 255 { - return Err("Password length cannot exceed 255 characters".to_owned()); - } - - auth::RequiredAuthPassword::Plain(second_part.to_owned()) - }, + } + } else { + // To make it Windows-compatible, the password needs to be shorter than 255 characters. + // After 255 characters, Windows will truncate the value. + // As for the username, the spec does not mention a limit in length + if second_part.len() > 255 { + return Err("Password length cannot exceed 255 characters".to_owned()); + } + + auth::RequiredAuthPassword::Plain(second_part.to_owned()) }; Ok(auth::RequiredAuth { -- cgit v1.2.3 From 03c196b76f28384e4a340312be67aab4a9c7885d Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 19 Apr 2019 23:05:44 +0700 Subject: Use '?' --- src/args.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/args.rs b/src/args.rs index c7a4917..00d3c9f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -173,26 +173,32 @@ mod tests { } #[test] - fn parse_auth_plain() { + fn parse_auth_plain() -> Result<(), String> { assert_eq!( - parse_auth("username:password").unwrap(), + parse_auth("username:password")?, create_required_auth("username", "password", "plain") ); + + Ok(()) } #[test] - fn parse_auth_sha256() { + fn parse_auth_sha256() -> Result<(), String> { assert_eq!( - parse_auth("username:sha256:hash").unwrap(), + parse_auth("username:sha256:hash")?, create_required_auth("username", "hash", "sha256") ); + + Ok(()) } #[test] - fn parse_auth_sha512() { + fn parse_auth_sha512() -> Result<(), String> { assert_eq!( - parse_auth("username:sha512:hash").unwrap(), + parse_auth("username:sha512:hash")?, create_required_auth("username", "hash", "sha512") ); + + Ok(()) } } \ No newline at end of file -- cgit v1.2.3 From 4928988b04dd1c63aa3163dcd98e6483cae0e323 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 02:31:32 +0700 Subject: Correct error message --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index 25d70ad..c749ac4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -95,7 +95,7 @@ fn parse_auth(src: &str) -> Result { match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), - _ => return Err("Invalid hash method, valid methods is sha256".to_owned()) + _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()) } } else { // To make it Windows-compatible, the password needs to be shorter than 255 characters. -- cgit v1.2.3 From b57f0db13f5d2d55fb6ae2942279896fd3922586 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 02:59:29 +0700 Subject: Add tests for where parse_auth fails --- src/args.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/args.rs b/src/args.rs index c749ac4..d42d317 100644 --- a/src/args.rs +++ b/src/args.rs @@ -200,4 +200,31 @@ mod tests { Ok(()) } + + #[test] + fn parse_auth_invalid_syntax() { + assert_eq!( + parse_auth("foo").unwrap_err(), + "Invalid credentials string, expected format is username:password".to_owned() + ); + } + + #[test] + fn parse_auth_invalid_hash_method() { + assert_eq!( + parse_auth("username:blahblah:hash").unwrap_err(), + "Invalid hash method, only accept either sha256 or sha512".to_owned() + ); + } + + #[test] + fn parse_auth_excessive_length() { + let password = &"x".repeat(256); + let param = "username:".to_owned() + password; + + assert_eq!( + parse_auth(&*param).unwrap_err(), + "Password length cannot exceed 255 characters".to_owned() + ); + } } \ No newline at end of file -- cgit v1.2.3 From a8983921f1b20112591bb41dc6f9ab6ba8340c2f Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 03:03:40 +0700 Subject: Update help description for --auth --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index d42d317..433840e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -37,7 +37,7 @@ struct CLIArgs { )] interfaces: Vec, - /// Set authentication (username:password) + /// Set authentication (username:password, username:sha256:hash, or username:sha512:hash) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] auth: Option, -- cgit v1.2.3 From 646190e5c6a5c93b32a5f6efcc4fc7b7af11037d Mon Sep 17 00:00:00 2001 From: khai96_ Date: Sat, 20 Apr 2019 14:14:24 +0700 Subject: Remove spaces before function names --- src/auth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index e96a0ce..fed3732 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -109,7 +109,7 @@ impl Middleware for Auth { #[cfg(test)] mod tests { use super::*; - + #[test] fn get_hash_hex_sha256() { let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned(); @@ -124,14 +124,14 @@ mod tests { assert_eq!(expectation, received); } - fn create_auth_params (username: &str, password: &str) -> BasicAuthParams { + fn create_auth_params(username: &str, password: &str) -> BasicAuthParams { BasicAuthParams { username: username.to_owned(), password: password.to_owned(), } } - fn create_required_auth (username: &str, password: &str, encrypt: &str) -> RequiredAuth { + fn create_required_auth(username: &str, password: &str, encrypt: &str) -> RequiredAuth { use RequiredAuthPassword::*; RequiredAuth { -- cgit v1.2.3 From f5d7a051a13ff6b16d69b80f91a06d264c0cd978 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Tue, 23 Apr 2019 23:12:03 +0700 Subject: Convert hex strings to raw byte vectors and compare them --- src/args.rs | 37 +++++++++++++++++++++++++------------ src/auth.rs | 33 +++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/args.rs b/src/args.rs index 433840e..5617704 100644 --- a/src/args.rs +++ b/src/args.rs @@ -91,11 +91,16 @@ fn parse_auth(src: &str) -> Result { None => return Err(errmsg), }; - let password = if let Some(hash) = split.next() { + let password = if let Some(hash_hex) = split.next() { + let hash_bin = match hex::decode(hash_hex) { + Ok(hash_bin) => hash_bin, + _ => return Err("Hash string is not a valid hex code".to_owned()), + }; + match second_part { - "sha256" => auth::RequiredAuthPassword::Sha256(hash.to_owned()), - "sha512" => auth::RequiredAuthPassword::Sha512(hash.to_owned()), - _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()) + "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin.to_owned()), + "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin.to_owned()), + _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()), } } else { // To make it Windows-compatible, the password needs to be shorter than 255 characters. @@ -164,8 +169,8 @@ mod tests { username: username.to_owned(), password: match encrypt { "plain" => Plain(password.to_owned()), - "sha256" => Sha256(password.to_owned()), - "sha512" => Sha512(password.to_owned()), + "sha256" => Sha256(hex::decode(password.to_owned()).unwrap()), + "sha512" => Sha512(hex::decode(password.to_owned()).unwrap()), _ => panic!("Unknown encryption type") }, } @@ -184,8 +189,8 @@ mod tests { #[test] fn parse_auth_sha256() -> Result<(), String> { assert_eq!( - parse_auth("username:sha256:hash")?, - create_required_auth("username", "hash", "sha256") + parse_auth("username:sha256:abcd")?, + create_required_auth("username", "abcd", "sha256") ); Ok(()) @@ -194,8 +199,8 @@ mod tests { #[test] fn parse_auth_sha512() -> Result<(), String> { assert_eq!( - parse_auth("username:sha512:hash")?, - create_required_auth("username", "hash", "sha512") + parse_auth("username:sha512:abcd")?, + create_required_auth("username", "abcd", "sha512") ); Ok(()) @@ -212,11 +217,19 @@ mod tests { #[test] fn parse_auth_invalid_hash_method() { assert_eq!( - parse_auth("username:blahblah:hash").unwrap_err(), + parse_auth("username:blahblah:abcd").unwrap_err(), "Invalid hash method, only accept either sha256 or sha512".to_owned() ); } + #[test] + fn parse_auth_invalid_hash_string() { + assert_eq!( + parse_auth("username:sha256:invalid").unwrap_err(), + "Hash string is not a valid hex code".to_owned() + ); + } + #[test] fn parse_auth_excessive_length() { let password = &"x".repeat(256); @@ -227,4 +240,4 @@ mod tests { "Password length cannot exceed 255 characters".to_owned() ); } -} \ No newline at end of file +} diff --git a/src/auth.rs b/src/auth.rs index fed3732..806bdb7 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -20,8 +20,8 @@ pub struct BasicAuthParams { #[derive(Clone, Debug, PartialEq)] pub enum RequiredAuthPassword { Plain(String), - Sha256(String), - Sha512(String), + Sha256(Vec), + Sha512(Vec), } #[derive(Clone, Debug, PartialEq)] @@ -61,14 +61,14 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } } -pub fn compare_hash(password: String, hash: &String) -> bool { - get_hash_hex::(password) == *hash +pub fn compare_hash(password: String, hash: &Vec) -> bool { + get_hash::(password) == *hash } -pub fn get_hash_hex(text: String) -> String { +pub fn get_hash(text: String) -> Vec { let mut hasher = T::new(); hasher.input(text); - hex::encode(hasher.result()) + hasher.result().to_vec() } impl Middleware for Auth { @@ -110,18 +110,23 @@ impl Middleware for Auth { mod tests { use super::*; + fn assert_hex_eq(expectation: &str, received: Vec) { + let bin = hex::decode(expectation).expect("Provided string is not a valid hex code"); + assert_eq!(bin, received); + } + #[test] fn get_hash_hex_sha256() { - let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned(); - let received = get_hash_hex::("abc".to_owned()); - assert_eq!(expectation, received); + let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; + let received = get_hash::("abc".to_owned()); + assert_hex_eq(expectation, received); } #[test] fn get_hash_hex_sha512() { - let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f".to_owned(); - let received = get_hash_hex::("abc".to_owned()); - assert_eq!(expectation, received); + let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; + let received = get_hash::("abc".to_owned()); + assert_hex_eq(expectation, received); } fn create_auth_params(username: &str, password: &str) -> BasicAuthParams { @@ -138,8 +143,8 @@ mod tests { username: username.to_owned(), password: match encrypt { "plain" => Plain(password.to_owned()), - "sha256" => Sha256(get_hash_hex::(password.to_owned())), - "sha512" => Sha512(get_hash_hex::(password.to_owned())), + "sha256" => Sha256(get_hash::(password.to_owned())), + "sha512" => Sha512(get_hash::(password.to_owned())), _ => panic!("Unknown encryption type") }, } -- cgit v1.2.3 From dbe6acefcb76b30a6f85ee4a622e3558d242598f Mon Sep 17 00:00:00 2001 From: khai96_ Date: Wed, 24 Apr 2019 19:01:27 +0700 Subject: Use format! instead of manual string concatenation --- src/args.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/args.rs b/src/args.rs index 5617704..b7e31b0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -232,11 +232,10 @@ mod tests { #[test] fn parse_auth_excessive_length() { - let password = &"x".repeat(256); - let param = "username:".to_owned() + password; + let auth_string = format!("username:{}", "x".repeat(256)); assert_eq!( - parse_auth(&*param).unwrap_err(), + parse_auth(&*auth_string).unwrap_err(), "Password length cannot exceed 255 characters".to_owned() ); } -- cgit v1.2.3 From 706649e38b850f6210dbface1a6c0ca5ccdb468f Mon Sep 17 00:00:00 2001 From: khai96_ Date: Wed, 24 Apr 2019 19:03:48 +0700 Subject: Run cargo fmt --- src/args.rs | 4 ++-- src/auth.rs | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/args.rs b/src/args.rs index b7e31b0..f13d14f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -161,7 +161,7 @@ pub fn parse_args() -> crate::MiniserveConfig { mod tests { use super::*; - fn create_required_auth (username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { + fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { use auth::*; use RequiredAuthPassword::*; @@ -171,7 +171,7 @@ mod tests { "plain" => Plain(password.to_owned()), "sha256" => Sha256(hex::decode(password.to_owned()).unwrap()), "sha512" => Sha512(hex::decode(password.to_owned()).unwrap()), - _ => panic!("Unknown encryption type") + _ => panic!("Unknown encryption type"), }, } } diff --git a/src/auth.rs b/src/auth.rs index 806bdb7..a4b3555 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; -use sha2::{Sha256, Sha512, Digest}; +use sha2::{Digest, Sha256, Sha512}; pub struct Auth; @@ -55,9 +55,15 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } match &required_auth.password { - RequiredAuthPassword::Plain(ref required_password) => basic_auth.password == *required_password, - RequiredAuthPassword::Sha256(password_hash) => compare_hash::(basic_auth.password, password_hash), - RequiredAuthPassword::Sha512(password_hash) => compare_hash::(basic_auth.password, password_hash), + RequiredAuthPassword::Plain(ref required_password) => { + basic_auth.password == *required_password + } + RequiredAuthPassword::Sha256(password_hash) => { + compare_hash::(basic_auth.password, password_hash) + } + RequiredAuthPassword::Sha512(password_hash) => { + compare_hash::(basic_auth.password, password_hash) + } } } @@ -145,7 +151,7 @@ mod tests { "plain" => Plain(password.to_owned()), "sha256" => Sha256(get_hash::(password.to_owned())), "sha512" => Sha512(get_hash::(password.to_owned())), - _ => panic!("Unknown encryption type") + _ => panic!("Unknown encryption type"), }, } } -- cgit v1.2.3 From cb3cf7331b2bf270a78121bf0ba70789df17e204 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Thu, 25 Apr 2019 17:51:48 +0700 Subject: Update Cargo.lock --- Cargo.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c67a2f4..5068d5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1313,15 +1313,16 @@ dependencies = [ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "opaque-debug" -version = "0.2.2" - [[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "opaque-debug" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "openssl" version = "0.10.20" @@ -2851,8 +2852,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" -"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum openssl 0.10.20 (registry+https://github.com/rust-lang/crates.io-index)" = "5a0d6b781aac4ac1bd6cafe2a2f0ad8c16ae8e1dd5184822a16c50139f8838d9" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.43 (registry+https://github.com/rust-lang/crates.io-index)" = "33c86834957dd5b915623e94f2f4ab2c70dd8f6b70679824155d5ae21dbd495d" -- cgit v1.2.3 From 0b731cf0a0337f4e406baf4cbbcc407e7d9af9eb Mon Sep 17 00:00:00 2001 From: khai96_ Date: Thu, 25 Apr 2019 19:13:18 +0700 Subject: Use rstest_parametrize for unit tests --- src/args.rs | 85 +++++++++++++++------------------------------ src/auth.rs | 112 ++++++++++++++++++++++++------------------------------------ 2 files changed, 71 insertions(+), 126 deletions(-) diff --git a/src/args.rs b/src/args.rs index f13d14f..496d697 100644 --- a/src/args.rs +++ b/src/args.rs @@ -160,6 +160,7 @@ pub fn parse_args() -> crate::MiniserveConfig { #[cfg(test)] mod tests { use super::*; + use rstest::rstest_parametrize; fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { use auth::*; @@ -176,67 +177,35 @@ mod tests { } } - #[test] - fn parse_auth_plain() -> Result<(), String> { - assert_eq!( - parse_auth("username:password")?, - create_required_auth("username", "password", "plain") - ); - - Ok(()) - } - - #[test] - fn parse_auth_sha256() -> Result<(), String> { - assert_eq!( - parse_auth("username:sha256:abcd")?, - create_required_auth("username", "abcd", "sha256") - ); - - Ok(()) - } - - #[test] - fn parse_auth_sha512() -> Result<(), String> { - assert_eq!( - parse_auth("username:sha512:abcd")?, - create_required_auth("username", "abcd", "sha512") - ); - - Ok(()) - } - - #[test] - fn parse_auth_invalid_syntax() { - assert_eq!( - parse_auth("foo").unwrap_err(), - "Invalid credentials string, expected format is username:password".to_owned() - ); - } - - #[test] - fn parse_auth_invalid_hash_method() { - assert_eq!( - parse_auth("username:blahblah:abcd").unwrap_err(), - "Invalid hash method, only accept either sha256 or sha512".to_owned() - ); - } - - #[test] - fn parse_auth_invalid_hash_string() { + #[rstest_parametrize( + auth_string, username, password, encrypt, + case("username:password", "username", "password", "plain"), + case("username:sha256:abcd", "username", "abcd", "sha256"), + case("username:sha512:abcd", "username", "abcd", "sha512") + )] + fn parse_auth_valid(auth_string: &str, username: &str, password: &str, encrypt: &str) { assert_eq!( - parse_auth("username:sha256:invalid").unwrap_err(), - "Hash string is not a valid hex code".to_owned() + parse_auth(auth_string).unwrap(), + create_required_auth(username, password, encrypt), ); } - #[test] - fn parse_auth_excessive_length() { - let auth_string = format!("username:{}", "x".repeat(256)); - - assert_eq!( - parse_auth(&*auth_string).unwrap_err(), - "Password length cannot exceed 255 characters".to_owned() - ); + #[rstest_parametrize( + auth_string, err_msg, + case( + "foo", + "Invalid credentials string, expected format is username:password" + ), + case( + "username:blahblah:abcd", + "Invalid hash method, only accept either sha256 or sha512" + ), + case( + "username:sha256:invalid", + "Hash string is not a valid hex code" + ), + )] + fn parse_auth_invalid(auth_string: &str, err_msg: &str) { + assert_eq!(parse_auth(auth_string).unwrap_err(), err_msg.to_owned(),); } } diff --git a/src/auth.rs b/src/auth.rs index a4b3555..2db422d 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -115,31 +115,26 @@ impl Middleware for Auth { #[cfg(test)] mod tests { use super::*; + use rstest::rstest_parametrize; - fn assert_hex_eq(expectation: &str, received: Vec) { - let bin = hex::decode(expectation).expect("Provided string is not a valid hex code"); - assert_eq!(bin, received); - } - - #[test] - fn get_hash_hex_sha256() { - let expectation = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; - let received = get_hash::("abc".to_owned()); - assert_hex_eq(expectation, received); + fn get_hash_func(name: &str) -> impl FnOnce(String) -> Vec { + match name { + "sha256" => get_hash::, + "sha512" => get_hash::, + _ => panic!("Invalid hash method"), + } } - #[test] - fn get_hash_hex_sha512() { - let expectation = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; - let received = get_hash::("abc".to_owned()); - assert_hex_eq(expectation, received); - } - - fn create_auth_params(username: &str, password: &str) -> BasicAuthParams { - BasicAuthParams { - username: username.to_owned(), - password: password.to_owned(), - } + #[rstest_parametrize( + password, hash_method, hash, + case("abc", "sha256", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"), + case("abc", "sha512", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"), + )] + fn test_get_hash(password: &str, hash_method: &str, hash: &str) { + let hash_func = get_hash_func(hash_method); + let expected = hex::decode(hash).expect("Provided hash is not a valid hex code"); + let received = hash_func(password.to_owned()); + assert_eq!(received, expected); } fn create_required_auth(username: &str, password: &str, encrypt: &str) -> RequiredAuth { @@ -156,51 +151,32 @@ mod tests { } } - #[test] - fn match_auth_plain_password_should_pass() { - assert!(match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hello there", "plain"), - )); - } - - #[test] - fn match_auth_plain_password_should_fail() { - assert!(!match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hi!", "plain"), - )); - } - - #[test] - fn match_auth_sha256_password_should_pass() { - assert!(match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hello there", "sha256"), - )); - } - - #[test] - fn match_auth_sha256_password_should_fail() { - assert!(!match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hi!", "sha256"), - )); - } - - #[test] - fn match_auth_sha512_password_should_pass() { - assert!(match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hello there", "sha512"), - )); - } - - #[test] - fn match_auth_sha512_password_should_fail() { - assert!(!match_auth( - create_auth_params("obi", "hello there"), - &create_required_auth("obi", "hi!", "sha512"), - )); + #[rstest_parametrize( + should_pass, param_username, param_password, required_username, required_password, encrypt, + case(true, "obi", "hello there", "obi", "hello there", "plain"), + case(false, "obi", "hello there", "obi", "hi!", "plain"), + case(true, "obi", "hello there", "obi", "hello there", "sha256"), + case(false, "obi", "hello there", "obi", "hi!", "sha256"), + case(true, "obi", "hello there", "obi", "hello there", "sha512"), + case(false, "obi", "hello there", "obi", "hi!", "sha512"), + )] + fn test_auth( + should_pass: bool, + param_username: &str, + param_password: &str, + required_username: &str, + required_password: &str, + encrypt: &str, + ) { + assert_eq!( + match_auth( + BasicAuthParams { + username: param_username.to_owned(), + password: param_password.to_owned(), + }, + &create_required_auth(required_username, required_password, encrypt), + ), + should_pass, + ) } } -- cgit v1.2.3 From aac70e6607d3e8172f15df91bc16dc3244473ea9 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 26 Apr 2019 17:08:23 +0700 Subject: Comply to change requests - Added doc comments - Added an additional test case --- src/args.rs | 9 ++++++++- src/auth.rs | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index 8f15ea4..97b391f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -38,7 +38,8 @@ struct CLIArgs { )] interfaces: Vec, - /// Set authentication (username:password, username:sha256:hash, or username:sha512:hash) + /// Set authentication. Currently supported formats: + /// username:password, username:sha256:hash, username:sha512:hash #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] auth: Option, @@ -88,6 +89,7 @@ fn parse_auth(src: &str) -> Result { None => return invalid_auth_format, }; + // second_part is either password in username:password or method in username:method:hash let second_part = match split.next() { // This allows empty passwords, as the spec does not forbid it Some(password) => password, @@ -169,6 +171,7 @@ mod tests { use super::*; use rstest::rstest_parametrize; + /// Helper function that creates a `RequiredAuth` structure fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { use auth::*; use RequiredAuthPassword::*; @@ -211,6 +214,10 @@ mod tests { "username:sha256:invalid", "Invalid format for password hash. Expected hex code" ), + case( + "username:sha512:invalid", + "Invalid format for password hash. Expected hex code" + ), )] fn parse_auth_invalid(auth_string: &str, err_msg: &str) { let err = parse_auth(auth_string).unwrap_err(); diff --git a/src/auth.rs b/src/auth.rs index 432f6ce..e75f498 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -15,6 +15,7 @@ pub struct BasicAuthParams { } #[derive(Clone, Debug, PartialEq)] +/// `password` field of `RequiredAuth` pub enum RequiredAuthPassword { Plain(String), Sha256(Vec), @@ -22,7 +23,7 @@ pub enum RequiredAuthPassword { } #[derive(Clone, Debug, PartialEq)] -/// Authentication structure to match BasicAuthParams against +/// Authentication structure to match `BasicAuthParams` against pub struct RequiredAuth { pub username: String, pub password: RequiredAuthPassword, @@ -54,6 +55,7 @@ pub fn parse_basic_auth( }) } +/// Verify authentication pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> bool { if basic_auth.username != required_auth.username { return false; @@ -72,10 +74,12 @@ pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &RequiredAuth) -> } } +/// Return `true` if hashing of `password` by `T` algorithm equals to `hash` pub fn compare_hash(password: String, hash: &Vec) -> bool { get_hash::(password) == *hash } +/// Get hash of a `text` pub fn get_hash(text: String) -> Vec { let mut hasher = T::new(); hasher.input(text); @@ -124,6 +128,7 @@ mod tests { use super::*; use rstest::rstest_parametrize; + /// Return a hashing function corresponds to given name fn get_hash_func(name: &str) -> impl FnOnce(String) -> Vec { match name { "sha256" => get_hash::, @@ -144,6 +149,7 @@ mod tests { assert_eq!(received, expected); } + /// Helper function that creates a `RequiredAuth` structure and encrypt `password` if necessary fn create_required_auth(username: &str, password: &str, encrypt: &str) -> RequiredAuth { use RequiredAuthPassword::*; -- cgit v1.2.3 From 7accdf8f480a34353c4d190ef893e51242275553 Mon Sep 17 00:00:00 2001 From: khai96_ Date: Fri, 26 Apr 2019 17:15:46 +0700 Subject: Use 'if let' --- src/args.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/args.rs b/src/args.rs index 97b391f..4077f35 100644 --- a/src/args.rs +++ b/src/args.rs @@ -97,9 +97,10 @@ fn parse_auth(src: &str) -> Result { }; let password = if let Some(hash_hex) = split.next() { - let hash_bin = match hex::decode(hash_hex) { - Ok(hash_bin) => hash_bin, - _ => return Err(ContextualError::new(ContextualErrorKind::InvalidPasswordHash)), + let hash_bin = if let Ok(hash_bin) = hex::decode(hash_hex) { + hash_bin + } else { + return Err(ContextualError::new(ContextualErrorKind::InvalidPasswordHash)) }; match second_part { -- cgit v1.2.3