use std::{io::stdin, os::unix::ffi::OsStrExt, process::ExitCode, str::from_utf8};
use btoi::btoi;
use digest::Digest;
use nix::errno::Errno;
use rpassword::{prompt_password, read_password_from_bufread};
use syd::hash::{
add_key, Key, KeySerial, SafeHash, KEY_SIZE, KEY_SPEC_PROCESS_KEYRING,
KEY_SPEC_SESSION_KEYRING, KEY_SPEC_THREAD_KEYRING, KEY_SPEC_USER_KEYRING,
KEY_SPEC_USER_SESSION_KEYRING,
};
use zeroize::Zeroize;
#[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;
syd::main! {
use lexopt::prelude::*;
syd::set_sigpipe_dfl()?;
let mut opt_desc = None;
let mut opt_type = None;
let mut opt_ring = None;
let mut opt_pass = false;
let mut opt_pinp = false;
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
match arg {
Short('h') => {
help();
return Ok(ExitCode::SUCCESS);
}
Short('d') => opt_desc = Some(from_utf8(parser.value()?.as_bytes())?.to_string()),
Short('t') => opt_type = Some(from_utf8(parser.value()?.as_bytes())?.to_string()),
Short('k') => opt_ring = Some(keyspec2serial(parser.value()?.as_bytes())?),
Short('p') => opt_pass = true,
Short('P') => opt_pinp = true,
_ => return Err(arg.unexpected().into()),
}
}
if opt_pass && opt_pinp {
eprintln!("syd-key: -p and -P are mutually exclusive!");
return Err(Errno::EINVAL.into());
}
let keydesc = opt_desc.unwrap_or_else(|| "SYD-3-CRYPT".to_string());
let keytype = opt_type.unwrap_or_else(|| "user".to_string());
let keyring = opt_ring.unwrap_or(KEY_SPEC_USER_KEYRING);
let key_id = {
const _: () = assert!(KEY_SIZE == 32, "SafeHash digest size must match KEY_SIZE");
let key = if opt_pass { let mut pass = prompt_password("Passphrase: ")?;
let hash: [u8; KEY_SIZE] =
<SafeHash as Digest>::digest(pass.as_bytes()).into();
pass.zeroize();
Key::new(hash)
} else if opt_pinp { let mut stdin = stdin().lock();
let mut pass = read_password_from_bufread(&mut stdin)?;
let hash: [u8; KEY_SIZE] =
<SafeHash as Digest>::digest(pass.as_bytes()).into();
pass.zeroize();
Key::new(hash)
} else { Key::random()?
};
add_key(&keytype, &keydesc, key.as_ref(), keyring)?
};
println!("{key_id}");
Ok(ExitCode::SUCCESS)
}
fn help() {
println!("Usage: syd-key [-hpP] [-d keydesc] [-t keytype] [-k keyring]");
println!("Utility to generate encryption keys and save to keyrings(7)");
println!("Options:");
println!(" -h Print this help message and exit.");
println!(" -p Read passphrase from controlling TTY but NOT stdin(3)!");
println!(" Hash passphrase using SHA3-256 to generate encryption key.");
println!(" Default is to generate key using getrandom(2) with GRND_RANDOM flag.");
println!(" -P Read passphrase from stdin(3) rather than TTY.");
println!(" -d keydesc Specify alternative key description. Default is 'SYD-3-CRYPT'.");
println!(" -t keytype Specify alternative key type. Default is 'user'.");
println!(" -k keyring Specify alternative key type. Default is 'KEY_SPEC_USER_KEYRING'.");
println!(
" May be exactly one of thread, process, session, user or user-session."
);
println!(" May also be a 32-bit decimal number specifying a keyring ID.");
}
fn keyspec2serial(spec: &[u8]) -> Result<KeySerial, Errno> {
match spec {
b"thread" => Ok(KEY_SPEC_THREAD_KEYRING),
b"process" => Ok(KEY_SPEC_PROCESS_KEYRING),
b"session" => Ok(KEY_SPEC_SESSION_KEYRING),
b"user" => Ok(KEY_SPEC_USER_KEYRING),
b"user-session" => Ok(KEY_SPEC_USER_SESSION_KEYRING),
other => {
btoi::<KeySerial>(other).map_err(|_| Errno::EINVAL)
}
}
}