nip4e: reject stale key requests
This commit is contained in:
+44
-17
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user