use std::collections::HashMap;
use pgp::{
packet::{PublicKey, Signature, SignatureType, UserId},
types::{Fingerprint, KeyDetails, KeyId, Tag, Timestamp},
};
use crate::certificate::Checked;
pub struct CertificateIndex<'a> {
by_fp: HashMap<Fingerprint, Vec<&'a Checked>>,
by_keyid: HashMap<KeyId, Vec<&'a Checked>>,
}
impl<'a> CertificateIndex<'a> {
pub fn index_as_certifier(checked: &'a [Checked]) -> Self {
let index_primary = |cert: &Checked| vec![(cert.fingerprint().clone(), cert.key_id())];
Self::initialize_index(checked, &index_primary)
}
pub fn index_as_data_signer(checked: &'a [Checked]) -> Self {
let now = Timestamp::now();
let index_data_signers = |cert: &Checked| {
cert.component_keys()
.filter(|sckp| {
sckp.key_flags_at(now)
.map(|flag| flag.sign())
.unwrap_or(false)
})
.map(|sckp| (sckp.fingerprint(), sckp.legacy_key_id()))
.collect()
};
Self::initialize_index(checked, &index_data_signers)
}
fn initialize_index(
checked: &'a [Checked],
indexer: &dyn Fn(&Checked) -> Vec<(Fingerprint, KeyId)>,
) -> Self {
let mut by_fp = HashMap::new();
let mut by_keyid = HashMap::new();
for c in checked {
for (fp, key_id) in indexer(c) {
let values = by_fp.entry(fp).or_insert(vec![]);
values.push(c);
let values = by_keyid.entry(key_id).or_insert(vec![]);
values.push(c);
}
}
Self { by_fp, by_keyid }
}
pub fn lookup_fingerprint(&self, fp: &Fingerprint) -> &[&'a Checked] {
self.by_fp.get(fp).map_or(&[], |v| v)
}
pub fn lookup_keyid(&self, keyid: &KeyId) -> &[&'a Checked] {
self.by_keyid.get(keyid).map_or(&[], |v| v)
}
}
pub struct CertifierLookup<'a> {
index: CertificateIndex<'a>,
}
impl<'a> CertifierLookup<'a> {
pub fn new(checked: &'a [Checked]) -> Self {
let index = CertificateIndex::index_as_certifier(checked);
Self { index }
}
pub fn find_userid_signer(
&self,
sig: &Signature,
signee: &PublicKey,
user_id: &UserId,
) -> Option<&'a Checked> {
let verify = |signee: &PublicKey, signer: &PublicKey| {
sig.verify_third_party_certification(signee, signer, Tag::UserId, user_id)
.is_ok()
};
self.find_signer(sig, signee, &verify)
}
pub fn find_direct_key_signer(
&self,
sig: &Signature,
signee: &PublicKey,
) -> Option<&'a Checked> {
let verify = |signee: &PublicKey, signer: &PublicKey| {
sig.verify_key_third_party(signee, signer).is_ok()
};
self.find_signer(sig, signee, &verify)
}
fn candidates(&self, sig: &Signature) -> Vec<&'a Checked> {
let mut cand = Vec::new();
for fp in sig.issuer_fingerprint() {
for &c in self.index.lookup_fingerprint(fp) {
cand.push(c);
}
}
for keyid in sig.issuer_key_id() {
for &c in self.index.lookup_keyid(keyid) {
cand.push(c);
}
}
cand
}
fn find_signer(
&self,
sig: &Signature,
signee: &PublicKey,
verify: &dyn Fn(&PublicKey, &PublicKey) -> bool,
) -> Option<&'a Checked> {
self.candidates(sig)
.into_iter()
.find(|&c| verify(signee, &c.as_signed_public_key().primary_key))
}
pub fn valid_userid_certifications(
&self,
signatures: &[&'a Signature],
target: &Checked,
target_user: &UserId,
reference_time: Timestamp,
) -> Vec<(&'a Checked, Vec<&'a Signature>)> {
const FILTER: &[Option<SignatureType>] = &[
Some(SignatureType::CertPositive),
Some(SignatureType::CertGeneric),
Some(SignatureType::CertCasual),
Some(SignatureType::CertRevocation),
];
let lookup = |sig| {
self.find_userid_signer(sig, &target.as_signed_public_key().primary_key, target_user)
};
Self::collect_third_party(signatures, reference_time, Box::new(lookup), FILTER)
}
pub fn valid_direct_signatures(
&self,
signatures: &[&'a Signature],
target: &Checked,
reference_time: Timestamp,
) -> Vec<(&'a Checked, Vec<&'a Signature>)> {
const FILTER: &[Option<SignatureType>] = &[Some(SignatureType::Key)];
let lookup =
|sig| self.find_direct_key_signer(sig, &target.as_signed_public_key().primary_key);
Self::collect_third_party(signatures, reference_time, Box::new(lookup), FILTER)
}
fn collect_third_party(
signatures: &[&'a Signature],
reference_time: Timestamp,
lookup: Box<impl Fn(&'a Signature) -> Option<&'a Checked>>,
sigtype_filter: &[Option<SignatureType>],
) -> Vec<(&'a Checked, Vec<&'a Signature>)> {
let mut map = HashMap::new();
for &sig in signatures {
if !sigtype_filter.contains(&sig.typ()) {
continue;
}
if !crate::signature::signature_acceptable(sig) {
continue;
}
let Some(sig_created) = sig.created() else {
continue;
};
if sig_created > reference_time {
log::trace!("skipping signature from the future {:?}", sig);
continue;
}
if let Some(exp) = sig.signature_expiration_time() {
if exp.as_secs() != 0 {
let expires = Timestamp::from_secs(sig_created.as_secs() + exp.as_secs());
if expires <= reference_time {
log::trace!("skipping expired sig {:?}", sig);
continue;
}
}
}
let Some(signer) = lookup(sig) else {
continue;
};
log::debug!(" found signer: {:?}", signer.fingerprint());
if !signer.primary_valid_at(sig_created).unwrap_or(false) {
continue;
}
if !signer.primary_valid_at(reference_time).unwrap_or(false) {
continue;
}
let entry: &mut (&Checked, Vec<&Signature>) =
map.entry(signer.fingerprint()).or_insert((signer, vec![]));
entry.1.push(sig);
}
map.into_values().collect()
}
}