use std::{
mem::MaybeUninit,
os::unix::ffi::OsStrExt,
process::ExitCode,
time::{Duration, Instant},
};
use btoi::btoi;
use ctr::cipher::StreamCipher;
use nix::unistd::isatty;
use syd::{
compat::slice_assume_init_mut,
config::IO_BUF_SIZE,
cookie::safe_read2,
err::SydResult,
hash::{aes_ctr, hex_decode, CryptKey, KeySerial, IV},
io::write_all,
retry::retry_on_eintr,
};
use zeroize::Zeroizing;
#[cfg(all(
not(coverage),
not(feature = "prof"),
not(target_os = "android"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;
fn process_data(encrypting: bool, key_id: KeySerial, iv: IV, verbose: bool) -> SydResult<()> {
let keys = CryptKey::load(key_id, key_id)?;
let mut cipher = aes_ctr(keys.enc(), &iv)?;
let stdin = std::io::stdin();
let stdout = std::io::stdout();
let stime = Instant::now();
let mut ltime = stime;
let mut nbytes: u64 = 0;
let mut nwrite: u64 = 0;
let verbose = verbose && isatty(std::io::stderr()).unwrap_or(false);
let mut buf: Zeroizing<[MaybeUninit<u8>; IO_BUF_SIZE]> =
Zeroizing::new([MaybeUninit::uninit(); IO_BUF_SIZE]);
loop {
let nread = match retry_on_eintr(|| safe_read2(&stdin, &mut buf[..]))? {
0 => break,
nread => nread,
};
let chunk = unsafe { slice_assume_init_mut(&mut buf[..nread]) };
cipher.apply_keystream(chunk);
write_all(&stdout, &*chunk)?;
nbytes = nbytes.saturating_add(nread as u64);
nwrite = nwrite.saturating_add(1);
if verbose {
let now = Instant::now();
if now.duration_since(ltime) >= Duration::from_millis(500) {
let elapsed = stime.elapsed();
let speed = nbytes as f64 / elapsed.as_secs_f64();
let output = format!(
"{} bytes ({:.2} GB, {:.2} GiB) processed, {:.2?} s, {:.2} MB/s",
nbytes,
nbytes as f64 / 1_000_000_000.0,
nbytes as f64 / (1 << 30) as f64,
elapsed,
speed / (1 << 20) as f64
);
eprint!("\r\x1B[K{output}");
ltime = now;
}
}
}
if verbose {
let action = if encrypting { "encrypted" } else { "decrypted" };
let elapsed = stime.elapsed();
eprintln!(
"\n{nwrite} records {action}.\n{} bytes ({:.2} GB, {:.2} GiB) processed, {:.5?} s, {:.2} MB/s",
nbytes,
nbytes as f64 / 1_000_000_000.0,
nbytes as f64 / (1 << 30) as f64,
elapsed,
nbytes as f64 / elapsed.as_secs_f64() / (1 << 20) as f64
);
}
Ok(())
}
syd::main! {
use lexopt::prelude::*;
syd::set_sigpipe_dfl()?;
let mut opt_encrypt = None;
let mut opt_key_id = None;
let mut opt_iv_hex = None;
let mut opt_verbose = false;
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
match arg {
Short('h') => {
help();
return Ok(ExitCode::SUCCESS);
}
Short('v') => opt_verbose = true,
Short('e') => opt_encrypt = Some(true),
Short('d') => opt_encrypt = Some(false),
Short('k') => opt_key_id = Some(btoi::<KeySerial>(parser.value()?.as_bytes())?),
Short('i') => opt_iv_hex = Some(parser.value()?.parse::<String>()?),
_ => return Err(arg.unexpected().into()),
}
}
let is_enc = if let Some(is_enc) = opt_encrypt {
is_enc
} else {
eprintln!("syd-aes: Error: -e or -d options are required.");
help();
return Ok(ExitCode::FAILURE);
};
let key_id = if let Some(key_id) = opt_key_id {
key_id
} else {
eprintln!("syd-aes: Error: -k option is required.");
help();
return Ok(ExitCode::FAILURE);
};
if opt_iv_hex.is_none() {
eprintln!("syd-aes: Error: -i option is required.");
help();
return Ok(ExitCode::FAILURE);
}
let iv = match opt_iv_hex
.and_then(|hex| hex_decode(hex.as_bytes()).ok())
.and_then(|vec| vec.as_slice().try_into().ok())
{
Some(iv) => IV::new(iv),
None => {
eprintln!("syd-aes: Error: IV must be valid hex, and 128 bits (16 bytes) in length!");
return Ok(ExitCode::FAILURE);
}
};
process_data(is_enc, key_id, iv, opt_verbose).map(|_| ExitCode::SUCCESS)
}
fn help() {
println!("Usage: syd-aes [-h] -e|-d -k <key-serial> -i <iv-hex>");
println!("AES-CTR Encryption and Decryption Utility");
println!("Reads from standard input and writes to standard output.");
println!(" -h Print this help message and exit.");
println!(" -v Enable verbose mode.");
println!(" -e Encrypt the input data.");
println!(" -d Decrypt the input data.");
println!(" -k <key> Key serial ID for keyrings(7) (32-bit integer)");
println!(" Key must have read permission.");
println!(" -i <iv> Hex-encoded IV (128 bits)");
}