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::{
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<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!())
.version(crate_version!())
.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("OUTPUT").help("<PATH> | \"-\" 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<CliArgs> {
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<CliArgs> {
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<CliArgs> {
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<CliArgs> {
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<T: FromStr>(val: &str) -> Option<T> {

View File

@ -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<Option<&str>> for WrappedPassword {
Self(s.map(String::from))
}
}
pub struct LazyWriter<P: AsRef<Path>> {
path: Option<P>,
writer: Option<Box<dyn Write>>,
pub enum WrappedWriter<P: AsRef<Path>> {
PATH {
path: P
},
WRITER {
writer: Box<dyn Write>
}
}
impl<P: AsRef<Path>> LazyWriter<P> {
impl<P: AsRef<Path> + Display> WrappedWriter<P> {
fn from_path(path: P) -> Self {
Self {
path: Some(path),
writer: None,
}
Self::PATH { path }
}
fn from_writer<T: 'static + Write>(writer: T) -> Self {
Self {
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)
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<BufWriter<Box<dyn Write>>> {
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::{
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(), &params);
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(), &params);
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(), &params);
password.zeroize();
match encrypt(
&mut reader,
&mut writer,
&params,
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(), &params);
password.zeroize();
match encrypt(
&mut reader,
&mut writer,
&params,
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