Overwrite by default
This commit is contained in:
parent
2c8ab7e8ad
commit
311677d195
79
src/cli.rs
79
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<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> {
|
||||
|
46
src/lib.rs
46
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<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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
90
src/main.rs
90
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
|
||||
|
Loading…
Reference in New Issue
Block a user