nip4e: reject stale key requests

This commit is contained in:
2026-11-11 09:28:41 +07:00
parent 41b6c80057
commit 6fc7732128
+44 -17
View File
@@ -20,16 +20,29 @@ use person::Directory;
use settings::{AppSettings, NIP4E_ROTATION_MAX_DAYS, NIP4E_ROTATION_MIN_DAYS}; use settings::{AppSettings, NIP4E_ROTATION_MAX_DAYS, NIP4E_ROTATION_MIN_DAYS};
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{Announcement, NostrRegistry, UniversalSigner, credentials}; use state::{Announcement, NostrRegistry, UniversalSigner, credentials};
use storage::{SECONDS_PER_DAY, archive_key, get_keys, load_device_keys, set_keys};
use ui::Avatar; use ui::Avatar;
mod storage; mod storage;
use storage::{SECONDS_PER_DAY, archive_key, get_keys, load_device_keys, set_keys}; /// Keep the durable processed-request log bounded
const PROCESSED_REQUESTS_LIMIT: usize = 256;
/// Ignore key requests older than a day so stale events on relays cannot resurface
const REQUEST_MAX_AGE: u64 = SECONDS_PER_DAY;
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
DeviceRegistry::set_global(cx.new(DeviceRegistry::new), cx); DeviceRegistry::set_global(cx.new(DeviceRegistry::new), cx);
} }
struct DeviceNotification;
/// A key request awaiting user approval on this device
#[derive(Debug)]
struct PendingRequest {
event: Event,
client_key: PublicKey,
}
struct GlobalDeviceRegistry(Entity<DeviceRegistry>); struct GlobalDeviceRegistry(Entity<DeviceRegistry>);
impl Global for GlobalDeviceRegistry {} impl Global for GlobalDeviceRegistry {}
@@ -74,7 +87,7 @@ pub struct DeviceRegistry {
resync: Arc<AtomicBool>, resync: Arc<AtomicBool>,
/// Whether a key rotation is in flight, to coalesce overlapping requests /// Whether a key rotation is in flight, to coalesce overlapping requests
rotating: bool, rotating: bool,
/// Person directory for requesters shown in notifications /// Person directory for requesters shown in the approval dialog
directory: Entity<Directory>, directory: Entity<Directory>,
/// Relay notification listener /// Relay notification listener
notification_listener: Option<Task<Result<(), Error>>>, notification_listener: Option<Task<Result<(), Error>>>,
@@ -590,10 +603,13 @@ impl DeviceRegistry {
self.tasks.push(cx.background_spawn(async move { self.tasks.push(cx.background_spawn(async move {
let public_key = signer.get_public_key_async().await?; let public_key = signer.get_public_key_async().await?;
let id = SubscriptionId::new("dekey-requests");
// Construct a filter for encryption key requests. let id = SubscriptionId::new("dekey-requests");
let filter = Filter::new().kind(Kind::Custom(4454)).author(public_key); let since = Timestamp::now().as_secs().saturating_sub(REQUEST_MAX_AGE);
let filter = Filter::new()
.kind(Kind::Custom(4454))
.author(public_key)
.since(Timestamp::from_secs(since));
// Subscribe to the device key requests on user's write relays // Subscribe to the device key requests on user's write relays
client.subscribe(vec![filter]).with_id(id).await?; client.subscribe(vec![filter]).with_id(id).await?;
@@ -903,12 +919,22 @@ impl DeviceRegistry {
/// Handle encryption request /// Handle encryption request
fn ask_for_approval(&mut self, event: Event, cx: &mut Context<Self>) { fn ask_for_approval(&mut self, event: Event, cx: &mut Context<Self>) {
let expires_at = Timestamp::now()
.as_secs()
.saturating_sub(event.created_at.as_secs());
if expires_at > REQUEST_MAX_AGE {
log::warn!("Ignoring expired encryption key request");
return;
}
let Some(client_key) = client_pubkey_from_event(&event) else { let Some(client_key) = client_pubkey_from_event(&event) else {
log::warn!("Ignoring invalid encryption key request: no valid requester key"); log::warn!("Ignoring invalid encryption key request: no valid requester key");
return; return;
}; };
let read = credentials::read_processed_requests(cx); let read = credentials::read_processed_requests(cx);
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
let processed = match read.await { let processed = match read.await {
Ok(bytes) => bytes Ok(bytes) => bytes
@@ -1077,6 +1103,19 @@ impl DeviceRegistry {
) )
.into_any_element() .into_any_element()
}) })
.on_close({
let entity = entity.clone();
let request_id = event.id;
move |_window, cx| {
// Closing without approving rejects the request, so it must not resurface
entity
.update(cx, |this, cx| {
this.mark_request_processed(request_id, cx);
})
.ok();
}
})
.action(move |_this, _window, _cx| { .action(move |_this, _window, _cx| {
let view = entity.clone(); let view = entity.clone();
let event = event.clone(); let event = event.clone();
@@ -1101,18 +1140,6 @@ impl DeviceRegistry {
} }
} }
struct DeviceNotification;
/// A key request awaiting user approval on this device
#[derive(Debug)]
struct PendingRequest {
event: Event,
client_key: PublicKey,
}
/// Keep the durable processed-request log bounded
const PROCESSED_REQUESTS_LIMIT: usize = 256;
/// Parse concatenated hex event ids /// Parse concatenated hex event ids
fn parse_processed_requests(bytes: &[u8]) -> VecDeque<EventId> { fn parse_processed_requests(bytes: &[u8]) -> VecDeque<EventId> {
let Ok(hex) = std::str::from_utf8(bytes) else { let Ok(hex) = std::str::from_utf8(bytes) else {