From 31b2d7961a73c2593c8b212e06648721c99d64d4 Mon Sep 17 00:00:00 2001 From: link2xt Date: Thu, 2 Jul 2026 00:32:03 +0000 Subject: [PATCH] fix: ensure public key signatures are not in the past compared to the public key We use the timestamp of the latest transport modification as the timestamp of the Direct Key Signature. However, first transport is created before the public key and existing transports may even have zero timestamp if they were created before migration 142. For interoperability with Sequoia-PGP, make sure signatures always have a timestamp that is not in the past compared to the timestamp of the primary key. --- src/key.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/key.rs b/src/key.rs index ff088a1c5..1f9febb9e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -131,7 +131,13 @@ pub(crate) fn secret_key_to_public_key( relay_addrs: &str, ) -> Result { info!(context, "Converting secret key to public key."); - let timestamp = pgp::types::Timestamp::from_secs(timestamp); + + // Make sure timestamp of created signatures + // is not in the past compared to the primary key timestamp. + let timestamp = std::cmp::max( + signed_secret_key.primary_key.created_at(), + pgp::types::Timestamp::from_secs(timestamp), + ); // Subpackets that we want to share between DKS and User ID signature. let common_subpackets = || -> Result> { @@ -650,10 +656,12 @@ impl std::str::FromStr for Fingerprint { #[cfg(test)] mod tests { use std::sync::{Arc, LazyLock}; + use std::time::Duration; use super::*; use crate::config::Config; - use crate::test_utils::{TestContext, alice_keypair}; + use crate::test_utils::{TestContext, TestContextManager, alice_keypair}; + use crate::tools::SystemTime; static KEYPAIR: LazyLock = LazyLock::new(alice_keypair); @@ -874,6 +882,25 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD assert_eq!(nrows().await, 1); } + /// Tests that key signature timestamp + /// is not in the past compared to the primary key creation timestamp. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_signature_not_in_the_past() -> Result<()> { + let mut tcm = TestContextManager::new(); + + let t = &tcm.unconfigured().await; + t.configure_addr("foo@example.org").await; + + SystemTime::shift(Duration::from_secs(600)); + + let key = load_self_public_key(t).await?; + assert!( + key.details.direct_signatures[0].created().unwrap() >= key.primary_key.created_at() + ); + + Ok(()) + } + #[test] fn test_fingerprint_from_str() { let res = Fingerprint::new(vec![