iris_chat_protocol/protocol_engine/
engine_invite_owner.rs1use super::*;
2
3const INVITE_OWNER_APP_KEYS_MAX_EVENT_BYTES: usize = 64 * 1024;
4const INVITE_OWNER_APP_KEYS_MAX_DEVICES: usize = 64;
5const INVITE_OWNER_APP_KEYS_MAX_FUTURE_SKEW_SECS: u64 = 5 * 60;
6
7impl ProtocolEngine {
8 pub(super) fn update_invite_owner_app_keys_evidence(
9 &mut self,
10 owner: NdrOwnerPubkey,
11 event: &Event,
12 app_keys: &AppKeys,
13 ) -> bool {
14 if !app_keys_event_is_acceptable_for_owner(event.pubkey, event, unix_now().get()) {
15 return false;
16 }
17 let previous = self.invite_owner_app_keys_evidence.get(&owner).cloned();
18 let created_at_secs = event.created_at.as_secs();
19 let next = match previous.clone() {
20 None => ProtocolAppKeysEvidence::Verified(Box::new(event.clone())),
21 Some(ProtocolAppKeysEvidence::Ambiguous {
22 created_at_secs: ambiguous_at,
23 }) if created_at_secs > ambiguous_at => {
24 ProtocolAppKeysEvidence::Verified(Box::new(event.clone()))
25 }
26 Some(ProtocolAppKeysEvidence::Ambiguous {
27 created_at_secs: ambiguous_at,
28 }) => ProtocolAppKeysEvidence::Ambiguous {
29 created_at_secs: ambiguous_at,
30 },
31 Some(ProtocolAppKeysEvidence::Verified(current)) => {
32 let current_created_at = current.created_at.as_secs();
33 if created_at_secs > current_created_at {
34 ProtocolAppKeysEvidence::Verified(Box::new(event.clone()))
35 } else if created_at_secs < current_created_at || current.id == event.id {
36 ProtocolAppKeysEvidence::Verified(current)
37 } else {
38 let same_roster =
39 AppKeys::from_event(current.as_ref()).is_ok_and(|current_app_keys| {
40 app_keys_device_pubkeys(¤t_app_keys)
41 == app_keys_device_pubkeys(app_keys)
42 });
43 if same_roster {
44 ProtocolAppKeysEvidence::Verified(if event.id < current.id {
45 Box::new(event.clone())
46 } else {
47 current
48 })
49 } else {
50 ProtocolAppKeysEvidence::Ambiguous { created_at_secs }
51 }
52 }
53 }
54 };
55 let changed = previous.as_ref() != Some(&next);
56 self.invite_owner_app_keys_evidence.insert(owner, next);
57 changed
58 }
59
60 pub fn import_private_invite_session_once(
61 &mut self,
62 response_event_id: &str,
63 owner_pubkey: PublicKey,
64 authenticated_device: PublicKey,
65 state: SessionState,
66 now: UnixSeconds,
67 ) -> anyhow::Result<ProtocolInviteSessionImportOutcome> {
68 if self
69 .processed_private_invite_response_ids
70 .iter()
71 .any(|processed| processed == response_event_id)
72 {
73 return Ok(ProtocolInviteSessionImportOutcome::AlreadyImported);
74 }
75 if owner_pubkey != authenticated_device {
76 match self
77 .invite_owner_app_keys_membership(ndr_owner(owner_pubkey), authenticated_device)
78 {
79 Some(true) => {}
80 Some(false) => {
81 return Ok(ProtocolInviteSessionImportOutcome::Blocked(
82 ProtocolAcceptInviteBlock::UnauthorizedDevice {
83 owner_pubkey,
84 device_pubkey: authenticated_device,
85 },
86 ))
87 }
88 None => {
89 return Ok(ProtocolInviteSessionImportOutcome::Blocked(
90 ProtocolAcceptInviteBlock::MissingOwnerRoster {
91 owner_pubkey,
92 device_pubkey: authenticated_device,
93 },
94 ))
95 }
96 }
97 }
98
99 let checkpoint = self.state_checkpoint();
100 self.session_manager.import_session_state(
101 ndr_owner(owner_pubkey),
102 ndr_device(authenticated_device),
103 state,
104 NdrUnixSeconds(now.get()),
105 );
106 self.processed_private_invite_response_ids
107 .push(response_event_id.to_string());
108 self.invalidate_known_message_author_cache();
111 self.wake_pending_protocol_for_owner(ndr_owner(owner_pubkey));
115 if let Err(error) = self.persist_now() {
116 self.restore_checkpoint(checkpoint);
117 return Err(error);
118 }
119 let retry_batch = self.retry_pending_protocol(NdrUnixSeconds(now.get()))?;
120 Ok(ProtocolInviteSessionImportOutcome::Imported(retry_batch))
121 }
122
123 pub(super) fn invite_owner_app_keys_membership(
124 &self,
125 owner: NdrOwnerPubkey,
126 device: PublicKey,
127 ) -> Option<bool> {
128 self.invite_owner_exact_app_keys_membership(owner, device)
129 }
130
131 pub(super) fn invite_owner_exact_app_keys_membership(
132 &self,
133 owner: NdrOwnerPubkey,
134 device: PublicKey,
135 ) -> Option<bool> {
136 match self.invite_owner_app_keys_evidence.get(&owner)? {
137 ProtocolAppKeysEvidence::Verified(event) => AppKeys::from_event(event)
138 .ok()
139 .map(|app_keys| app_keys.get_device(&device).is_some()),
140 ProtocolAppKeysEvidence::Ambiguous { .. } => None,
141 }
142 }
143
144 pub fn persisted_invite_owner_device_is_authorized(
148 storage: Arc<dyn StorageAdapter>,
149 local_owner_pubkey: PublicKey,
150 local_device_keys: &Keys,
151 invite_owner_pubkey: PublicKey,
152 invite_device_pubkey: PublicKey,
153 ) -> anyhow::Result<bool> {
154 if invite_owner_pubkey == invite_device_pubkey {
155 return Ok(true);
156 }
157 let local_owner = ndr_owner(local_owner_pubkey);
158 let local_device = ndr_device(local_device_keys.public_key());
159 let Some(engine) = Self::load_persisted_state(
160 storage,
161 local_owner_pubkey,
162 local_owner,
163 local_device,
164 local_device_keys.secret_key().to_secret_bytes(),
165 false,
166 )?
167 else {
168 return Ok(false);
169 };
170 Ok(engine.invite_owner_device_is_authorized(invite_owner_pubkey, invite_device_pubkey))
171 }
172
173 pub fn invite_owner_device_is_authorized(
176 &self,
177 invite_owner_pubkey: PublicKey,
178 invite_device_pubkey: PublicKey,
179 ) -> bool {
180 invite_owner_pubkey == invite_device_pubkey
181 || matches!(
182 self.invite_owner_exact_app_keys_membership(
183 ndr_owner(invite_owner_pubkey),
184 invite_device_pubkey,
185 ),
186 Some(true)
187 )
188 }
189}
190
191pub(super) fn sanitize_invite_owner_persisted_state(state: &mut ProtocolEnginePersistedState) {
192 state
193 .invite_owner_app_keys_evidence
194 .retain(|owner, evidence| match evidence {
195 ProtocolAppKeysEvidence::Verified(event) => public_owner(*owner).is_ok_and(|owner| {
196 app_keys_event_is_acceptable_for_owner(owner, event, unix_now().get())
197 }),
198 ProtocolAppKeysEvidence::Ambiguous { .. } => true,
199 });
200 let exact_verified_owners = state
201 .invite_owner_app_keys_evidence
202 .iter()
203 .filter_map(|(owner, evidence)| {
204 matches!(evidence, ProtocolAppKeysEvidence::Verified(_)).then_some(*owner)
205 })
206 .collect::<BTreeSet<_>>();
207 if state.app_keys_provenance_version != PROTOCOL_APP_KEYS_PROVENANCE_VERSION {
208 state.verified_app_keys_owners = exact_verified_owners.clone();
209 } else {
210 state
211 .verified_app_keys_owners
212 .extend(exact_verified_owners.iter().copied());
213 }
214}
215
216pub fn app_keys_event_is_acceptable_for_owner(
219 owner: PublicKey,
220 event: &Event,
221 now_secs: u64,
222) -> bool {
223 let device_tags = event
224 .tags
225 .iter()
226 .filter(|tag| tag.as_slice().first().map(String::as_str) == Some("device"))
227 .collect::<Vec<_>>();
228 event.pubkey == owner
229 && event.created_at.as_secs()
230 <= now_secs.saturating_add(INVITE_OWNER_APP_KEYS_MAX_FUTURE_SKEW_SECS)
231 && serde_json::to_vec(event)
232 .is_ok_and(|bytes| bytes.len() <= INVITE_OWNER_APP_KEYS_MAX_EVENT_BYTES)
233 && device_tags.len() <= INVITE_OWNER_APP_KEYS_MAX_DEVICES
234 && device_tags.iter().all(|tag| {
235 let values = tag.as_slice();
236 values.len() >= 3 && values[2].parse::<u64>().is_ok()
237 })
238 && AppKeys::from_event(event).is_ok()
239}