From 31f89b72ddd704fef71ff73f9c25714c62b618d2 Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 8 Jun 2026 00:23:04 +0200 Subject: [PATCH] feat: take key flags and expiration into account when selecting the key --- src/pgp.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/pgp.rs b/src/pgp.rs index 8e1d27555..4ecf95cfe 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -1,5 +1,6 @@ //! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp). +use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::io::Cursor; @@ -83,13 +84,63 @@ pub(crate) fn create_keypair(addr: EmailAddress) -> Result { /// Selects a subkey of the public key to use for encryption. /// -/// Returns `None` if the public key cannot be used for encryption. +/// The key is selected according to +/// . +/// If multiple keys are available, the one that will expire sooner is selected. /// -/// TODO: take key flags and expiration dates into account -fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> { +/// Returns `None` if the public key cannot be used for encryption. +fn select_pk_for_encryption(now: u32, key: &SignedPublicKey) -> Option<&SignedPublicSubKey> { key.public_subkeys .iter() - .find(|subkey| subkey.algorithm().can_encrypt()) + .filter(|subkey| subkey.algorithm().can_encrypt()) + .filter_map(|subkey| { + // TODO: deal with multiple signatures. + let signature = subkey.signatures.first()?; + + let key_flags = signature.key_flags(); + if !key_flags.encrypt_comms() { + return None; + } + + if let Some(expiration_duration) = signature + .key_expiration_time() + .filter(|duration| duration.as_secs() != 0) + && now + > subkey + .created_at() + .as_secs() + .saturating_add(expiration_duration.as_secs()) + { + // Key is expired. + return None; + } + Some((subkey, signature)) + }) + .min_by(|(subkey1, signature1), (subkey2, signature2)| { + match ( + signature1 + .key_expiration_time() + .filter(|duration| duration.as_secs() != 0), + signature2 + .key_expiration_time() + .filter(|duration| duration.as_secs() != 0), + ) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + (Some(expiration1), Some(expiration2)) => (subkey1 + .created_at() + .as_secs() + .saturating_add(expiration1.as_secs())) + .cmp( + &(subkey2 + .created_at() + .as_secs() + .saturating_add(expiration2.as_secs())), + ), + } + }) + .map(|(subkey, _signature)| subkey) } /// Version of SEIPD packet to use. @@ -119,10 +170,11 @@ pub async fn pk_encrypt( Handle::current() .spawn_blocking(move || { let mut rng = thread_rng(); + let now = pgp::types::Timestamp::now(); let pkeys = public_keys_for_encryption .iter() - .filter_map(select_pk_for_encryption); + .filter_map(|key| select_pk_for_encryption(now.as_secs(), key)); let subpkts = { let mut hashed = Vec::with_capacity(1 + public_keys_for_encryption.len() + 1); hashed.push(Subpacket::critical(SubpacketData::SignatureCreationTime(