Overwrite by default

This commit is contained in:
Matéo Duparc 2021-11-13 19:34:46 +01:00
parent 2c8ab7e8ad
commit 311677d195
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
3 changed files with 124 additions and 91 deletions

View File

@ -1,11 +1,6 @@
use std::{ use std::{fs::File, io::{self, Read, stdin, stdout}, path::Path, str::FromStr};
path::Path,
fs::File,
str::FromStr,
io::{stdin, stdout, Read},
};
use clap::{crate_name, crate_version, App, Arg, AppSettings}; 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"); cpufeatures::new!(aes_ni, "aes");
@ -16,10 +11,27 @@ pub struct CliArgs {
pub cipher: CipherAlgorithm, pub cipher: CipherAlgorithm,
pub block_size: usize, pub block_size: usize,
pub reader: Box<dyn Read>, pub reader: Box<dyn Read>,
pub writer: LazyWriter<String>, pub writer: WrappedWriter<String>,
} }
pub fn parse() -> Option<CliArgs> { pub struct ParseResult {
pub error: bool,
pub cli_args: Option<CliArgs>,
}
impl ParseResult {
fn exited() -> Self {
Self { error: false, cli_args: None }
}
}
impl From<CliArgs> for ParseResult {
fn from(args: CliArgs) -> Self {
ParseResult { error: false, cli_args: Some(args) }
}
}
pub fn parse() -> Option<ParseResult> {
let app = App::new(crate_name!()) let app = App::new(crate_name!())
.version(crate_version!()) .version(crate_version!())
.setting(AppSettings::ColoredHelp) .setting(AppSettings::ColoredHelp)
@ -27,11 +39,17 @@ pub fn parse() -> Option<CliArgs> {
.arg(Arg::with_name("INPUT").help("<PATH> | \"-\" or empty for stdin")) .arg(Arg::with_name("INPUT").help("<PATH> | \"-\" or empty for stdin"))
.arg(Arg::with_name("OUTPUT").help("<PATH> | \"-\" or empty for stdout")) .arg(Arg::with_name("OUTPUT").help("<PATH> | \"-\" or empty for stdout"))
.arg( .arg(
Arg::with_name("force-encrypt") Arg::with_name("1_force_encrypt")
.short("f") .short("f")
.long("force-encrypt") .long("force-encrypt")
.help(&format!("Encrypt even if {} format is recognized", crate_name!())) .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(
Arg::with_name("1_password") Arg::with_name("1_password")
.long("password") .long("password")
@ -42,7 +60,7 @@ pub fn parse() -> Option<CliArgs> {
Arg::with_name("2_t_cost") Arg::with_name("2_t_cost")
.short("t") .short("t")
.long("time-cost") .long("time-cost")
.value_name("number of iterations") .value_name("iterations")
.help("Argon2 time cost") .help("Argon2 time cost")
.default_value("10") .default_value("10")
) )
@ -50,7 +68,7 @@ pub fn parse() -> Option<CliArgs> {
Arg::with_name("3_m_cost") Arg::with_name("3_m_cost")
.short("m") .short("m")
.long("memory-cost") .long("memory-cost")
.value_name("memory cost") .value_name("memory size")
.help("Argon2 memory cost (in kilobytes)") .help("Argon2 memory cost (in kilobytes)")
.default_value("4096") .default_value("4096")
) )
@ -58,7 +76,7 @@ pub fn parse() -> Option<CliArgs> {
Arg::with_name("4_p_cost") Arg::with_name("4_p_cost")
.short("p") .short("p")
.long("parallelism") .long("parallelism")
.value_name("degree of parallelism") .value_name("threads")
.help("Argon2 parallelism cost") .help("Argon2 parallelism cost")
.default_value("4") .default_value("4")
) )
@ -126,28 +144,37 @@ pub fn parse() -> Option<CliArgs> {
None => Box::new(stdin()) None => Box::new(stdin())
}; };
let output = app let wrapped_writer = match app
.value_of("OUTPUT") .value_of("OUTPUT")
.and_then(|s| if s == "-" { None } else { Some(s) }) .and_then(|s| if s == "-" { None } else { Some(s) }) {
.map(|s| { Some(path) => {
if Path::new(s).exists() { if {
eprintln!("WARNING: {} already exists", s); if app.is_present("2_interactive") && Path::new(path).exists() {
None eprint!("Warning: {} already exists. Overwrite [y/N]? ", path);
} else { let mut c = String::with_capacity(2);
Some(LazyWriter::from_path(s.to_owned())) 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())
}
} }
}) None => WrappedWriter::from_writer(stdout())
.unwrap_or_else(|| Some(LazyWriter::from_writer(stdout())))?; };
Some(CliArgs { Some(CliArgs {
password: app.value_of("1_password").into(), 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, argon2_params: params,
cipher, cipher,
block_size, block_size,
reader: input, reader: input,
writer: output, writer: wrapped_writer,
}) }.into())
} }
fn number<T: FromStr>(val: &str) -> Option<T> { fn number<T: FromStr>(val: &str) -> Option<T> {

View File

@ -1,7 +1,7 @@
pub mod cli; pub mod cli;
pub mod crypto; 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 crypto::{DobyCipher, EncryptionParams};
use zeroize::Zeroize; use zeroize::Zeroize;
@ -36,37 +36,33 @@ impl From<Option<&str>> for WrappedPassword {
Self(s.map(String::from)) Self(s.map(String::from))
} }
} }
pub enum WrappedWriter<P: AsRef<Path>> {
pub struct LazyWriter<P: AsRef<Path>> { PATH {
path: Option<P>, path: P
writer: Option<Box<dyn Write>>, },
WRITER {
writer: Box<dyn Write>
}
} }
impl<P: AsRef<Path>> LazyWriter<P> { impl<P: AsRef<Path> + Display> WrappedWriter<P> {
fn from_path(path: P) -> Self { fn from_path(path: P) -> Self {
Self { Self::PATH { path }
path: Some(path),
writer: None,
}
} }
fn from_writer<T: 'static + Write>(writer: T) -> Self { fn from_writer<T: 'static + Write>(writer: T) -> Self {
Self { Self::WRITER { writer: Box::new(writer) }
path: None,
writer: Some(Box::new(writer)),
}
}
}
impl<P: AsRef<Path>> Write for LazyWriter<P> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
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)
} }
fn flush(&mut self) -> io::Result<()> { pub fn into_buf_writer(self) -> Option<BufWriter<Box<dyn Write>>> {
self.writer.as_mut().unwrap().flush() 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<dyn Write>,
Self::WRITER { writer } => writer,
}))
} }
} }

View File

@ -1,4 +1,4 @@
use std::{process, io::{BufWriter, BufReader, Read}}; use std::{process, io::{BufReader, Read}};
use doby::{ use doby::{
cli, cli,
crypto::{EncryptionParams, DobyCipher}, crypto::{EncryptionParams, DobyCipher},
@ -10,58 +10,68 @@ use zeroize::Zeroize;
fn run() -> bool { fn run() -> bool {
let mut success = false; let mut success = false;
if let Some(cli_args) = cli::parse() { if let Some(result) = cli::parse() {
let mut reader = BufReader::with_capacity(cli_args.block_size, cli_args.reader); if let Some(cli_args) = result.cli_args {
let mut writer = BufWriter::with_capacity(cli_args.block_size, cli_args.writer); let mut reader = BufReader::new(cli_args.reader);
let mut magic_bytes = vec![0; MAGIC_BYTES.len()]; let mut magic_bytes = vec![0; MAGIC_BYTES.len()];
match reader.read(&mut magic_bytes) { match reader.read(&mut magic_bytes) {
Ok(n) => { Ok(n) => {
if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt
match EncryptionParams::read(&mut reader) { match EncryptionParams::read(&mut reader) {
Ok(params) => { Ok(params) => {
match params { if let Some(params) = params {
Some(params) => {
if let Some(mut password) = cli_args.password.get(false) { if let Some(mut password) = cli_args.password.get(false) {
let cipher = DobyCipher::new(password.as_bytes(), &params); if let Some(mut writer) = cli_args.writer.into_buf_writer() {
password.zeroize(); let cipher = DobyCipher::new(password.as_bytes(), &params);
match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) { password.zeroize();
Ok(verified) => { match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) {
if verified { Ok(verified) => {
success = true if verified {
} else { success = true
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."); } 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);
} else { //otherwise, encrypt if let Some(mut password) = cli_args.password.get(true) {
let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher); if let Some(mut writer) = cli_args.writer.into_buf_writer() {
if let Some(mut password) = cli_args.password.get(true) { let cipher = DobyCipher::new(password.as_bytes(), &params);
let cipher = DobyCipher::new(password.as_bytes(), &params); password.zeroize();
password.zeroize(); match encrypt(
match encrypt( &mut reader,
&mut reader, &mut writer,
&mut writer, &params,
&params, cipher,
cipher, cli_args.block_size,
cli_args.block_size, Some(&magic_bytes[..n])
Some(&magic_bytes[..n]) ) {
) { Ok(_) => success = true,
Ok(_) => success = true, Err(e) => eprintln!("I/O error while encrypting: {}", e)
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 success