aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven-Hendrik Haase <svenstaro@gmail.com>2019-04-26 14:27:03 +0000
committerGitHub <noreply@github.com>2019-04-26 14:27:03 +0000
commiteddeefc3ae3a655cf57e3e259464cee464b6f96e (patch)
treeebb46cbcdde44f87e0d02791bb8e3b20baf8ec37
parentUse rstest test fixtures to cut down on code duplication in integration tests (diff)
parentUse 'if let' (diff)
downloadminiserve-eddeefc3ae3a655cf57e3e259464cee464b6f96e.tar.gz
miniserve-eddeefc3ae3a655cf57e3e259464cee464b6f96e.zip
Merge pull request #76 from KSXGitHub/pullrequest.hashed-password
Add support for hashed password (sha256 and sha512)
-rw-r--r--Cargo.lock83
-rw-r--r--Cargo.toml2
-rw-r--r--src/args.rs128
-rw-r--r--src/auth.rs122
-rw-r--r--src/errors.rs12
-rw-r--r--src/main.rs2
-rw-r--r--stale_outputs_checked0
7 files changed, 318 insertions, 31 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d72d5ef..48ac09c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -276,6 +276,25 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -307,6 +326,11 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -514,6 +538,14 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -641,6 +673,11 @@ dependencies = [
]
[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "filetime"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -730,6 +767,14 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -776,6 +821,11 @@ dependencies = [
]
[[package]]
+name = "hex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "hostname"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1094,6 +1144,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)",
@@ -1106,6 +1157,7 @@ dependencies = [
"rstest 0.2.2 (git+https://github.com/la10736/rstest.git)",
"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)",
@@ -1267,6 +1319,11 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1850,6 +1907,17 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2381,6 +2449,11 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2653,10 +2726,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
"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"
@@ -2681,6 +2757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum ctor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3b4c17619643c1252b5f690084b82639dd7fac141c57c8e77a00e0148132092c"
"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"
@@ -2696,6 +2773,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.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2f8c63033fcba1f51ef744505b3cad42510432b904c062afa67ad7ece008429d"
"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"
@@ -2708,10 +2786,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"
@@ -2765,6 +2845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"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 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"
@@ -2831,6 +2912,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.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
"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"
@@ -2881,6 +2963,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 cef92a6..d7b621f 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 8d2e105..4077f35 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -38,9 +38,10 @@ struct CLIArgs {
)]
interfaces: Vec<IpAddr>,
- /// Set authentication (username:password)
+ /// 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<(String, String)>,
+ auth: Option<auth::RequiredAuth>,
/// Generate a random 6-hexdigit route
#[structopt(long = "random-route")]
@@ -77,34 +78,55 @@ fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> {
}
/// Checks wether the auth string is valid, i.e. it follows the syntax username:password
-fn parse_auth(src: &str) -> Result<(String, String), ContextualError> {
- let mut split = src.splitn(2, ':');
+fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> {
+ let mut split = src.splitn(3, ':');
+ let invalid_auth_format = Err(
+ ContextualError::new(ContextualErrorKind::InvalidAuthFormat)
+ );
let username = match split.next() {
Some(username) => username,
- None => {
- return Err(ContextualError::new(ContextualErrorKind::InvalidAuthFormat));
- }
+ None => return invalid_auth_format,
};
- let password = match split.next() {
+ // 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,
- None => {
- return Err(ContextualError::new(ContextualErrorKind::InvalidAuthFormat));
- }
+ None => return invalid_auth_format,
};
- // 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(ContextualError::new(
- ContextualErrorKind::PasswordTooLongError,
- ));
- }
+ let password = if let Some(hash_hex) = split.next() {
+ let hash_bin = if let Ok(hash_bin) = hex::decode(hash_hex) {
+ hash_bin
+ } else {
+ return Err(ContextualError::new(ContextualErrorKind::InvalidPasswordHash))
+ };
+
+ match second_part {
+ "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin.to_owned()),
+ "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin.to_owned()),
+ _ => {
+ return Err(ContextualError::new(
+ ContextualErrorKind::InvalidHashMethod(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(ContextualError::new(ContextualErrorKind::PasswordTooLongError));
+ }
+
+ 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
@@ -120,11 +142,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 {
@@ -140,7 +157,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,
@@ -149,3 +166,62 @@ pub fn parse_args() -> crate::MiniserveConfig {
file_upload: args.file_upload,
}
}
+
+#[cfg(test)]
+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::*;
+
+ RequiredAuth {
+ username: username.to_owned(),
+ password: match encrypt {
+ "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"),
+ },
+ }
+ }
+
+ #[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(auth_string).unwrap(),
+ create_required_auth(username, password, encrypt),
+ );
+ }
+
+ #[rstest_parametrize(
+ auth_string, err_msg,
+ case(
+ "foo",
+ "Invalid format for credentials string. Expected username:password, username:sha256:hash or username:sha512:hash"
+ ),
+ case(
+ "username:blahblah:abcd",
+ "blahblah is not a valid hashing method. Expected sha256 or sha512"
+ ),
+ case(
+ "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();
+ assert_eq!(format!("{}", err), err_msg.to_owned());
+ }
+}
diff --git a/src/auth.rs b/src/auth.rs
index 1bdf0be..e75f498 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::{Digest, Sha256, Sha512};
use crate::errors::{ContextualError, ContextualErrorKind};
@@ -13,6 +14,21 @@ pub struct BasicAuthParams {
pub password: String,
}
+#[derive(Clone, Debug, PartialEq)]
+/// `password` field of `RequiredAuth`
+pub enum RequiredAuthPassword {
+ Plain(String),
+ Sha256(Vec<u8>),
+ Sha512(Vec<u8>),
+}
+
+#[derive(Clone, Debug, PartialEq)]
+/// 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,
@@ -39,6 +55,37 @@ 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;
+ }
+
+ match &required_auth.password {
+ RequiredAuthPassword::Plain(ref required_password) => {
+ basic_auth.password == *required_password
+ }
+ RequiredAuthPassword::Sha256(password_hash) => {
+ compare_hash::<Sha256>(basic_auth.password, password_hash)
+ }
+ RequiredAuthPassword::Sha512(password_hash) => {
+ compare_hash::<Sha512>(basic_auth.password, password_hash)
+ }
+ }
+}
+
+/// Return `true` if hashing of `password` by `T` algorithm equals to `hash`
+pub fn compare_hash<T: Digest>(password: String, hash: &Vec<u8>) -> bool {
+ get_hash::<T>(password) == *hash
+}
+
+/// Get hash of a `text`
+pub fn get_hash<T: Digest>(text: String) -> Vec<u8> {
+ let mut hasher = T::new();
+ hasher.input(text);
+ hasher.result().to_vec()
+}
+
impl Middleware<crate::MiniserveConfig> for Auth {
fn response(
&self,
@@ -58,9 +105,7 @@ impl Middleware<crate::MiniserveConfig> 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));
}
@@ -77,3 +122,74 @@ impl Middleware<crate::MiniserveConfig> for Auth {
Ok(Response::Done(resp))
}
}
+
+#[cfg(test)]
+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<u8> {
+ match name {
+ "sha256" => get_hash::<Sha256>,
+ "sha512" => get_hash::<Sha512>,
+ _ => panic!("Invalid hash method"),
+ }
+ }
+
+ #[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);
+ }
+
+ /// 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::*;
+
+ RequiredAuth {
+ username: username.to_owned(),
+ password: match encrypt {
+ "plain" => Plain(password.to_owned()),
+ "sha256" => Sha256(get_hash::<sha2::Sha256>(password.to_owned())),
+ "sha512" => Sha512(get_hash::<sha2::Sha512>(password.to_owned())),
+ _ => panic!("Unknown encryption type"),
+ },
+ }
+ }
+
+ #[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,
+ )
+ }
+}
diff --git a/src/errors.rs b/src/errors.rs
index bd6dc66..833e9c4 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -27,9 +27,19 @@ pub enum ContextualErrorKind {
InvalidPathError(String),
/// This error might occur if the HTTP credential string does not respect the expected format
- #[fail(display = "Invalid format for credentials string. Expected is username:format")]
+ #[fail(
+ display = "Invalid format for credentials string. Expected username:password, username:sha256:hash or username:sha512:hash"
+ )]
InvalidAuthFormat,
+ /// This error might occure if the hash method is neither sha256 nor sha512
+ #[fail(display = "{} is not a valid hashing method. Expected sha256 or sha512", _0)]
+ InvalidHashMethod(String),
+
+ /// This error might occur if the HTTP auth hash password is not a valid hex code
+ #[fail(display = "Invalid format for password hash. Expected hex code")]
+ InvalidPasswordHash,
+
/// This error might occur if the HTTP auth password exceeds 255 characters
#[fail(display = "HTTP password length exceeds 255 characters")]
PasswordTooLongError,
diff --git a/src/main.rs b/src/main.rs
index cf7ca93..bc8f3f0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,7 +37,7 @@ pub struct MiniserveConfig {
pub interfaces: Vec<IpAddr>,
/// Enable HTTP basic authentication
- pub auth: Option<auth::BasicAuthParams>,
+ pub auth: Option<auth::RequiredAuth>,
/// 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
--- /dev/null
+++ b/stale_outputs_checked