use std::io;
use pgp::{
composed::{Deserializable, DetachedSignature},
packet::{Packet, RevocationCode, Signature, SignatureConfig, SignatureType, SubpacketData},
ser::Serialize,
types::Timestamp,
};
use crate::{
Error,
policy::{accept_for_signatures, acceptable_hash_algorithm},
};
fn is_data_type(typ: Option<SignatureType>) -> bool {
matches!(typ, Some(SignatureType::Binary) | Some(SignatureType::Text))
}
pub(crate) fn is_revocation_type(typ: SignatureType) -> bool {
matches!(
typ,
SignatureType::KeyRevocation
| SignatureType::CertRevocation
| SignatureType::SubkeyRevocation
)
}
pub(crate) fn is_revocation(sig: &Signature) -> bool {
if let Some(typ) = sig.typ() {
is_revocation_type(typ)
} else {
false
}
}
fn is_soft_revocation_reason(reason: Option<&RevocationCode>) -> bool {
matches!(
reason,
Some(RevocationCode::KeyRetired)
| Some(RevocationCode::CertUserIdInvalid)
| Some(RevocationCode::KeySuperseded)
)
}
pub(crate) fn is_hard_revocation(sig: &Signature) -> bool {
if is_revocation(sig) {
!is_soft_revocation_reason(sig.revocation_reason_code())
} else {
false
}
}
fn unknown_critical(config: &SignatureConfig) -> bool {
for sp in &config.hashed_subpackets {
if sp.is_critical {
if let SubpacketData::Notation(_notation) = &sp.data {
return true;
}
}
}
false
}
pub fn signature_acceptable(sig: &Signature) -> bool {
let Some(sig_creation_time) = sig.created() else {
return false;
};
let Some(config) = sig.config() else {
return false;
};
if unknown_critical(config) {
return false;
}
if !acceptable_hash_algorithm(config.hash_alg, sig_creation_time, is_data_type(sig.typ())) {
return false;
}
if !accept_for_signatures(config.pub_alg, sig_creation_time) {
return false;
}
true
}
pub(crate) fn not_older_than(sig: &Signature, key_created: Timestamp) -> bool {
sig.created()
.is_some_and(|sig_created| sig_created >= key_created)
}
fn not_newer_than(sig: &Signature, reference: Timestamp) -> bool {
sig.created()
.is_some_and(|sig_created| sig_created <= reference)
}
pub(crate) fn signature_validity_expiration(sig: &Signature) -> Option<Timestamp> {
let Some(sig_creation) = sig.created() else {
return None;
};
if let Some(sig_expiration) = sig.signature_expiration_time() {
if sig_expiration.as_secs() != 0 {
Some(Timestamp::from_secs(
sig_creation.as_secs() + sig_expiration.as_secs(),
))
} else {
None
}
} else {
None
}
}
pub fn load<R: io::Read>(source: &mut R) -> Result<Vec<Signature>, Error> {
let (iter, _header) = DetachedSignature::from_reader_many(source)?;
let mut sigs = vec![];
for res in iter {
match res {
Ok(sig) => sigs.push(sig.signature),
Err(e) => return Err(Error::Message(format!("Bad data: {e:?}"))),
}
}
Ok(sigs)
}
pub fn save(
signatures: &[Signature],
armored: bool,
mut sink: &mut dyn io::Write,
) -> Result<(), Error> {
if armored {
let armor_checksum = signatures.iter().any(|sig| u8::from(sig.version()) < 6);
let packets: Vec<_> = signatures.iter().map(|s| Packet::from(s.clone())).collect();
pgp::armor::write(
&packets,
pgp::armor::BlockType::Signature,
&mut sink,
None,
armor_checksum,
)?;
} else {
for s in signatures {
let p = Packet::from(s.clone());
p.to_writer(&mut sink)?;
}
}
Ok(())
}
#[derive(Debug)]
pub(crate) struct SigStack<'inner> {
hard: Vec<&'inner Signature>,
soft: Vec<&'inner Signature>,
regular: Vec<&'inner Signature>,
#[allow(dead_code)]
invalid: Vec<&'inner Signature>,
#[allow(dead_code)]
third_party: Vec<&'inner Signature>,
}
impl<'a> FromIterator<&'a Signature> for SigStack<'a> {
fn from_iter<T: IntoIterator<Item = &'a Signature>>(iter: T) -> Self {
let mut hard = vec![];
let mut soft = vec![];
let mut regular = vec![];
let mut invalid = vec![];
let third_party = vec![];
for sig in iter.into_iter() {
if is_hard_revocation(sig) {
hard.push(sig)
} else if is_revocation(sig) {
soft.push(sig)
} else if signature_acceptable(sig) {
regular.push(sig)
} else {
invalid.push(sig)
}
}
regular.sort_by(|a, b| {
a.created()
.map(Timestamp::as_secs)
.cmp(&b.created().map(Timestamp::as_secs))
});
Self {
hard,
soft,
regular,
invalid,
third_party,
}
}
}
impl<'a> SigStack<'a> {
pub(crate) fn active_at(
&self,
reference: Timestamp,
key_creation: Timestamp,
) -> Option<&'a Signature> {
log::debug!("search active_at: {reference:?}");
if let Some(&hard) = self.hard.first() {
log::debug!(
" found hard: {}",
hard.created()
.map(|dt| format!("{dt:?}"))
.unwrap_or("<no creation time>".to_string())
);
return Some(hard);
}
fn replace_if_newer<'a>(cur: &mut Option<&'a Signature>, s: &'a Signature) {
if let Some(cur) = cur {
if let Some(sig_created) = s.created() {
if let Some(cur_created) = cur.created() {
if cur_created < sig_created {
*cur = s;
}
}
}
} else {
*cur = Some(s);
}
}
let mut latest_soft: Option<&Signature> = None;
self.soft
.iter()
.filter(|&&s| {
not_older_than(s, key_creation)
})
.filter(|&&s| {
not_newer_than(s, reference)
})
.for_each(|s| {
replace_if_newer(&mut latest_soft, s);
});
if let Some(latest_soft) = latest_soft {
log::debug!(
" found soft: {}",
latest_soft
.created()
.map(|dt| format!("{dt:?}"))
.unwrap_or("<no creation time>".to_string())
);
return Some(latest_soft);
}
let now = Timestamp::now();
let mut latest_before_reference: Option<&Signature> = None;
let mut latest_before_now: Option<&Signature> = None;
self.regular
.iter()
.filter(|&&s| {
not_older_than(s, key_creation)
})
.filter(|&&s| {
not_newer_than(s, now)
})
.filter(|s| {
if let Some(expired) = signature_validity_expiration(s) {
expired > reference
} else {
true
}
})
.for_each(|s| {
if let Some(c) = s.created() {
if c <= reference {
replace_if_newer(&mut latest_before_reference, s);
} else {
replace_if_newer(&mut latest_before_now, s);
}
}
});
let latest = match (latest_before_reference, latest_before_now) {
(Some(before_reference), _) => Some(before_reference),
(_, before_now) => before_now,
};
if let Some(latest) = latest {
log::debug!(
" latest regular: {}",
latest
.created()
.map(|dt| format!("{dt:?}"))
.unwrap_or("<no creation time>".to_string())
);
}
latest
}
}
pub(crate) fn signature_bytes_eq(a: &Signature, b: &Signature) -> bool {
if let (Some(sb1), Some(sb2)) = (a.signature(), b.signature()) {
sb1 == sb2
} else {
a == b
}
}
pub(crate) fn merge_signatures(target: &mut Vec<Signature>, updates: Vec<Signature>) {
for upd in updates {
if let Some(orig) = target.iter_mut().find(|s| signature_bytes_eq(s, &upd)) {
merge_unhashed(orig, &upd);
} else {
target.push(upd); }
}
}
pub(crate) fn merge_unhashed(target: &mut Signature, source: &Signature) {
let mut inserts = Vec::new();
if let (Some(c1), Some(c2)) = (target.config(), source.config()) {
for (n, sub) in c2.unhashed_subpackets.iter().enumerate() {
if !c1.unhashed_subpackets.contains(sub) {
inserts.push((n, sub.clone()));
}
}
}
for (pos, sp) in inserts {
let _ = target.unhashed_subpacket_insert(pos, sp); }
}