use std::io::{Read, Write};
use std::path::PathBuf;
use chrono::DateTime;
use hex::ToHex;
use pgp::packet::{Signature, SignatureConfig};
use pgp::types::{KeyDetails, Timestamp};
use rpgpie::certificate::{Checked, SignatureVerifier};
fn signer_fps(sc: &SignatureConfig) -> Vec<String> {
let fingerprints = sc.issuer_fingerprint();
fingerprints
.iter()
.map(|fp| fp.as_bytes())
.map(hex::encode)
.collect()
}
fn signer_key_ids(sc: &SignatureConfig) -> Vec<String> {
let key_ids = sc.issuer_key_id();
key_ids.iter().map(|key_id| key_id.encode_hex()).collect()
}
fn yolo_signer_id(sc: &SignatureConfig) -> String {
let fps = signer_fps(sc);
if !fps.is_empty() {
fps[0].clone() } else {
let kid = signer_key_ids(sc);
if !kid.is_empty() {
kid[0].clone() } else {
"[unknown signer id]".to_string()
}
}
}
pub fn verify(
mut data: impl Read,
mut out: impl Write,
mut err: impl Write,
sig_path: &PathBuf,
cert_store_path: Option<&PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
log::trace!("verify start");
let store = crate::open_store(cert_store_path)?;
log::trace!("verify open_store");
let mut buffer = vec![];
std::io::copy(&mut data, &mut buffer)?;
let sig = &rpgpie::signature::load(&mut std::fs::File::open(sig_path)?)?[0];
let Some(conf) = sig.config() else {
unimplemented!()
};
let fps_hex = signer_fps(conf);
let sig_creation = conf.created().expect("FIXME");
log::trace!("verify sig setup done");
let mut certs: Vec<Checked> = fps_hex
.iter()
.flat_map(|fpr| store.search_and_poll_by_fingerprint(fpr).ok())
.flatten()
.map(|c| Checked::new(c.clone()))
.filter(|c| {
!c.valid_signing_capable_component_keys_at(sig_creation)
.is_empty()
})
.collect::<Vec<_>>();
log::trace!("verify: cert store lookup by fp");
let key_ids_hex = signer_key_ids(conf);
if certs.is_empty() {
certs = key_ids_hex
.iter()
.flat_map(|key_id| store.search_and_poll_by_key_id(key_id).ok())
.flatten()
.map(|c| Checked::new(c.clone()))
.filter(|c| {
!c.valid_signing_capable_component_keys_at(sig_creation)
.is_empty()
})
.collect::<Vec<_>>();
}
log::trace!("verify: cert store lookup by key id");
let valid_sigs = certs
.iter()
.flat_map(|ccert| {
log::trace!("verify: got ccert");
ccert
.valid_signing_capable_component_keys_at(sig_creation)
.into_iter()
.filter_map(|verifier| {
verifier
.verify(sig, &buffer)
.map(|_| (ccert.clone(), verifier))
.ok()
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
log::trace!("verify: validation done");
print_results(sig, &certs, valid_sigs, &mut out, &mut err)?;
log::trace!("verify: print done");
Ok(())
}
fn print_results(
sig: &Signature,
ccerts: &[Checked],
valid_sigs: Vec<(Checked, SignatureVerifier)>,
mut out: impl Write,
mut err: impl Write,
) -> Result<(), Box<dyn std::error::Error>> {
let Some(conf) = sig.config() else {
unimplemented!()
};
writeln!(
err,
"oct-git: Signature created {}",
conf.created()
.map(|ts| {
DateTime::from_timestamp(ts.as_secs() as i64, 0)
.expect("DateTime")
.to_string()
})
.unwrap_or("[missing creation time]".to_string())
)?;
writeln!(
err,
"oct-git: by {:?} key {}",
conf.pub_alg,
yolo_signer_id(conf)
)?;
if ccerts.is_empty() {
writeln!(
err,
"oct-git: Can't check signature: Certificate not available."
)?;
return Ok(());
}
for (ccert, verifier) in &valid_sigs {
let verifier_fp = verifier.as_componentkey().fingerprint();
let now = Timestamp::now();
let cert_revoked = ccert.revoked_at(now);
let cert_invalid = !ccert.primary_valid_at(now)?;
let (ckey_revoked, ckey_invalid) = if *ccert.fingerprint() == verifier_fp {
(cert_revoked, cert_invalid)
} else {
let status = rpgpie::model::status_summary(ccert);
let Some(key_summary) = status
.subkeys
.iter()
.find(|key| key.fingerprint == hex::encode(verifier_fp.as_bytes()))
else {
unimplemented!("FIXME");
};
let ckey_revoked = cert_revoked || key_summary.status.is_revoked();
let ckey_invalid = cert_invalid || !key_summary.status.is_valid();
(ckey_revoked, ckey_invalid)
};
fn revoked_str(rev: bool) -> &'static str {
if rev {
" [revoked]"
} else {
""
}
}
fn invalid_str(rev: bool) -> &'static str {
if rev {
" [expired]"
} else {
""
}
}
writeln!(
err,
"oct-git: Good signature by {}{}{}",
hex::encode(verifier_fp.as_bytes()),
revoked_str(cert_revoked | ckey_revoked),
invalid_str(!(cert_revoked | ckey_revoked) && (cert_invalid | ckey_invalid)),
)?;
writeln!(
err,
"oct-git: Certificate {}{}{}",
hex::encode(ccert.fingerprint().as_bytes()),
revoked_str(cert_revoked),
invalid_str(!cert_revoked && cert_invalid),
)?;
if let Some(signed_user) = ccert.primary_user_id() {
let user_id = String::from_utf8_lossy(signed_user.id.id());
writeln!(err, "oct-git: Signer User ID \"{}\"", user_id)?;
writeln!(
out,
"\n[GNUPG:] GOODSIG {} {}",
hex::encode_upper(verifier.as_componentkey().legacy_key_id()),
user_id
)?;
} else {
writeln!(
out,
"\n[GNUPG:] GOODSIG {}",
hex::encode_upper(verifier.as_componentkey().legacy_key_id()),
)?;
}
write!(
out,
"[GNUPG:] VALIDSIG {}",
verifier
.as_componentkey()
.fingerprint()
.as_bytes()
.encode_hex_upper::<String>()
)?;
}
if valid_sigs.is_empty() {
writeln!(
err,
"oct-git: No valid signature by key {}",
signer_fps(conf)
.into_iter()
.chain(signer_key_ids(conf).into_iter())
.collect::<Vec<_>>()
.join(" ")
)?;
}
Ok(())
}