use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::capability;
use crate::error::{Error, Result};
use crate::pki;
use super::{CommonArgs, FingerprintSubcmd, KeygenSubcmd, SignSubcmd, VerifySigSubcmd};
pub fn keygen(_common: CommonArgs, a: KeygenSubcmd) -> Result<()> {
let kind: pki::SigAlgKind = a.alg.parse()?;
let mut rng = botan::RandomNumberGenerator::new_system().map_err(Error::botan)?;
let (priv_pem, pub_pem) = pki::keygen(kind, &mut rng)?;
write_key_or_stdout(a.out_priv.as_deref(), priv_pem.as_bytes())?;
write_key_or_stdout(a.out_pub.as_deref(), pub_pem.as_bytes())?;
Ok(())
}
pub fn sign(_common: CommonArgs, a: SignSubcmd) -> Result<()> {
if a.key.is_empty() {
return Err(Error::InvalidArg {
arg: "key-file",
reason: "sign: at least one --key-file is required".to_string(),
});
}
let kind: pki::SigAlgKind = a.alg.parse()?;
let msg = read_file_or_stdin(a.input.as_deref())?;
let mut rng = botan::RandomNumberGenerator::new_system().map_err(Error::botan)?;
let out_path = match (&a.out, &a.input) {
(Some(p), _) => p.clone(),
(None, Some(input)) => append_sig_ext(input),
(None, None) => PathBuf::from("-"),
};
if a.key.len() == 1 {
let priv_pem = fs::read_to_string(&a.key[0])?;
let sig = pki::sign(kind, &priv_pem, &msg, &mut rng)?;
if out_path == Path::new("-") {
std::io::stdout().write_all(&sig)?;
} else {
fs::write(&out_path, &sig)?;
}
return Ok(());
}
let mut entries = Vec::with_capacity(a.key.len());
for key_path in &a.key {
let priv_pem = fs::read_to_string(key_path)?;
let botan_priv = botan::Privkey::load_pem(&priv_pem).map_err(Error::botan)?;
let botan_pub = botan_priv.pubkey().map_err(Error::botan)?;
let pub_pem = botan_pub.pem_encode().map_err(Error::botan)?;
let fp = capability::KeyFp::from_pem(&pub_pem)?;
let sig = pki::sign(kind, &priv_pem, &msg, &mut rng)?;
entries.push(pki::SigEntry {
alg: kind,
fp: fp.to_hex(),
sig,
});
}
let bundle = pki::SigBundle { entries };
let body = bundle.serialize();
if out_path == Path::new("-") {
std::io::stdout().write_all(body.as_bytes())?;
} else {
fs::write(&out_path, body.as_bytes())?;
}
Ok(())
}
pub fn verify_sig(_common: CommonArgs, a: VerifySigSubcmd) -> Result<()> {
if a.key.is_empty() {
return Err(Error::InvalidArg {
arg: "key-file",
reason: "verify-sig: at least one --key-file is required".to_string(),
});
}
let kind: pki::SigAlgKind = a.alg.parse()?;
let sig_bytes = match (&a.sig, &a.input) {
(Some(p), _) => fs::read(p)?,
(None, Some(input)) => fs::read(append_sig_ext(input))?,
(None, None) => {
return Err(Error::InvalidArg {
arg: "sig",
reason: "verify-sig: no signature file or input file given".to_string(),
});
}
};
let msg = read_file_or_stdin(a.input.as_deref())?;
if a.key.len() == 1 {
let pub_pem = fs::read_to_string(&a.key[0])?;
let ok = pki::verify(kind, &pub_pem, &msg, &sig_bytes)?;
return if ok {
Ok(())
} else {
Err(Error::SignatureVerify {
key_id: "verify-sig".to_string(),
})
};
}
let body = String::from_utf8(sig_bytes).map_err(|e| Error::InvalidArg {
arg: "sig",
reason: format!("signature bundle is not UTF-8: {e}"),
})?;
let bundle = pki::SigBundle::parse(&body)?;
if bundle.entries.len() != a.key.len() {
return Err(Error::InvalidArg {
arg: "sig",
reason: format!(
"verify-sig: {} signatures in bundle but {} pubkeys supplied",
bundle.entries.len(),
a.key.len()
),
});
}
let mut by_fp: HashMap<String, String> = HashMap::new();
for key_path in &a.key {
let pem = fs::read_to_string(key_path)?;
let fp = capability::KeyFp::from_pem(&pem)?;
by_fp.insert(fp.to_hex(), pem);
}
for entry in &bundle.entries {
let pem = by_fp.get(&entry.fp).ok_or_else(|| Error::SignatureVerify {
key_id: format!("no pubkey for fp {}", entry.fp),
})?;
let ok = pki::verify(entry.alg, pem, &msg, &entry.sig)?;
if !ok {
return Err(Error::SignatureVerify {
key_id: format!("fp {}", entry.fp),
});
}
}
Ok(())
}
fn append_sig_ext(input: &Path) -> PathBuf {
let mut p = input.to_path_buf();
if p.extension().and_then(|e| e.to_str()) == Some("sig") {
return p;
}
match p.extension() {
Some(e) => {
let mut new_ext = e.to_os_string();
new_ext.push(".sig");
p.set_extension(new_ext);
}
None => {
p.set_extension("sig");
}
}
p
}
pub fn fingerprint(a: FingerprintSubcmd) -> Result<()> {
let pem = fs::read_to_string(&a.key)?;
let fp = capability::KeyFp::from_pem(&pem)?;
println!("{}", fp);
Ok(())
}
fn read_file_or_stdin(path: Option<&Path>) -> Result<Vec<u8>> {
match path {
Some(p) if p != Path::new("-") => Ok(fs::read(p)?),
_ => {
use std::io::Read;
let mut buf = Vec::new();
std::io::stdin().read_to_end(&mut buf)?;
Ok(buf)
}
}
}
fn write_key_or_stdout(path: Option<&Path>, data: &[u8]) -> Result<()> {
match path {
Some(p) if p != Path::new("-") => {
fs::write(p, data)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(p, fs::Permissions::from_mode(0o600))?;
}
}
_ => {
std::io::stdout().write_all(data)?;
}
}
Ok(())
}