use std::{collections::HashMap, ops::Add};
use chrono::{DateTime, Utc};
use pgp::{
packet::{PublicKey, Signature, SignatureType, UserId},
types::{Fingerprint, KeyId, Tag},
};
use rpgpie::certificate::Checked;
use wot_network::{Certificate, Network};
use crate::util::{pick_latest, regexes_from, to_certificate, to_identity, user_id_valid};
pub(crate) struct ThirdPartyLookup<'a> {
by_fp: HashMap<Fingerprint, &'a Checked>,
by_keyid: HashMap<KeyId, Vec<&'a Checked>>,
}
impl<'a> ThirdPartyLookup<'a> {
pub(crate) fn new(checked: &'a Vec<Checked>) -> Self {
let mut by_fp = HashMap::new();
let mut by_keyid = HashMap::new();
for c in checked {
by_fp.insert(c.fingerprint().clone(), c);
let values = by_keyid.entry(c.key_id()).or_insert(vec![]);
values.push(c);
}
ThirdPartyLookup { by_fp, by_keyid }
}
pub(crate) fn find_third_party_userid_signer(
&self,
sig: &Signature,
signee: &PublicKey,
user_id: &UserId,
) -> Option<&Checked> {
let verify = |signee: &PublicKey, signer: &PublicKey| {
sig.verify_third_party_certification(signee, signer, Tag::UserId, user_id)
.is_ok()
};
self.find_third_party_signer(sig, signee, &verify)
}
pub(crate) fn find_third_party_dks_signer(
&self,
sig: &Signature,
signee: &PublicKey,
) -> Option<&Checked> {
let verify = |_signee: &PublicKey, _signer: &PublicKey| {
false
};
self.find_third_party_signer(sig, signee, &verify)
}
fn find_third_party_signer(
&self,
sig: &Signature,
signee: &PublicKey,
verify: &dyn Fn(&PublicKey, &PublicKey) -> bool,
) -> Option<&Checked> {
for fp in sig.issuer_fingerprint() {
if let Some(c) = self.by_fp.get(fp) {
if verify(signee, &c.as_signed_public_key().primary_key) {
return Some(c);
}
}
}
for keyid in sig.issuer() {
for &c in self.by_keyid.get(keyid).unwrap_or(&vec![]) {
if verify(signee, &c.as_signed_public_key().primary_key) {
return Some(c);
}
}
}
None }
}
pub(crate) fn handle_userid_third_party(
net: &mut Network,
tplookup: &ThirdPartyLookup,
target: &Checked,
user: &UserId,
sigs: &Vec<&Signature>,
reference_time: &DateTime<Utc>,
) {
if let Some(target_user) = target.user_ids().iter().find(|su| &su.id == user) {
if !user_id_valid(target_user, reference_time) {
return;
}
}
let Some(id) = to_identity(user) else {
return;
};
if !sigs.is_empty() {
log::debug!("{} is certified by:", id);
}
let valid_third_party =
valid_userid_third_party_sigs(tplookup, sigs, target, user, reference_time);
for (signer, sigs) in valid_third_party {
if sigs
.iter()
.any(|s| s.typ() == Some(SignatureType::CertRevocation))
{
continue;
}
let latest = pick_latest(&sigs);
net.add_binding(signer.clone(), to_certificate(target), id.clone());
add_delegation(net, latest, signer, target);
}
}
pub(crate) fn handle_dks_third_party(
net: &mut Network,
tplookup: &ThirdPartyLookup,
target: &Checked,
dks_third: &Vec<&Signature>,
reference_time: &DateTime<Utc>,
) {
if !dks_third.is_empty() {
log::trace!("found {} dks", dks_third.len());
}
let valid_dks = valid_dks_third_party_sigs(tplookup, dks_third, target, reference_time);
for (signer, sigs) in valid_dks {
let latest = pick_latest(&sigs);
add_delegation(net, latest, signer, target);
}
}
pub fn valid_userid_third_party_sigs<'a>(
tplookup: &'a ThirdPartyLookup<'_>,
signatures: &[&'a Signature],
target: &Checked,
target_user: &UserId,
reference_time: &DateTime<Utc>,
) -> HashMap<Certificate, Vec<&'a Signature>> {
const FILTER: &[Option<SignatureType>] = &[
Some(SignatureType::CertPositive),
Some(SignatureType::CertGeneric),
Some(SignatureType::CertCasual),
Some(SignatureType::CertRevocation),
];
let lookup = |sig| {
tplookup.find_third_party_userid_signer(
sig,
&target.as_signed_public_key().primary_key,
target_user,
)
};
collect_third_party(signatures, reference_time, Box::new(lookup), FILTER)
}
fn valid_dks_third_party_sigs<'a>(
tplookup: &'a ThirdPartyLookup,
signatures: &[&'a Signature],
target: &Checked,
reference_time: &DateTime<Utc>,
) -> HashMap<Certificate, Vec<&'a Signature>> {
const FILTER: &[Option<SignatureType>] = &[
Some(SignatureType::Key),
Some(SignatureType::CertRevocation),
];
let lookup =
|sig| tplookup.find_third_party_dks_signer(sig, &target.as_signed_public_key().primary_key);
collect_third_party(signatures, reference_time, Box::new(lookup), FILTER)
}
fn collect_third_party<'a>(
signatures: &[&'a Signature],
reference_time: &DateTime<Utc>,
lookup: Box<impl Fn(&'a Signature) -> Option<&'a Checked>>,
sigtype_filter: &[Option<SignatureType>],
) -> HashMap<Certificate, Vec<&'a Signature>> {
let mut map = HashMap::new();
for &sig in signatures {
if !sigtype_filter.contains(&sig.typ()) {
continue;
}
if !rpgpie::signature::signature_acceptable(sig, *reference_time) {
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.num_seconds() != 0 {
let expires = sig_created.add(*exp);
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 signer_cert = to_certificate(signer);
let sigs: &mut Vec<&Signature> = map.entry(signer_cert).or_default();
sigs.push(sig);
}
map
}
fn add_delegation(net: &mut Network, sig: &Signature, signer: Certificate, target: &Checked) {
if let Some((depth, amount)) = sig.trust_signature() {
if depth > 0 {
let regexes = regexes_from(sig);
net.add_delegation(
signer,
to_certificate(target),
amount,
depth.into(),
regexes,
);
}
}
}