From 311677d1951765294da82e58cf0b0f033051fa69 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sat, 13 Nov 2021 19:34:46 +0100 Subject: [PATCH] Overwrite by default --- src/cli.rs | 79 ++++++++++++++++++++++++++++++---------------- src/lib.rs | 46 +++++++++++++-------------- src/main.rs | 90 +++++++++++++++++++++++++++++------------------------ 3 files changed, 124 insertions(+), 91 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 27379a9..657e558 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,11 +1,6 @@ -use std::{ - path::Path, - fs::File, - str::FromStr, - io::{stdin, stdout, Read}, -}; +use std::{fs::File, io::{self, Read, stdin, stdout}, path::Path, str::FromStr}; use clap::{crate_name, crate_version, App, Arg, AppSettings}; -use crate::{LazyWriter, WrappedPassword, crypto::CipherAlgorithm}; +use crate::{WrappedWriter, WrappedPassword, crypto::CipherAlgorithm}; cpufeatures::new!(aes_ni, "aes"); @@ -16,10 +11,27 @@ pub struct CliArgs { pub cipher: CipherAlgorithm, pub block_size: usize, pub reader: Box, - pub writer: LazyWriter, + pub writer: WrappedWriter, } -pub fn parse() -> Option { +pub struct ParseResult { + pub error: bool, + pub cli_args: Option, +} + +impl ParseResult { + fn exited() -> Self { + Self { error: false, cli_args: None } + } +} + +impl From for ParseResult { + fn from(args: CliArgs) -> Self { + ParseResult { error: false, cli_args: Some(args) } + } +} + +pub fn parse() -> Option { let app = App::new(crate_name!()) .version(crate_version!()) .setting(AppSettings::ColoredHelp) @@ -27,11 +39,17 @@ pub fn parse() -> Option { .arg(Arg::with_name("INPUT").help(" | \"-\" or empty for stdin")) .arg(Arg::with_name("OUTPUT").help(" | \"-\" or empty for stdout")) .arg( - Arg::with_name("force-encrypt") + Arg::with_name("1_force_encrypt") .short("f") .long("force-encrypt") .help(&format!("Encrypt even if {} format is recognized", crate_name!())) ) + .arg( + Arg::with_name("2_interactive") + .short("i") + .long("interactive") + .help("Prompt before overwriting files") + ) .arg( Arg::with_name("1_password") .long("password") @@ -42,7 +60,7 @@ pub fn parse() -> Option { Arg::with_name("2_t_cost") .short("t") .long("time-cost") - .value_name("number of iterations") + .value_name("iterations") .help("Argon2 time cost") .default_value("10") ) @@ -50,7 +68,7 @@ pub fn parse() -> Option { Arg::with_name("3_m_cost") .short("m") .long("memory-cost") - .value_name("memory cost") + .value_name("memory size") .help("Argon2 memory cost (in kilobytes)") .default_value("4096") ) @@ -58,7 +76,7 @@ pub fn parse() -> Option { Arg::with_name("4_p_cost") .short("p") .long("parallelism") - .value_name("degree of parallelism") + .value_name("threads") .help("Argon2 parallelism cost") .default_value("4") ) @@ -126,28 +144,37 @@ pub fn parse() -> Option { None => Box::new(stdin()) }; - let output = app + let wrapped_writer = match app .value_of("OUTPUT") - .and_then(|s| if s == "-" { None } else { Some(s) }) - .map(|s| { - if Path::new(s).exists() { - eprintln!("WARNING: {} already exists", s); - None - } else { - Some(LazyWriter::from_path(s.to_owned())) + .and_then(|s| if s == "-" { None } else { Some(s) }) { + Some(path) => { + if { + if app.is_present("2_interactive") && Path::new(path).exists() { + eprint!("Warning: {} already exists. Overwrite [y/N]? ", path); + let mut c = String::with_capacity(2); + io::stdin().read_line(&mut c).unwrap(); + !c.is_empty() && c.chars().nth(0).unwrap() == 'y' + } else { + true + } + } { + WrappedWriter::from_path(path.to_string()) + } else { + return Some(ParseResult::exited()) + } } - }) - .unwrap_or_else(|| Some(LazyWriter::from_writer(stdout())))?; + None => WrappedWriter::from_writer(stdout()) + }; Some(CliArgs { password: app.value_of("1_password").into(), - force_encrypt: app.is_present("force-encrypt"), + force_encrypt: app.is_present("1_force_encrypt"), argon2_params: params, cipher, block_size, reader: input, - writer: output, - }) + writer: wrapped_writer, + }.into()) } fn number(val: &str) -> Option { diff --git a/src/lib.rs b/src/lib.rs index 6f60a6d..ecc24c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod cli; pub mod crypto; -use std::{fs::File, path::Path, io::{self, Read, Write}}; +use std::{fmt::Display, fs::OpenOptions, io::{self, BufWriter, Read, Write}, path::Path}; use crypto::{DobyCipher, EncryptionParams}; use zeroize::Zeroize; @@ -36,37 +36,33 @@ impl From> for WrappedPassword { Self(s.map(String::from)) } } - -pub struct LazyWriter> { - path: Option

, - writer: Option>, +pub enum WrappedWriter> { + PATH { + path: P + }, + WRITER { + writer: Box + } } -impl> LazyWriter

{ +impl + Display> WrappedWriter

{ fn from_path(path: P) -> Self { - Self { - path: Some(path), - writer: None, - } + Self::PATH { path } } + fn from_writer(writer: T) -> Self { - Self { - path: None, - writer: Some(Box::new(writer)), - } - } -} - -impl> Write for LazyWriter

{ - fn write(&mut self, buf: &[u8]) -> io::Result { - if self.writer.is_none() { - self.writer = Some(Box::new(File::create(self.path.as_ref().unwrap()).unwrap())); - } - self.writer.as_mut().unwrap().write(buf) + Self::WRITER { writer: Box::new(writer) } } - fn flush(&mut self) -> io::Result<()> { - self.writer.as_mut().unwrap().flush() + pub fn into_buf_writer(self) -> Option>> { + Some(BufWriter::new(match self { + Self::PATH { path } => Box::new( + OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref()) + .map_err(|e| eprintln!("{}: {}", path, e)) + .ok()? + ) as Box, + Self::WRITER { writer } => writer, + })) } } diff --git a/src/main.rs b/src/main.rs index 86aa328..c0cd392 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{process, io::{BufWriter, BufReader, Read}}; +use std::{process, io::{BufReader, Read}}; use doby::{ cli, crypto::{EncryptionParams, DobyCipher}, @@ -10,58 +10,68 @@ use zeroize::Zeroize; fn run() -> bool { let mut success = false; - if let Some(cli_args) = cli::parse() { - let mut reader = BufReader::with_capacity(cli_args.block_size, cli_args.reader); - let mut writer = BufWriter::with_capacity(cli_args.block_size, cli_args.writer); + if let Some(result) = cli::parse() { + if let Some(cli_args) = result.cli_args { + let mut reader = BufReader::new(cli_args.reader); - let mut magic_bytes = vec![0; MAGIC_BYTES.len()]; - match reader.read(&mut magic_bytes) { - Ok(n) => { - if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt - match EncryptionParams::read(&mut reader) { - Ok(params) => { - match params { - Some(params) => { + let mut magic_bytes = vec![0; MAGIC_BYTES.len()]; + match reader.read(&mut magic_bytes) { + Ok(n) => { + if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt + match EncryptionParams::read(&mut reader) { + Ok(params) => { + if let Some(params) = params { if let Some(mut password) = cli_args.password.get(false) { - let cipher = DobyCipher::new(password.as_bytes(), ¶ms); - password.zeroize(); - match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) { - Ok(verified) => { - if verified { - success = true - } else { - eprintln!("WARNING: HMAC verification failed !\nEither your password is incorrect or the ciphertext has been corrupted.\nBe careful, the data could have been altered by an attacker."); + if let Some(mut writer) = cli_args.writer.into_buf_writer() { + let cipher = DobyCipher::new(password.as_bytes(), ¶ms); + password.zeroize(); + match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) { + Ok(verified) => { + if verified { + success = true + } else { + eprintln!("Warning: HMAC verification failed !\nEither your password is incorrect or the ciphertext has been corrupted.\nBe careful, the data could have been altered by an attacker."); + } } + Err(e) => eprintln!("I/O error while decrypting: {}", e) } - Err(e) => eprintln!("I/O error while decrypting: {}", e) + } else { + password.zeroize(); } } + } else { + eprintln!("Invalid parameters") } - None => eprintln!("Invalid parameters") } + Err(e) => eprintln!("I/O error while reading headers: {}", e) } - Err(e) => eprintln!("I/O error while reading headers: {}", e) - } - } else { //otherwise, encrypt - let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher); - if let Some(mut password) = cli_args.password.get(true) { - let cipher = DobyCipher::new(password.as_bytes(), ¶ms); - password.zeroize(); - match encrypt( - &mut reader, - &mut writer, - ¶ms, - cipher, - cli_args.block_size, - Some(&magic_bytes[..n]) - ) { - Ok(_) => success = true, - Err(e) => eprintln!("I/O error while encrypting: {}", e) + } else { //otherwise, encrypt + let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher); + if let Some(mut password) = cli_args.password.get(true) { + if let Some(mut writer) = cli_args.writer.into_buf_writer() { + let cipher = DobyCipher::new(password.as_bytes(), ¶ms); + password.zeroize(); + match encrypt( + &mut reader, + &mut writer, + ¶ms, + cipher, + cli_args.block_size, + Some(&magic_bytes[..n]) + ) { + Ok(_) => success = true, + Err(e) => eprintln!("I/O error while encrypting: {}", e) + } + } else { + password.zeroize(); + } } } } + Err(e) => eprintln!("I/O error while reading magic bytes: {}", e), } - Err(e) => eprintln!("I/O error while reading magic bytes: {}", e), + } else { + success = !result.error; } } success