diff options
Diffstat (limited to 'openpgp/src/cert/amalgamation/iter.rs')
-rw-r--r-- | openpgp/src/cert/amalgamation/iter.rs | 1074 |
1 files changed, 1074 insertions, 0 deletions
diff --git a/openpgp/src/cert/amalgamation/iter.rs b/openpgp/src/cert/amalgamation/iter.rs new file mode 100644 index 00000000..1f9973dc --- /dev/null +++ b/openpgp/src/cert/amalgamation/iter.rs @@ -0,0 +1,1074 @@ +use std::fmt; +use std::convert::TryInto; +use std::time::SystemTime; +use std::borrow::Borrow; +use std::slice; + +use crate::{ + KeyHandle, + types::RevocationStatus, + packet::key, + packet::key::SecretKeyMaterial, + types::KeyFlags, + cert::prelude::*, + policy::Policy, +}; + +/// An iterator over all `Key`s (both the primary key and the subkeys) +/// in a certificate. +/// +/// Returned by `Cert::keys()`. +/// +/// `KeyAmalgamationIter` follows the builder pattern. There is no need to +/// explicitly finalize it, however: it already implements the +/// `Iterator` trait. +/// +/// By default, `KeyAmalgamationIter` returns all keys. `KeyAmalgamationIter` provides some +/// filters to control what it returns. For instance, +/// `KeyAmalgamationIter::secret` causes the iterator to only returns keys that +/// include secret key material. Of course, since `KeyAmalgamationIter` +/// implements `Iterator`, it is possible to use `Iterator::filter` to +/// implement custom filters. +pub struct KeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + // This is an option to make it easier to create an empty KeyAmalgamationIter. + cert: Option<&'a Cert>, + primary: bool, + subkey_iter: slice::Iter<'a, KeyBundle<key::PublicParts, + key::SubordinateRole>>, + + // If not None, filters by whether a key has a secret. + secret: Option<bool>, + + // If not None, filters by whether a key has an unencrypted + // secret. + unencrypted_secret: Option<bool>, + + // Only return keys in this set. + key_handles: Vec<KeyHandle>, + + _p: std::marker::PhantomData<P>, + _r: std::marker::PhantomData<R>, +} + +impl<'a, P, R> fmt::Debug for KeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("KeyAmalgamationIter") + .field("secret", &self.secret) + .field("unencrypted_secret", &self.unencrypted_secret) + .field("key_handles", &self.key_handles) + .finish() + } +} + +macro_rules! impl_iterator { + ($parts:path, $role:path, $item:ty) => { + impl<'a> Iterator for KeyAmalgamationIter<'a, $parts, $role> + { + type Item = $item; + + fn next(&mut self) -> Option<Self::Item> { + // We unwrap the result of the conversion. But, this + // is safe by construction: next_common only returns + // keys that can be correctly converted. + self.next_common().map(|k| k.try_into().expect("filtered")) + } + } + } +} + +impl_iterator!(key::PublicParts, key::PrimaryRole, + PrimaryKeyAmalgamation<'a, key::PublicParts>); +impl_iterator!(key::SecretParts, key::PrimaryRole, + PrimaryKeyAmalgamation<'a, key::SecretParts>); +impl_iterator!(key::UnspecifiedParts, key::PrimaryRole, + PrimaryKeyAmalgamation<'a, key::UnspecifiedParts>); + +impl_iterator!(key::PublicParts, key::SubordinateRole, + SubordinateKeyAmalgamation<'a, key::PublicParts>); +impl_iterator!(key::SecretParts, key::SubordinateRole, + SubordinateKeyAmalgamation<'a, key::SecretParts>); +impl_iterator!(key::UnspecifiedParts, key::SubordinateRole, + SubordinateKeyAmalgamation<'a, key::UnspecifiedParts>); + +impl_iterator!(key::PublicParts, key::UnspecifiedRole, + ErasedKeyAmalgamation<'a, key::PublicParts>); +impl_iterator!(key::SecretParts, key::UnspecifiedRole, + ErasedKeyAmalgamation<'a, key::SecretParts>); +impl_iterator!(key::UnspecifiedParts, key::UnspecifiedRole, + ErasedKeyAmalgamation<'a, key::UnspecifiedParts>); + +impl<'a, P, R> KeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + fn next_common(&mut self) -> Option<ErasedKeyAmalgamation<'a, key::PublicParts>> + { + tracer!(false, "KeyAmalgamationIter::next", 0); + t!("KeyAmalgamationIter: {:?}", self); + + if self.cert.is_none() { + return None; + } + let cert = self.cert.unwrap(); + + loop { + let ka : ErasedKeyAmalgamation<key::PublicParts> + = if ! self.primary { + self.primary = true; + PrimaryKeyAmalgamation::new(cert).into() + } else { + SubordinateKeyAmalgamation::new( + cert, self.subkey_iter.next()?).into() + }; + + t!("Considering key: {:?}", ka.key()); + + if self.key_handles.len() > 0 { + if !self.key_handles + .iter() + .any(|h| h.aliases(ka.key().key_handle())) + { + t!("{} is not one of the keys that we are looking for ({:?})", + ka.key().fingerprint(), self.key_handles); + continue; + } + } + + if let Some(want_secret) = self.secret { + if ka.key().has_secret() { + // We have a secret. + if ! want_secret { + t!("Have a secret... skipping."); + continue; + } + } else { + if want_secret { + t!("No secret... skipping."); + continue; + } + } + } + + if let Some(want_unencrypted_secret) = self.unencrypted_secret { + if let Some(secret) = ka.key().optional_secret() { + if let SecretKeyMaterial::Unencrypted { .. } = secret { + if ! want_unencrypted_secret { + t!("Unencrypted secret... skipping."); + continue; + } + } else { + if want_unencrypted_secret { + t!("Encrypted secret... skipping."); + continue; + } + } + } else { + // No secret. + t!("No secret... skipping."); + continue; + } + } + + return Some(ka); + } + } +} + +impl<'a, P, R> KeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + /// Returns a new `KeyAmalgamationIter` instance. + pub(crate) fn new(cert: &'a Cert) -> Self where Self: 'a { + KeyAmalgamationIter { + cert: Some(cert), + primary: false, + subkey_iter: cert.subkeys.iter(), + + // The filters. + secret: None, + unencrypted_secret: None, + key_handles: Vec::with_capacity(0), + + _p: std::marker::PhantomData, + _r: std::marker::PhantomData, + } + } + + /// Changes the filter to only return keys with secret key material. + pub fn secret(self) -> KeyAmalgamationIter<'a, key::SecretParts, R> { + KeyAmalgamationIter { + cert: self.cert, + primary: self.primary, + subkey_iter: self.subkey_iter, + + // The filters. + secret: Some(true), + unencrypted_secret: self.unencrypted_secret, + key_handles: self.key_handles, + + _p: std::marker::PhantomData, + _r: std::marker::PhantomData, + } + } + + /// Changes the filter to only return keys with unencrypted secret + /// key material. + pub fn unencrypted_secret(self) -> KeyAmalgamationIter<'a, key::SecretParts, R> { + KeyAmalgamationIter { + cert: self.cert, + primary: self.primary, + subkey_iter: self.subkey_iter, + + // The filters. + secret: self.secret, + unencrypted_secret: Some(true), + key_handles: self.key_handles, + + _p: std::marker::PhantomData, + _r: std::marker::PhantomData, + } + } + + /// Only returns a key if it matches the specified handle. + /// + /// Note: this function is cumulative. If you call this function + /// (or `key_handles`) multiple times, then the iterator returns a + /// key if it matches *any* of the specified handles. + pub fn key_handle<H>(mut self, h: H) -> Self + where H: Into<KeyHandle> + { + self.key_handles.push(h.into()); + self + } + + /// Only returns a key if it matches any of the specified handles. + /// + /// Note: this function is cumulative. If you call this function + /// (or `key_handle`) multiple times, then the iterator returns a + /// key if it matches *any* of the specified handles. + pub fn key_handles<'b>(mut self, h: impl Iterator<Item=&'b KeyHandle>) + -> Self + where 'a: 'b + { + self.key_handles.extend(h.map(|h| h.clone())); + self + } + + /// Changes the iterator to skip the primary key. + pub fn subkeys(self) -> KeyAmalgamationIter<'a, P, key::SubordinateRole> { + KeyAmalgamationIter { + cert: self.cert, + primary: true, + subkey_iter: self.subkey_iter, + + // The filters. + secret: self.secret, + unencrypted_secret: self.unencrypted_secret, + key_handles: self.key_handles, + + _p: std::marker::PhantomData, + _r: std::marker::PhantomData, + } + } + + /// Changes the iterator to only return keys that are valid at + /// time `time`. + /// + /// If `time` is None, then the current time is used. + /// + /// See `ValidKeyAmalgamationIter` for the definition of a valid key. + /// + /// This also makes a number of filters like `alive` and `revoked` + /// available and causes the iterator to return a + /// `KeyAmalgamation` instead of a bare `Key`. + /// + /// As a general rule of thumb, when encrypting or signing a + /// message, you only want to use keys that are alive, not + /// revoked, and have the appropriate capabilities keys right now. + /// For example: + /// + /// ```rust + /// # extern crate sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// # use openpgp::cert::prelude::*; + /// use openpgp::types::RevocationStatus; + /// use sequoia_openpgp::policy::StandardPolicy; + /// + /// # fn main() { f().unwrap(); } + /// # fn f() -> Result<()> { + /// # let (cert, _) = + /// # CertBuilder::general_purpose(None, Some("alice@example.org")) + /// # .generate()?; + /// let p = &StandardPolicy::new(); + /// + /// if let RevocationStatus::Revoked(_) = cert.revoked(p, None) { + /// // The certificate is revoked, don't use any keys from it. + /// } else if let Err(_) = cert.alive(p, None) { + /// // The certificate is not alive, don't use any keys from it. + /// } else { + /// for key in cert.keys().with_policy(p, None).alive().revoked(false).for_signing() { + /// // We can sign the message with this key. + /// } + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// When verifying a message, you only want to use keys that were + /// alive, not revoked, and signing capable when the message was + /// signed. These are the only keys that the signer could have + /// used; anything else suggests an attack, e.g., a forged time + /// stamp. + /// + /// For version 4 Signature packets, the `Signature Creation Time` + /// subpacket indicates when the signature was allegedly created. + /// For the purpose of finding the key to verify the signature, + /// this time stamp should be trusted. + /// + /// ```rust + /// # extern crate sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// # use openpgp::cert::prelude::*; + /// use openpgp::types::RevocationStatus; + /// use sequoia_openpgp::policy::StandardPolicy; + /// + /// # fn main() { f().unwrap(); } + /// # fn f() -> Result<()> { + /// let p = &StandardPolicy::new(); + /// + /// # let (cert, _) = + /// # CertBuilder::general_purpose(None, Some("alice@example.org")) + /// # .generate()?; + /// # let timestamp = None; + /// if let RevocationStatus::Revoked(_) = cert.revoked(p, None) { + /// // The certificate is revoked, don't use any keys from it. + /// } else if let Err(_) = cert.alive(p, None) { + /// // The certificate is not alive, don't use any keys from it. + /// } else { + /// for key in cert.keys().with_policy(p, timestamp).alive().revoked(false).for_signing() { + /// // Verify the message with this keys. + /// } + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Similarly, when decrypting a message, you should only consider + /// keys that were alive, not revoked, and encryption-capable when + /// the message was encrypted. Unfortunately, we don't know when + /// a message was encrypted. This, of course, precludes checking + /// the key's liveness, its revocation status, and its key + /// capabilities at the time of encryption. + /// + /// Decrypting a message encrypt to an expired or revoked key is + /// not a security problem. In fact, due to the slow propagation + /// of revocation certificates, it is better to not ignore revoked + /// keys in this case. However, checking whether a key is + /// encryption capable is important. [This discussion] explains + /// why using a signing key to decrypt a message can be dangerous. + /// + /// A possible workaround is to check whether the key is + /// encryption capable now. Since a key's key flags don't + /// typically change, this will correctly filter out keys that are + /// not encryption capable. But, it will also skip keys whose + /// self signature is now expired. Happily, no one appears to use + /// [signature expirations] on self signatures. Since using the + /// current time will almost never result in skipping the correct + /// decryption key, but does protect the user from a dangerous + /// attack, we recommend this approach when looking up a + /// decryption key. + /// + /// ```rust + /// # extern crate sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// # use openpgp::cert::prelude::*; + /// use sequoia_openpgp::policy::StandardPolicy; + /// + /// # fn main() { f().unwrap(); } + /// # fn f() -> Result<()> { + /// let p = &StandardPolicy::new(); + /// + /// # let (cert, _) = + /// # CertBuilder::general_purpose(None, Some("alice@example.org")) + /// # .generate()?; + /// let decryption_keys = cert.keys().with_policy(p, None) + /// .for_storage_encryption().for_transport_encryption() + /// .collect::<Vec<_>>(); + /// # Ok(()) + /// # } + /// ``` + /// + /// [signature expirations]: https://tools.ietf.org/html/rfc4880#section-5.2.3.10 + /// [This discussion]: https://crypto.stackexchange.com/a/12138 + pub fn with_policy<T>(self, policy: &'a dyn Policy, time: T) + -> ValidKeyAmalgamationIter<'a, P, R> + where T: Into<Option<SystemTime>> + { + ValidKeyAmalgamationIter { + cert: self.cert, + primary: self.primary, + subkey_iter: self.subkey_iter, + + policy, + time: time.into().unwrap_or_else(SystemTime::now), + + // The filters. + secret: self.secret, + unencrypted_secret: self.unencrypted_secret, + key_handles: self.key_handles, + flags: None, + alive: None, + revoked: None, + + _p: self._p, + _r: self._r, + } + } +} + +/// An iterator over all valid `Key`s in a certificate. +/// +/// A key is valid at time `t` if it was not created after `t` and it +/// has a live *self-signature* at time `t`. Note: this does not mean +/// that the key or the certificate is also live at time `t`; the key +/// or certificate may be expired, but the self-signature is still +/// valid. +/// +/// `ValidKeyAmalgamationIter` follows the builder pattern. There is no need to +/// explicitly finalize it, however: it already implements the +/// `Iterator` trait. +pub struct ValidKeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + // This is an option to make it easier to create an empty ValidKeyAmalgamationIter. + cert: Option<&'a Cert>, + primary: bool, + subkey_iter: slice::Iter<'a, KeyBundle<key::PublicParts, + key::SubordinateRole>>, + + // The policy. + policy: &'a dyn Policy, + + // The time. + time: SystemTime, + + // If not None, filters by whether a key has a secret. + secret: Option<bool>, + + // If not None, filters by whether a key has an unencrypted + // secret. + unencrypted_secret: Option<bool>, + + // Only return keys in this set. + key_handles: Vec<KeyHandle>, + + // If not None, only returns keys with the specified flags. + flags: Option<KeyFlags>, + + // If not None, filters by whether a key is alive at time `t`. + alive: Option<()>, + + // If not None, filters by whether the key is revoked or not at + // time `t`. + revoked: Option<bool>, + + _p: std::marker::PhantomData<P>, + _r: std::marker::PhantomData<R>, +} + +impl<'a, P, R> fmt::Debug for ValidKeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ValidKeyAmalgamationIter") + .field("policy", &self.policy) + .field("time", &self.time) + .field("secret", &self.secret) + .field("unencrypted_secret", &self.unencrypted_secret) + .field("key_handles", &self.key_handles) + .field("flags", &self.flags) + .field("alive", &self.alive) + .field("revoked", &self.revoked) + .finish() + } +} + +macro_rules! impl_iterator { + ($parts:path, $role:path, $item:ty) => { + impl<'a> Iterator for ValidKeyAmalgamationIter<'a, $parts, $role> + { + type Item = $item; + + fn next(&mut self) -> Option<Self::Item> { + // We unwrap the result of the conversion. But, this + // is safe by construction: next_common only returns + // keys that can be correctly converted. + self.next_common().map(|k| k.try_into().expect("filtered")) + } + } + } +} + +impl_iterator!(key::PublicParts, key::PrimaryRole, + ValidPrimaryKeyAmalgamation<'a, key::PublicParts>); +impl_iterator!(key::SecretParts, key::PrimaryRole, + ValidPrimaryKeyAmalgamation<'a, key::SecretParts>); +impl_iterator!(key::UnspecifiedParts, key::PrimaryRole, + ValidPrimaryKeyAmalgamation<'a, key::UnspecifiedParts>); + +impl_iterator!(key::PublicParts, key::SubordinateRole, + ValidSubordinateKeyAmalgamation<'a, key::PublicParts>); +impl_iterator!(key::SecretParts, key::SubordinateRole, + ValidSubordinateKeyAmalgamation<'a, key::SecretParts>); +impl_iterator!(key::UnspecifiedParts, key::SubordinateRole, + ValidSubordinateKeyAmalgamation<'a, key::UnspecifiedParts>); + +impl_iterator!(key::PublicParts, key::UnspecifiedRole, + ValidErasedKeyAmalgamation<'a, key::PublicParts>); +impl_iterator!(key::SecretParts, key::UnspecifiedRole, + ValidErasedKeyAmalgamation<'a, key::SecretParts>); +impl_iterator!(key::UnspecifiedParts, key::UnspecifiedRole, + ValidErasedKeyAmalgamation<'a, key::UnspecifiedParts>); + +impl<'a, P, R> ValidKeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + fn next_common(&mut self) + -> Option<ValidErasedKeyAmalgamation<'a, key::PublicParts>> + { + tracer!(false, "ValidKeyAmalgamationIter::next", 0); + t!("ValidKeyAmalgamationIter: {:?}", self); + + if self.cert.is_none() { + return None; + } + let cert = self.cert.unwrap(); + + if let Some(flags) = self.flags.as_ref() { + if flags.is_empty() { + // Nothing to do. + t!("short circuiting: flags is empty"); + return None; + } + } + + loop { + let ka = if ! self.primary { + self.primary = true; + let ka : ErasedKeyAmalgamation<'a, key::PublicParts> + = PrimaryKeyAmalgamation::new(cert).into(); + match ka.with_policy(self.policy, self.time) { + Ok(ka) => ka, + Err(err) => { + // The primary key is bad. Abort. + t!("Getting primary key: {:?}", err); + return None; + } + } + } else { + let ka : ErasedKeyAmalgamation<'a, key::PublicParts> + = SubordinateKeyAmalgamation::new( + cert.into(), self.subkey_iter.next()?).into(); + match ka.with_policy(self.policy, self.time) { + Ok(ka) => ka, + Err(err) => { + // The subkey is bad, abort. + t!("Getting subkey: {:?}", err); + continue; + } + } + }; + + let key = ka.key(); + t!("Considering key: {:?}", key); + + if self.key_handles.len() > 0 { + if !self.key_handles + .iter() + .any(|h| h.aliases(key.key_handle())) + { + t!("{} is not one of the keys that we are looking for ({:?})", + key.key_handle(), self.key_handles); + continue; + } + } + + if let Some(flags) = self.flags.as_ref() { + if !ka.has_any_key_flag(flags) { + t!("Have flags: {:?}, want flags: {:?}... skipping.", + flags, flags); + continue; + } + } + + if let Some(()) = self.alive { + if let Err(err) = ka.alive() { + t!("Key not alive: {:?}", err); + continue; + } + } + + if let Some(want_revoked) = self.revoked { + if let RevocationStatus::Revoked(_) = ka.revoked() { + // The key is definitely revoked. + if ! want_revoked { + t!("Key revoked... skipping."); + continue; + } + } else { + // The key is probably not revoked. + if want_revoked { + t!("Key not revoked... skipping."); + continue; + } + } + } + + if let Some(want_secret) = self.secret { + if key.has_secret() { + // We have a secret. + if ! want_secret { + t!("Have a secret... skipping."); + continue; + } + } else { + if want_secret { + t!("No secret... skipping."); + continue; + } + } + } + + if let Some(want_unencrypted_secret) = self.unencrypted_secret { + if let Some(secret) = key.optional_secret() { + if let SecretKeyMaterial::Unencrypted { .. } = secret { + if ! want_unencrypted_secret { + t!("Unencrypted secret... skipping."); + continue; + } + } else { + if want_unencrypted_secret { + t!("Encrypted secret... skipping."); + continue; + } + } + } else { + // No secret. + t!("No secret... skipping."); + continue; + } + } + + return Some(ka.into()); + } + } +} + +impl<'a, P, R> ValidKeyAmalgamationIter<'a, P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + /// Returns keys that have the at least one of the flags specified + /// in `flags`. + /// + /// If you call this function (or one of `for_certification` or + /// `for_signing` functions) multiple times, the *union* of the + /// values is used. Thus, + /// `cert.flags().for_certification().for_signing()` will return + /// keys that are certification capable *or* signing capable. + /// + /// If you need more complex filtering, e.g., you want a key that + /// is both certification and signing capable, then use + /// [`Iterator::filter`]. + /// + /// [`Iterator::filter`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter + pub fn key_flags<F>(mut self, flags: F) -> Self + where F: Borrow<KeyFlags> + { + let flags = flags.borrow(); + if let Some(flags_old) = self.flags { + self.flags = Some(flags | &flags_old); + } else { + self.flags = Some(flags.clone()); + } + self + } + + /// Returns keys that are certification capable. + /// + /// See `key_flags` for caveats. + pub fn for_certification(self) -> Self { + self.key_flags(KeyFlags::default().set_certification(true)) + } + + /// Returns keys that are signing capable. + /// + /// See `key_flags` for caveats. + pub fn for_signing(self) -> Self { + self.key_flags(KeyFlags::default().set_signing(true)) + } + + /// Returns keys that are authentication capable. + /// + /// See `key_flags` for caveats. + pub fn for_authentication(self) -> Self { + self.key_flags(KeyFlags::default().set_authentication(true)) + } + + /// Returns keys that are capable of encrypting data at rest. + /// + /// See `key_flags` for caveats. + pub fn for_storage_encryption(self) -> Self { + self.key_flags(KeyFlags::default().set_storage_encryption(true)) + } + + /// Returns keys that are capable of encrypting data for transport. + /// + /// See `key_flags` for caveats. + pub fn for_transport_encryption(self) -> Self { + self.key_flags(KeyFlags::default().set_transport_encryption(true)) + } + + /// Only returns keys that are alive. + /// + /// Note: this only checks if the key is alive; it does not check + /// whether the certificate is alive. + pub fn alive(mut self) -> Self + { + self.alive = Some(()); + self + } + + /// Filters by whether a key is definitely revoked. + /// + /// A value of None disables this filter. + /// + /// Note: If you call this function multiple times on the same + /// iterator, only the last value is used. + /// + /// Note: This only checks if the key is not revoked; it does not + /// check whether the certificate not revoked. + /// + /// This filter checks whether a key's revocation status is + /// `RevocationStatus::Revoked` or not. The latter (i.e., + /// `revoked(false)`) is equivalent to: + /// + /// ```rust + /// extern crate sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::RevocationStatus; + /// use openpgp::cert::prelude::*; + /// use sequoia_openpgp::policy::StandardPolicy; + /// + /// # fn main() { f().unwrap(); } + /// # fn f() -> Result<()> { + /// # let (cert, _) = + /// # CertBuilder::general_purpose(None, Some("alice@example.org")) + /// # .generate()?; + /// let p = &StandardPolicy::new(); + /// + /// # let timestamp = None; + /// let non_revoked_keys = cert + /// .keys() + /// .with_policy(p, timestamp) + /// .filter(|ka| { + /// match ka.revoked() { + /// RevocationStatus::Revoked(_) => + /// // It's definitely rev |