diff --git a/Cargo.lock b/Cargo.lock index 66070a9..0420f2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -953,6 +953,7 @@ dependencies = [ "clap", "omegaupload-common", "reqwest", + "rpassword", ] [[package]] @@ -1327,6 +1328,16 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rpassword" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c7da07f..75ce687 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,4 +11,5 @@ omegaupload-common = { path = "../common" } anyhow = "1" atty = "0.2" clap = "3.0.0-beta.4" -reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] } \ No newline at end of file +reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] } +rpassword = "5" \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs index df66e45..029840f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,7 @@ #![warn(clippy::nursery, clippy::pedantic)] #![deny(unsafe_code)] -use std::io::{Read, Write}; +use std::io::Write; use std::path::PathBuf; use anyhow::{anyhow, bail, Context, Result}; @@ -15,6 +15,7 @@ use omegaupload_common::{ use reqwest::blocking::Client; use reqwest::header::EXPIRES; use reqwest::StatusCode; +use rpassword::prompt_password_stderr; #[derive(Parser)] struct Opts { @@ -65,16 +66,12 @@ fn handle_upload( ) -> Result<()> { url.set_fragment(None); - if atty::is(Stream::Stdin) { - bail!("This tool requires non interactive CLI. Pipe something in!"); - } - let (data, key) = { let mut container = std::fs::read(path)?; let password = if password { - let mut buffer = vec![]; - std::io::stdin().read_to_end(&mut buffer)?; - Some(SecretVec::new(buffer)) + let maybe_password = + prompt_password_stderr("Please set the password for this paste: ")?; + Some(SecretVec::new(maybe_password.into_bytes())) } else { None }; @@ -140,21 +137,12 @@ fn handle_download(mut url: ParsedUrl) -> Result<()> { let mut password = None; if url.needs_password { // Only print prompt on interactive, else it messes with output - if atty::is(Stream::Stdout) { - print!("Please enter the password to access this document: "); - std::io::stdout().flush()?; - } - let mut input = String::new(); - std::io::stdin().read_line(&mut input)?; - input.pop(); // last character is \n, we need to drop it. - password = Some(input); + let maybe_password = + prompt_password_stderr("Please enter the password to access this paste: ")?; + password = Some(SecretVec::new(maybe_password.into_bytes())); } - open_in_place( - &mut data, - &url.decryption_key, - password.map(|v| SecretVec::new(v.into_bytes())), - )?; + open_in_place(&mut data, &url.decryption_key, password)?; if atty::is(Stream::Stdout) { if let Ok(data) = String::from_utf8(data) { diff --git a/common/src/crypto.rs b/common/src/crypto.rs index 4ca7e7a..e7f44af 100644 --- a/common/src/crypto.rs +++ b/common/src/crypto.rs @@ -123,9 +123,8 @@ pub fn open_in_place( key: &Secret, password: Option>, ) -> Result<(), Error> { - let buffer_len = data.len(); let pw_key = if let Some(password) = password { - let salt_buf = data.split_off(buffer_len - Salt::SIZE); + let salt_buf = data.split_off(data.len() - Salt::SIZE); let argon = Argon2::default(); let mut pw_key = Key::default(); argon.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)?; @@ -134,7 +133,7 @@ pub fn open_in_place( None }; - let nonce = Nonce::from_slice(&data.split_off(buffer_len - Nonce::SIZE)); + let nonce = Nonce::from_slice(&data.split_off(data.len() - Nonce::SIZE)); // At this point we should have a buffer that's only the ciphertext.