use std::{io::stdin, os::unix::ffi::OsStrExt, process::ExitCode, str::from_utf8};
use btoi::btoi;
use nix::errno::Errno;
use rpassword::{prompt_password, read_password_with_config, ConfigBuilder};
use sha3::{Digest, Sha3_256};
use syd::{
hash::{
add_key, key_setperm, Key, KeyPerms, KeySerial, KEY_SIZE, KEY_SPEC_PROCESS_KEYRING,
KEY_SPEC_SESSION_KEYRING, KEY_SPEC_THREAD_KEYRING, KEY_SPEC_USER_KEYRING,
KEY_SPEC_USER_SESSION_KEYRING,
},
parsers::sandbox::str2u32,
};
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_perm = 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(str2keyserial(parser.value()?.as_bytes())?),
Short('m') => opt_perm = Some(str2keyperms(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, "SHA3-256 digest size must match KEY_SIZE");
let key = if opt_pass { let mut pass = prompt_password("Passphrase: ")?;
let hash: [u8; KEY_SIZE] = Sha3_256::digest(pass.as_bytes()).into();
pass.zeroize();
Key::new(hash)
} else if opt_pinp { let cfg = ConfigBuilder::new()
.input_reader(stdin())
.build();
let mut pass = read_password_with_config(cfg)?;
let hash: [u8; KEY_SIZE] = Sha3_256::digest(pass.as_bytes()).into();
pass.zeroize();
Key::new(hash)
} else { Key::random()?
};
let key_id = add_key(&keytype, &keydesc, key.as_ref(), keyring)?;
if let Some(perms) = opt_perm {
key_setperm(key_id, perms)?;
}
key_id
};
println!("{key_id}");
Ok(ExitCode::SUCCESS)
}
fn help() {
println!("Usage: syd-key [-hpP] [-d keydesc] [-t keytype] [-k keyring] [-m keyperm]");
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 ring ID. 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.");
println!(
" -m keyperm Specify keyctl(1) permission mask (hex 0x.., octal 0o.., or decimal)."
);
println!(
" When omitted, the key keeps its default permissions, which Syd reads."
);
}
fn str2keyperms(spec: &[u8]) -> Result<KeyPerms, Errno> {
Ok(KeyPerms::from_bits_retain(str2u32(spec)?))
}
fn str2keyserial(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),
}
}