diff options
author | Neal H. Walfield <neal@pep.foundation> | 2019-08-30 16:12:06 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2019-08-30 16:12:06 +0200 |
commit | 50d651a5f4045dccfcb2aec09bec44d5a92958f8 (patch) | |
tree | 662dea4a0ef8964cdde1f60a5e7b9b3307589a03 | |
parent | beded916330c406982e244d1d71ebcf9a80514dd (diff) |
openpgp: Move TPKParser to tpk::parser.
- Move the TPKParser structure and its implementation from the tpk
module to the tpk::parser module.
-rw-r--r-- | openpgp/src/tpk/mod.rs | 423 | ||||
-rw-r--r-- | openpgp/src/tpk/parser/mod.rs | 412 |
2 files changed, 423 insertions, 412 deletions
diff --git a/openpgp/src/tpk/mod.rs b/openpgp/src/tpk/mod.rs index 983e66d6..fe33fee0 100644 --- a/openpgp/src/tpk/mod.rs +++ b/openpgp/src/tpk/mod.rs @@ -6,7 +6,6 @@ use std::path::Path; use std::slice; use std::mem; use std::fmt; -use std::vec; use time; use crate::{ @@ -17,7 +16,6 @@ use crate::{ SignatureType, HashAlgorithm, packet, - packet::Tag, packet::Signature, packet::signature, packet::Key, @@ -45,16 +43,11 @@ pub use keyiter::KeyIter; pub use parser::{ KeyringValidity, KeyringValidator, + TPKParser, TPKValidity, TPKValidator, }; -use parser::low_level::{ - Lexer, - Token, - TPKParser as TPKLowLevelParser, -}; - const TRACE : bool = false; // Helper functions. @@ -322,407 +315,6 @@ impl ComponentBinding<Unknown> { } } -// A TPKParser can read packets from either an Iterator or a -// PacketParser. Ideally, we would just take an iterator, but we -// want to be able to handle errors, which iterators hide. -enum PacketSource<'a, I: Iterator<Item=Packet>> { - EOF, - PacketParser(PacketParser<'a>), - Iter(I), -} - -/// An iterator over a sequence of TPKs (e.g., an OpenPGP keyring). -/// -/// The source of packets can either be a `PacketParser` or an -/// iterator over `Packet`s. (In the latter case, the underlying -/// parser is not able to propagate errors. Thus, this is only -/// appropriate for in-memory structures, like a vector of `Packet`s -/// or a `PacketPile`.) -/// -/// # Example -/// -/// ```rust -/// # extern crate sequoia_openpgp as openpgp; -/// # use openpgp::Result; -/// # use openpgp::parse::{Parse, PacketParserResult, PacketParser}; -/// use openpgp::tpk::TPKParser; -/// -/// # fn main() { f().unwrap(); } -/// # fn f() -> Result<()> { -/// # let ppr = PacketParser::from_bytes(b"")?; -/// for tpko in TPKParser::from_packet_parser(ppr) { -/// match tpko { -/// Ok(tpk) => { -/// println!("Key: {}", tpk.primary().key()); -/// for binding in tpk.userids() { -/// println!("User ID: {}", binding.userid()); -/// } -/// } -/// Err(err) => { -/// eprintln!("Error reading keyring: {}", err); -/// } -/// } -/// } -/// # Ok(()) -/// # } -/// ``` -pub struct TPKParser<'a, I: Iterator<Item=Packet>> { - source: PacketSource<'a, I>, - packets: Vec<Packet>, - saw_error: bool, - filter: Vec<Box<Fn(&TPK, bool) -> bool + 'a>>, -} - -impl<'a, I: Iterator<Item=Packet>> Default for TPKParser<'a, I> { - fn default() -> Self { - TPKParser { - source: PacketSource::EOF, - packets: vec![], - saw_error: false, - filter: vec![], - } - } -} - -// When using a `PacketParser`, we never use the `Iter` variant. -// Nevertheless, we need to provide a concrete type. -// vec::IntoIter<Packet> is about as good as any other. -impl<'a> TPKParser<'a, vec::IntoIter<Packet>> { - /// Initializes a `TPKParser` from a `PacketParser`. - pub fn from_packet_parser(ppr: PacketParserResult<'a>) -> Self { - let mut parser : Self = Default::default(); - if let PacketParserResult::Some(pp) = ppr { - parser.source = PacketSource::PacketParser(pp); - } - parser - } -} - -impl<'a> Parse<'a, TPKParser<'a, vec::IntoIter<Packet>>> - for TPKParser<'a, vec::IntoIter<Packet>> -{ - /// Initializes a `TPKParser` from a `Read`er. - fn from_reader<R: 'a + io::Read>(reader: R) -> Result<Self> { - Ok(Self::from_packet_parser(PacketParser::from_reader(reader)?)) - } - - /// Initializes a `TPKParser` from a `File`. - fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { - Ok(Self::from_packet_parser(PacketParser::from_file(path)?)) - } - - /// Initializes a `TPKParser` from a byte string. - fn from_bytes(data: &'a [u8]) -> Result<Self> { - Ok(Self::from_packet_parser(PacketParser::from_bytes(data)?)) - } -} - -impl std::str::FromStr for TPK { - type Err = failure::Error; - - fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { - Self::from_bytes(s.as_bytes()) - } -} - -impl<'a, I: Iterator<Item=Packet>> TPKParser<'a, I> { - /// Initializes a TPKParser from an iterator over Packets. - pub fn from_iter(iter: I) -> Self { - let mut parser : Self = Default::default(); - parser.source = PacketSource::Iter(iter); - parser - } - - /// Filters the TPKs prior to validation. - /// - /// By default, the `TPKParser` only returns valdiated `TPK`s. - /// Checking that a `TPK`'s self-signatures are valid, however, is - /// computationally expensive, and not always necessary. For - /// example, when looking for a small number of `TPK`s in a large - /// keyring, most `TPK`s can be immediately discarded. That is, - /// it is more efficient to filter, validate, and double check, - /// than to validate and filter. (It is necessary to double - /// check, because the check might have been on an invalid part. - /// For example, if searching for a key with a particular key ID, - /// a matching subkey might not have any self signatures.) - /// - /// If the `TPKParser` gave out unvalidated `TPK`s, and provided - /// an interface to validate them, then the caller could implement - /// this first-validate-double-check pattern. Giving out - /// unvalidated `TPK`s, however, is too dangerous: inevitably, a - /// `TPK` will be used without having been validated in a context - /// where it should have been. - /// - /// This function avoids this class of bugs while still providing - /// a mechanism to filter `TPK`s prior to validation: the caller - /// provides a callback, that is invoked on the *unvalidated* - /// `TPK`. If the callback returns `true`, then the parser - /// validates the `TPK`, and invokes the callback *a second time* - /// to make sure the `TPK` is really wanted. If the callback - /// returns false, then the `TPK` is skipped. - /// - /// Note: calling this function multiple times on a single - /// `TPKParser` will install multiple filters. - /// - /// # Example - /// - /// ```rust - /// # extern crate sequoia_openpgp as openpgp; - /// # use openpgp::Result; - /// # use openpgp::parse::{Parse, PacketParser}; - /// use openpgp::tpk::TPKParser; - /// use openpgp::TPK; - /// use openpgp::KeyID; - /// - /// # fn main() { f().unwrap(); } - /// # fn f() -> Result<()> { - /// # let ppr = PacketParser::from_bytes(b"")?; - /// # let some_keyid = KeyID::from_hex("C2B819056C652598").unwrap(); - /// for tpkr in TPKParser::from_packet_parser(ppr) - /// .unvalidated_tpk_filter(|tpk, _| { - /// if tpk.primary().key().keyid() == some_keyid { - /// return true; - /// } - /// for binding in tpk.subkeys() { - /// if binding.key().keyid() == some_keyid { - /// return true; - /// } - /// } - /// false - /// }) - /// { - /// match tpkr { - /// Ok(tpk) => { - /// // The TPK contains the subkey. - /// } - /// Err(err) => { - /// eprintln!("Error reading keyring: {}", err); - /// } - /// } - /// } - /// # Ok(()) - /// # } - /// ``` - pub fn unvalidated_tpk_filter<F: 'a>(mut self, filter: F) -> Self - where F: Fn(&TPK, bool) -> bool - { - self.filter.push(Box::new(filter)); - self - } - - // Parses the next packet in the packet stream. - // - // If we complete parsing a TPK, returns the TPK. Otherwise, - // returns None. - fn parse(&mut self, p: Packet) -> Result<Option<TPK>> { - if self.packets.len() > 0 { - match p.tag() { - Tag::PublicKey | Tag::SecretKey => { - return self.tpk(Some(p)); - }, - _ => {}, - } - } - - self.packets.push(p); - Ok(None) - } - - // Resets the parser so that it starts parsing a new packet. - // - // Returns the old state. Note: the packet iterator is preserved. - fn reset(&mut self) -> Self { - // We need to preserve `source`. - let mut orig = mem::replace(self, Default::default()); - self.source = mem::replace(&mut orig.source, PacketSource::EOF); - orig - } - - // Finalizes the current TPK and returns it. Sets the parser up to - // begin parsing the next TPK. - fn tpk(&mut self, pk: Option<Packet>) -> Result<Option<TPK>> { - let orig = self.reset(); - - if let Some(pk) = pk { - self.packets.push(pk); - } - - let packets = orig.packets.len(); - let tokens = orig.packets - .into_iter() - .filter_map(|p| p.into()) - .collect::<Vec<Token>>(); - if tokens.len() != packets { - // There was at least one packet that doesn't belong in a - // TPK. Fail now. - return Err(Error::UnsupportedTPK( - "Packet sequence includes non-TPK packets.".into()).into()); - } - - let tpko = match TPKLowLevelParser::new() - .parse(Lexer::from_tokens(&tokens)) - { - Ok(tpko) => tpko, - Err(e) => return Err( - parser::low_level::parse_error_to_openpgp_error( - parser::low_level::parse_error_downcast(e)).into()), - }.and_then(|tpk| { - for filter in &self.filter { - if !filter(&tpk, true) { - return None; - } - } - - Some(tpk) - }).and_then(|mut tpk| { - fn split_sigs<C>(primary: &Fingerprint, primary_keyid: &KeyID, - b: &mut ComponentBinding<C>) - { - let mut selfsigs = vec![]; - let mut certifications = vec![]; - let mut self_revs = vec![]; - let mut other_revs = vec![]; - - for sig in mem::replace(&mut b.certifications, vec![]) { - match sig { - Signature::V4(sig) => { - let typ = sig.typ(); - - let is_selfsig = - sig.issuer_fingerprint() - .map(|fp| fp == *primary) - .unwrap_or(false) - || sig.issuer() - .map(|keyid| keyid == *primary_keyid) - .unwrap_or(false); - - use self::SignatureType::*; - if typ == KeyRevocation - || typ == SubkeyRevocation - || typ == CertificateRevocation - { - if is_selfsig { - self_revs.push(sig.into()); - } else { - other_revs.push(sig.into()); - } - } else { - if is_selfsig { - selfsigs.push(sig.into()); - } else { - certifications.push(sig.into()); - } - } - }, - } - } - - b.selfsigs = selfsigs; - b.certifications = certifications; - b.self_revocations = self_revs; - b.other_revocations = other_revs; - } - - let primary_fp = tpk.primary().key().fingerprint(); - let primary_keyid = primary_fp.to_keyid(); - - // The parser puts all of the signatures on the - // certifications field. Split them now. - - split_sigs(&primary_fp, &primary_keyid, &mut tpk.primary); - - for b in tpk.userids.iter_mut() { - split_sigs(&primary_fp, &primary_keyid, b); - } - for b in tpk.user_attributes.iter_mut() { - split_sigs(&primary_fp, &primary_keyid, b); - } - for b in tpk.subkeys.iter_mut() { - split_sigs(&primary_fp, &primary_keyid, b); - } - - let tpk = tpk.canonicalize(); - - // Make sure it is still wanted. - for filter in &self.filter { - if !filter(&tpk, true) { - return None; - } - } - - Some(tpk) - }); - - Ok(tpko) - } -} - -impl<'a, I: Iterator<Item=Packet>> Iterator for TPKParser<'a, I> { - type Item = Result<TPK>; - - fn next(&mut self) -> Option<Self::Item> { - loop { - match mem::replace(&mut self.source, PacketSource::EOF) { - PacketSource::EOF => { - if TRACE { - eprintln!("TPKParser::next: EOF."); - } - - if self.packets.len() == 0 { - return None; - } - match self.tpk(None) { - Ok(Some(tpk)) => return Some(Ok(tpk)), - Ok(None) => return None, - Err(err) => return Some(Err(err)), - } - }, - PacketSource::PacketParser(pp) => { - match pp.next() { - Ok((packet, ppr)) => { - if let PacketParserResult::Some(pp) = ppr { - self.source = PacketSource::PacketParser(pp); - } - - match self.parse(packet) { - Ok(Some(tpk)) => return Some(Ok(tpk)), - Ok(None) => (), - Err(err) => return Some(Err(err)), - } - }, - Err(err) => { - self.saw_error = true; - return Some(Err(err)); - } - } - }, - PacketSource::Iter(mut iter) => { - let r = match iter.next() { - Some(packet) => { - self.source = PacketSource::Iter(iter); - self.parse(packet) - } - None if self.packets.len() == 0 => Ok(None), - None => self.tpk(None), - }; - - match r { - Ok(Some(tpk)) => { - if TRACE { - eprintln!("TPKParser::next => {}", - tpk.primary().key().fingerprint()); - } - return Some(Ok(tpk)); - } - Ok(None) => (), - Err(err) => return Some(Err(err)), - } - }, - } - } - } -} - impl fmt::Display for TPK { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.primary().key().fingerprint()) @@ -832,6 +424,14 @@ pub struct TPK { bad: Vec<packet::Signature>, } +impl std::str::FromStr for TPK { + type Err = failure::Error; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + Self::from_bytes(s.as_bytes()) + } +} + impl<'a> Parse<'a, TPK> for TPK { /// Returns the first TPK encountered in the reader. fn from_reader<R: io::Read>(reader: R) -> Result<Self> { @@ -1208,7 +808,7 @@ impl TPK { /// packet stream is a keyring, this function will return /// `Error::MalformedTPK`. pub fn from_packet_parser(ppr: PacketParserResult) -> Result<Self> { - let mut parser = TPKParser::from_packet_parser(ppr); + let mut parser = parser::TPKParser::from_packet_parser(ppr); if let Some(tpk_result) = parser.next() { if parser.next().is_some() { Err(Error::MalformedTPK( @@ -1224,7 +824,7 @@ impl TPK { /// Returns the first TPK found in the `PacketPile`. pub fn from_packet_pile(p: PacketPile) -> Result<Self> { - let mut i = TPKParser::from_iter(p.into_children()); + let mut i = parser::TPKParser::from_iter(p.into_children()); match i.next() { Some(Ok(tpk)) => Ok(tpk), Some(Err(err)) => Err(err), @@ -2402,6 +2002,7 @@ mod test { #[test] fn merge_packets() { use crate::armor; + use crate::packet::Tag; // Merge the revocation certificate into the TPK and make sure // it shows up. diff --git a/openpgp/src/tpk/parser/mod.rs b/openpgp/src/tpk/parser/mod.rs index 21015aca..a4d118b8 100644 --- a/openpgp/src/tpk/parser/mod.rs +++ b/openpgp/src/tpk/parser/mod.rs @@ -1,13 +1,28 @@ +use std::io; +use std::mem; use std::vec; +use std::path::Path; use lalrpop_util::ParseError; use crate::{ Error, + Fingerprint, + KeyID, packet::Tag, + packet::Signature, + Packet, + parse::{ + Parse, + PacketParserResult, + PacketParser + }, + Result, + tpk::ComponentBinding, + TPK, }; -pub mod low_level; +mod low_level; use low_level::{ Lexer, TPKParser as TPKLowLevelParser, @@ -16,6 +31,8 @@ use low_level::{ parse_error_downcast, }; +use super::TRACE; + /// Whether a packet sequence is a valid key ring. #[derive(Debug)] pub enum KeyringValidity { @@ -355,6 +372,399 @@ impl TPKValidator { } } +// A TPKParser can read packets from either an Iterator or a +// PacketParser. Ideally, we would just take an iterator, but we +// want to be able to handle errors, which iterators hide. +enum PacketSource<'a, I: Iterator<Item=Packet>> { + EOF, + PacketParser(PacketParser<'a>), + Iter(I), +} + +/// An iterator over a sequence of TPKs (e.g., an OpenPGP keyring). +/// +/// The source of packets can either be a `PacketParser` or an +/// iterator over `Packet`s. (In the latter case, the underlying +/// parser is not able to propagate errors. Thus, this is only +/// appropriate for in-memory structures, like a vector of `Packet`s +/// or a `PacketPile`.) +/// +/// # Example +/// +/// ```rust +/// # extern crate sequoia_openpgp as openpgp; +/// # use openpgp::Result; +/// # use openpgp::parse::{Parse, PacketParserResult, PacketParser}; +/// use openpgp::tpk::TPKParser; +/// +/// # fn main() { f().unwrap(); } +/// # fn f() -> Result<()> { +/// # let ppr = PacketParser::from_bytes(b"")?; +/// for tpko in TPKParser::from_packet_parser(ppr) { +/// match tpko { +/// Ok(tpk) => { +/// println!("Key: {}", tpk.primary().key()); +/// for binding in tpk.userids() { +/// println!("User ID: {}", binding.userid()); +/// } +/// } +/// Err(err) => { +/// eprintln!("Error reading keyring: {}", err); +/// } +/// } +/// } +/// # Ok(()) +/// # } +/// ``` +pub struct TPKParser<'a, I: Iterator<Item=Packet>> { + source: PacketSource<'a, I>, + packets: Vec<Packet>, + saw_error: bool, + filter: Vec<Box<Fn(&TPK, bool) -> bool + 'a>>, +} + +impl<'a, I: Iterator<Item=Packet>> Default for TPKParser<'a, I> { + fn default() -> Self { + TPKParser { + source: PacketSource::EOF, + packets: vec![], + saw_error: false, + filter: vec![], + } + } +} + +// When using a `PacketParser`, we never use the `Iter` variant. +// Nevertheless, we need to provide a concrete type. +// vec::IntoIter<Packet> is about as good as any other. +impl<'a> TPKParser<'a, vec::IntoIter<Packet>> { + /// Initializes a `TPKParser` from a `PacketParser`. + pub fn from_packet_parser(ppr: PacketParserResult<'a>) -> Self { + let mut parser : Self = Default::default(); + if let PacketParserResult::Some(pp) = ppr { + parser.source = PacketSource::PacketParser(pp); + } + parser + } +} + +impl<'a> Parse<'a, TPKParser<'a, vec::IntoIter<Packet>>> + for TPKParser<'a, vec::IntoIter<Packet>> +{ + /// Initializes a `TPKParser` from a `Read`er. + fn from_reader<R: 'a + io::Read>(reader: R) -> Result<Self> { + Ok(Self::from_packet_parser(PacketParser::from_reader(reader)?)) + } + + /// Initializes a `TPKParser` from a `File`. + fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { + Ok(Self::from_packet_parser(PacketParser::from_file(path)?)) + } + + /// Initializes a `TPKParser` from a byte string. + fn from_bytes(data: &'a [u8]) -> Result<Self> { + Ok(Self::from_packet_parser(PacketParser::from_bytes(data)?)) + } +} + +impl<'a, I: Iterator<Item=Packet>> TPKParser<'a, I> { + /// Initializes a TPKParser from an iterator over Packets. + pub fn from_iter(iter: I) -> Self { + let mut parser : Self = Default::default(); + parser.source = PacketSource::Iter(iter); + parser + } + + /// Filters the TPKs prior to validation. + /// + /// By default, the `TPKParser` only returns valdiated `TPK`s. + /// Checking that a `TPK`'s self-signatures are valid, however, is + /// computationally expensive, and not always necessary. For + /// example, when looking for a small number of `TPK`s in a large + /// keyring, most `TPK`s can be immediately discarded. That is, + /// it is more efficient to filter, validate, and double check, + /// than to validate and filter. (It is necessary to double + /// check, because the check might have been on an invalid part. + /// For example, if searching for a key with a particular key ID, + /// a matching subkey might not have any self signatures.) + /// + /// If the `TPKParser` gave out unvalidated `TPK`s, and provided + /// an interface to validate them, then the caller could implement + /// this first-validate-double-check pattern. Giving out + /// unvalidated `TPK`s, however, is too dangerous: inevitably, a + /// `TPK` will be used without having been validated in a context + /// where it should have been. + /// + /// This function avoids this class of bugs while still providing + /// a mechanism to filter `TPK`s prior to validation: the caller + /// provides a callback, that is invoked on the *unvalidated* + /// `TPK`. If the callback returns `true`, then the parser + /// validates the `TPK`, and invokes the callback *a second time* + /// to make sure the `TPK` is really wanted. If the callback + /// returns false, then the `TPK` is skipped. + /// + /// Note: calling this function multiple times on a single + /// `TPKParser` will install multiple filters. + /// + /// # Example + /// + /// ```rust + /// # extern crate sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// # use openpgp::parse::{Parse, PacketParser}; + /// use openpgp::tpk::TPKParser; + /// use openpgp::TPK; + /// use openpgp::KeyID; + /// + /// # fn main() { f().unwrap(); } + /// # fn f() -> Result<()> { + /// # let ppr = PacketParser::from_bytes(b"")?; + /// # let some_keyid = KeyID::from_hex("C2B819056C652598").unwrap(); + /// for tpkr in TPKParser::from_packet_parser(ppr) + /// .unvalidated_tpk_filter(|tpk, _| { + /// if tpk.primary().key().keyid() == some_keyid { + /// return true; + /// } + /// for binding in tpk.subkeys() { + /// if binding.key().keyid() == some_keyid { + /// return true; + /// } + /// } + /// false + /// }) + /// { + /// match tpkr { + /// Ok(tpk) => { + /// // The TPK contains the subkey. + /// } + /// Err(err) => { + /// eprintln!("Error reading keyring: {}", err); + /// } + /// } + /// } + /// # Ok(()) + /// # } + /// ``` + pub fn unvalidated_tpk_filter<F: 'a>(mut self, filter: F) -> Self + where F: Fn(&TPK, bool) -> bool + { + self.filter.push(Box::new(filter)); + self + } + + // Parses the next packet in the packet stream. + // + // If we complete parsing a TPK, returns the TPK. Otherwise, + // returns None. + fn parse(&mut self, p: Packet) -> Result<Option<TPK>> { + if self.packets.len() > 0 { + match p.tag() { + Tag::PublicKey | Tag::SecretKey => { + return self.tpk(Some(p)); + }, + _ => {}, + } + } + + self.packets.push(p); + Ok(None) + } + + // Resets the parser so that it starts parsing a new packet. + // + // Returns the old state. Note: the packet iterator is preserved. + fn reset(&mut self) -> Self { + // We need to preserve `source`. + let mut orig = mem::replace(self, Default::default()); + self.source = mem::replace(&mut orig.source, PacketSource::EOF); + orig + } + + // Finalizes the current TPK and returns it. Sets the parser up to + // begin parsing the next TPK. + fn tpk(&mut self, pk: Option<Packet>) -> Result<Option<TPK>> { + let orig = self.reset(); + + if let Some(pk) = pk { + self.packets.push(pk); + } + + let packets = orig.packets.len(); + let tokens = orig.packets + .into_iter() + .filter_map(|p| p.into()) + .collect::<Vec<Token>>(); + if tokens.len() != packets { + // There was at least one packet that doesn't belong in a + // TPK. Fail now. + return Err(Error::UnsupportedTPK( + "Packet sequence includes non-TPK packets.".into()).into()); + } + + let tpko = match TPKLowLevelParser::new() + .parse(Lexer::from_tokens(&tokens)) + { + Ok(tpko) => tpko, + Err(e) => return Err( + low_level::parse_error_to_openpgp_error( + low_level::parse_error_downcast(e)).into()), + }.and_then(|tpk| { + for filter in &self.filter { + if !filter(&tpk, true) { + return None; + } + } + + Some(tpk) + }).and_then(|mut tpk| { + fn split_sigs<C>(primary: &Fingerprint, primary_keyid: &KeyID, + b: &mut ComponentBinding<C>) + { + let mut selfsigs = vec![]; + let mut certifications = vec![]; + let mut self_revs = vec![]; + let mut other_revs = vec![]; + + for sig in mem::replace(&mut b.certifications, vec![]) { + match sig { + Signature::V4(sig) => { + let typ = sig.typ(); + + let is_selfsig = |