diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-06-27 20:49:13 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-06-27 21:00:17 +0200 |
commit | 0ef86a91b691dc5d2368f1963603dc2da9c8e586 (patch) | |
tree | 54955d9eaf38f25a618887f49e6247f08402f0e0 | |
parent | 7381f2f5e37126d65489f32b2a20defea983fd4d (diff) |
openpgp: Add an interface to filter unvalidated TPKs.
- Validating self-signatures is computationally expensive. When
searching a keyring, it would be faster to filter, validate and
then recheck, than to validate and filter.
- Because handing out unvalidated TPKs will inevitably result in an
unvalidated TPK being used in a context where it should have been
validated, we provide this new interface,
`TPKParser::unvalidated_tpk_filter`, which is similar to Rust's
`Iterator::filter` method. Using a callback means that the user
has to go out of their way to propagate the unvalidated TPK, which
will hopefully prevents the unvalidated TPK from being used in a
context where a validated TPK is required.
-rw-r--r-- | openpgp/src/tpk.rs | 101 |
1 files changed, 100 insertions, 1 deletions
diff --git a/openpgp/src/tpk.rs b/openpgp/src/tpk.rs index fe1c1492..dda8b4dd 100644 --- a/openpgp/src/tpk.rs +++ b/openpgp/src/tpk.rs @@ -248,6 +248,8 @@ pub struct TPKParser<'a, I: Iterator<Item=Packet>> { subkeys: Vec<SubkeyBinding>, saw_error: bool, + + filter: Vec<Box<Fn(&TPK, bool) -> bool + 'a>>, } impl<'a, I: Iterator<Item=Packet>> Default for TPKParser<'a, I> { @@ -260,6 +262,7 @@ impl<'a, I: Iterator<Item=Packet>> Default for TPKParser<'a, I> { user_attributes: vec![], subkeys: vec![], saw_error: false, + filter: vec![], } } } @@ -306,6 +309,85 @@ impl<'a, I: Iterator<Item=Packet>> TPKParser<'a, I> { 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 openpgp; + /// # use openpgp::Result; + /// # use openpgp::parse::PacketParser; + /// use openpgp::tpk::TPKParser; + /// use openpgp::TPK; + /// use openpgp::KeyID; + /// + /// # fn main() { f().unwrap(); } + /// # fn f() -> Result<()> { + /// # let ppo = PacketParser::from_bytes(&b""[..])?; + /// # let some_keyid = KeyID::from_hex("C2B819056C652598").unwrap(); + /// # if let Some(pp) = ppo { + /// for tpkr in TPKParser::from_packet_parser(pp) + /// .unvalidated_tpk_filter(|tpk, _| { + /// if tpk.primary().keyid() == some_keyid { + /// return true; + /// } + /// for binding in tpk.subkeys() { + /// if binding.subkey().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 + } + // Resets the parser so that it starts parsing a new packet. // // Returns the old state. Note: the packet iterator is preserved. @@ -693,7 +775,24 @@ impl<'a, I: Iterator<Item=Packet>> TPKParser<'a, I> { TPKParserState::End => Some(tpk), _ => None, - }.and_then(|tpk| Some(tpk.canonicalize())); + }.and_then(|tpk| { + for filter in &self.filter { + if !filter(&tpk, false) { + return None; + } + } + + let tpk = tpk.canonicalize(); + + // Make sure it is still okay. + for filter in &self.filter { + if !filter(&tpk, true) { + return None; + } + } + + Some(tpk) + }); self.primary = pk; |