doby/src/cli.rs

162 lines
4.9 KiB
Rust
Raw Normal View History

2021-06-25 23:01:50 +02:00
use std::{
path::Path,
fs::File,
str::FromStr,
2021-07-04 19:24:20 +02:00
io::{stdin, stdout, Read},
2021-06-25 23:01:50 +02:00
};
2021-06-27 20:35:23 +02:00
use clap::{crate_name, crate_version, App, Arg, AppSettings};
2021-11-13 19:08:28 +01:00
use crate::{LazyWriter, WrappedPassword, crypto::CipherAlgorithm};
2021-06-27 20:35:23 +02:00
cpufeatures::new!(aes_ni, "aes");
2021-06-25 23:01:50 +02:00
pub struct CliArgs {
2021-11-13 19:08:28 +01:00
pub password: WrappedPassword,
2021-06-25 23:01:50 +02:00
pub force_encrypt: bool,
2021-11-13 19:08:28 +01:00
pub argon2_params: argon2::Params,
2021-06-27 20:35:23 +02:00
pub cipher: CipherAlgorithm,
2021-06-25 23:01:50 +02:00
pub block_size: usize,
pub reader: Box<dyn Read>,
2021-07-04 19:24:20 +02:00
pub writer: LazyWriter<String>,
2021-06-25 23:01:50 +02:00
}
pub fn parse() -> Option<CliArgs> {
let app = App::new(crate_name!())
.version(crate_version!())
2021-06-27 20:35:23 +02:00
.setting(AppSettings::ColoredHelp)
2021-06-30 15:24:56 +02:00
.about("Secure symmetric encryption from the command line.")
2021-06-25 23:01:50 +02:00
.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")
.short("f")
.long("force-encrypt")
.help(&format!("Encrypt even if {} format is recognized", crate_name!()))
)
.arg(
2021-06-30 15:24:56 +02:00
Arg::with_name("1_password")
2021-06-25 23:01:50 +02:00
.long("password")
.value_name("password")
2021-06-27 20:35:23 +02:00
.help("Password used to derive encryption keys")
2021-06-25 23:01:50 +02:00
)
.arg(
2021-06-30 15:24:56 +02:00
Arg::with_name("2_t_cost")
2021-11-13 19:08:28 +01:00
.short("t")
.long("time-cost")
.value_name("number of iterations")
2021-06-25 23:01:50 +02:00
.help("Argon2 time cost")
.default_value("10")
)
.arg(
2021-06-30 15:24:56 +02:00
Arg::with_name("3_m_cost")
2021-06-25 23:01:50 +02:00
.short("m")
.long("memory-cost")
.value_name("memory cost")
.help("Argon2 memory cost (in kilobytes)")
.default_value("4096")
)
.arg(
2021-11-13 19:08:28 +01:00
Arg::with_name("4_p_cost")
.short("p")
.long("parallelism")
.value_name("degree of parallelism")
.help("Argon2 parallelism cost")
2021-06-25 23:01:50 +02:00
.default_value("4")
)
.arg(
Arg::with_name("blocksize")
.short("b")
.long("block-size")
2021-07-08 18:16:50 +02:00
.help("Size of the I/O buffer (in bytes)")
2021-06-25 23:01:50 +02:00
.default_value("65536")
)
2021-06-27 20:35:23 +02:00
.arg(
Arg::with_name("cipher")
.short("c")
.long("cipher")
.value_name("cipher")
.help("Encryption cipher to use")
.long_help("Encryption cipher to use. By default, AES is selected if AES-NI is supported. Otherwise, XChaCha20 is used.")
.possible_values(&["aes", "xchacha20"])
.case_insensitive(true)
)
2021-06-25 23:01:50 +02:00
.get_matches();
let params = {
2021-06-30 15:24:56 +02:00
let t_cost = number(app.value_of("2_t_cost").unwrap())?;
let m_cost = number(app.value_of("3_m_cost").unwrap())?;
2021-11-13 19:08:28 +01:00
let p_cost = number(app.value_of("4_p_cost").unwrap())?;
2021-06-25 23:01:50 +02:00
2021-11-13 19:08:28 +01:00
match argon2::Params::new(m_cost, t_cost, p_cost, None) {
Ok(params) => Some(params),
Err(e) => {
eprintln!("Invalid Argon2 parameters: {}", e);
None
}
2021-06-25 23:01:50 +02:00
}
2021-11-13 19:08:28 +01:00
}?;
2021-06-25 23:01:50 +02:00
2021-06-27 20:35:23 +02:00
let cipher = app
.value_of("cipher")
2021-08-31 11:23:15 +02:00
.map(|s| if s.to_lowercase() == "aes" {
2021-06-27 20:35:23 +02:00
CipherAlgorithm::AesCtr
} else {
CipherAlgorithm::XChaCha20
2021-08-31 11:23:15 +02:00
}
2021-06-27 20:35:23 +02:00
)
.unwrap_or_else(|| if aes_ni::get() {
CipherAlgorithm::AesCtr
} else {
CipherAlgorithm::XChaCha20
}
);
2021-06-25 23:01:50 +02:00
let block_size = number(app.value_of("blocksize").unwrap())?;
let input = match app
2021-06-25 23:01:50 +02:00
.value_of("INPUT")
.and_then(|s| if s == "-" { None } else { Some(s) })
{
Some(s) =>
Box::new(
File::open(s)
.map_err(|e| eprintln!("{}: {}", s, e))
.ok()?
) as Box<dyn Read>
,
None => Box::new(stdin())
};
2021-06-25 23:01:50 +02:00
let output = 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);
2021-06-25 23:01:50 +02:00
None
} else {
2021-07-04 19:24:20 +02:00
Some(LazyWriter::from_path(s.to_owned()))
2021-06-25 23:01:50 +02:00
}
})
2021-07-04 19:24:20 +02:00
.unwrap_or_else(|| Some(LazyWriter::from_writer(stdout())))?;
2021-06-25 23:01:50 +02:00
Some(CliArgs {
2021-07-04 16:24:44 +02:00
password: app.value_of("1_password").into(),
2021-06-25 23:01:50 +02:00
force_encrypt: app.is_present("force-encrypt"),
argon2_params: params,
2021-06-27 20:35:23 +02:00
cipher,
2021-06-25 23:01:50 +02:00
block_size,
reader: input,
2021-06-27 20:35:23 +02:00
writer: output,
2021-06-25 23:01:50 +02:00
})
}
fn number<T: FromStr>(val: &str) -> Option<T> {
match val.parse::<T>() {
Ok(n) => Some(n),
Err(_) => {
eprintln!("Cannot parse '{}' to '{}'", val, std::any::type_name::<T>());
None
}
}
}