// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // TODO // // handle the case where we can't create a session with a device. clearing our // stale key share requests that we'll never be able to handle. // // If we don't trust the device store an object that remembers the request and // let the users introspect that object. use std::{collections::BTreeMap, sync::Arc}; use dashmap::{mapref::entry::Entry, DashMap, DashSet}; use matrix_sdk_common::uuid::Uuid; use ruma::{ api::client::r0::keys::claim_keys::Request as KeysClaimRequest, events::{ forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, room_key_request::{Action, RequestedKeyInfo, RoomKeyRequestToDeviceEventContent}, secret::{ request::{ RequestAction, RequestToDeviceEventContent as SecretRequestEventContent, SecretName, }, send::SendToDeviceEventContent as SecretSendEventContent, }, AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent, }, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId, }; use tracing::{debug, info, trace, warn}; use super::{GossipRequest, KeyForwardDecision, RequestEvent, RequestInfo, SecretInfo, WaitQueue}; use crate::{ error::{OlmError, OlmResult}, olm::{InboundGroupSession, Session, ShareState}, requests::{OutgoingRequest, ToDeviceRequest}, session_manager::GroupSessionCache, store::{Changes, CryptoStoreError, SecretImportError, Store}, Device, }; #[derive(Debug, Clone)] pub(crate) struct GossipMachine { user_id: Arc, device_id: Arc, store: Store, outbound_group_sessions: GroupSessionCache, outgoing_requests: Arc>, incoming_key_requests: Arc>, wait_queue: WaitQueue, users_for_key_claim: Arc>>, } impl GossipMachine { pub fn new( user_id: Arc, device_id: Arc, store: Store, outbound_group_sessions: GroupSessionCache, users_for_key_claim: Arc>>, ) -> Self { Self { user_id, device_id, store, outbound_group_sessions, outgoing_requests: Default::default(), incoming_key_requests: Default::default(), wait_queue: WaitQueue::new(), users_for_key_claim, } } /// Load stored outgoing requests that were not yet sent out. async fn load_outgoing_requests(&self) -> Result, CryptoStoreError> { Ok(self .store .get_unsent_secret_requests() .await? .into_iter() .filter(|i| !i.sent_out) .map(|info| info.to_request(self.device_id())) .collect()) } /// Our own user id. pub fn user_id(&self) -> &UserId { &self.user_id } /// Our own device id. pub fn device_id(&self) -> &DeviceId { &self.device_id } pub async fn outgoing_to_device_requests( &self, ) -> Result, CryptoStoreError> { let mut key_requests = self.load_outgoing_requests().await?; let key_forwards: Vec = self.outgoing_requests.iter().map(|i| i.value().clone()).collect(); key_requests.extend(key_forwards); let users_for_key_claim: BTreeMap<_, _> = self .users_for_key_claim .iter() .map(|i| { let device_map = i .value() .iter() .map(|d| (d.key().to_owned(), DeviceKeyAlgorithm::SignedCurve25519)) .collect(); (i.key().to_owned(), device_map) }) .collect(); if !users_for_key_claim.is_empty() { let key_claim_request = KeysClaimRequest::new(users_for_key_claim); key_requests.push(OutgoingRequest { request_id: Uuid::new_v4(), request: Arc::new(key_claim_request.into()), }); } Ok(key_requests) } /// Receive a room key request event. pub fn receive_incoming_key_request( &self, event: &ToDeviceEvent, ) { self.receive_event(event.clone().into()) } fn receive_event(&self, event: RequestEvent) { // Some servers might send to-device events to ourselves if we send one // out using a wildcard instead of a specific device as a recipient. // // Check if we're the sender of this request event and ignore it if // so. if event.sender() == self.user_id() && event.requesting_device_id() == self.device_id() { trace!("Received a secret request event from ourselves, ignoring") } else { let request_info = event.to_request_info(); self.incoming_key_requests.insert(request_info, event); } } pub fn receive_incoming_secret_request( &self, event: &ToDeviceEvent, ) { self.receive_event(event.clone().into()) } /// Handle all the incoming key requests that are queued up and empty our /// key request queue. pub async fn collect_incoming_key_requests(&self) -> OlmResult> { let mut changed_sessions = Vec::new(); for item in self.incoming_key_requests.iter() { let event = item.value(); if let Some(s) = match event { RequestEvent::KeyShare(e) => self.handle_key_request(e).await?, RequestEvent::Secret(e) => self.handle_secret_request(e).await?, } { changed_sessions.push(s); } } self.incoming_key_requests.clear(); Ok(changed_sessions) } /// Store the key share request for later, once we get an Olm session with /// the given device [`retry_keyshare`](#method.retry_keyshare) should be /// called. fn handle_key_share_without_session(&self, device: Device, event: RequestEvent) { self.users_for_key_claim .entry(device.user_id().to_owned()) .or_insert_with(DashSet::new) .insert(device.device_id().into()); self.wait_queue.insert(&device, event); } /// Retry keyshares for a device that previously didn't have an Olm session /// with us. /// /// This should be only called if the given user/device got a new Olm /// session. /// /// # Arguments /// /// * `user_id` - The user id of the device that we created the Olm session /// with. /// /// * `device_id` - The device id of the device that got the Olm session. pub fn retry_keyshare(&self, user_id: &UserId, device_id: &DeviceId) { if let Entry::Occupied(e) = self.users_for_key_claim.entry(user_id.to_owned()) { e.get().remove(device_id); if e.get().is_empty() { e.remove(); } } for (key, event) in self.wait_queue.remove(user_id, device_id) { if !self.incoming_key_requests.contains_key(&key) { self.incoming_key_requests.insert(key, event); } } } async fn handle_secret_request( &self, event: &ToDeviceEvent, ) -> OlmResult> { let secret_name = match &event.content.action { RequestAction::Request(s) => s, // We ignore cancellations here since there's nothing to serve. RequestAction::RequestCancellation => return Ok(None), action => { warn!(action =? action, "Unknown secret request action"); return Ok(None); } }; let content = if let Some(secret) = self.store.export_secret(secret_name).await { SecretSendEventContent::new(event.content.request_id.to_owned(), secret) } else { info!(secret_name =? secret_name, "Can't serve a secret request, secret isn't found"); return Ok(None); }; let device = self.store.get_device(&event.sender, &event.content.requesting_device_id).await?; Ok(if let Some(device) = device { if device.user_id() == self.user_id() { if device.verified() { info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), secret_name =? secret_name, "Sharing a secret with a device", ); match self.share_secret(&device, content).await { Ok(s) => Ok(Some(s)), Err(OlmError::MissingSession) => { info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), secret_name = secret_name.as_ref(), "Secret request is missing an Olm session, \ putting the request in the wait queue", ); self.handle_key_share_without_session(device, event.clone().into()); Ok(None) } Err(e) => Err(e), }? } else { info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), secret_name =? secret_name, "Received a secret request that we won't serve, the device isn't trusted", ); None } } else { info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), secret_name =? secret_name, "Received a secret request that we won't serve, the device doesn't belong to us", ); None } } else { warn!( user_id = event.sender.as_str(), device_id = event.content.requesting_device_id.as_str(), secret_name =? secret_name, "Received a secret request form an unknown device", ); self.store.update_tracked_user(&event.sender, true).await?; None }) } /// Handle a single incoming key request. async fn handle_key_request( &self, event: &ToDeviceEvent, ) -> OlmResult> { let key_info = match &event.content.action { Action::Request => { if let Some(info) = &event.content.body { info } else { warn!( sender = event.sender.as_str(), requesting_device_id = event.content.requesting_device_id.as_str(), "Received a key request with a request of action, but no key info was found", ); return Ok(None); } } // We ignore cancellations here since there's nothing to serve. Action::CancelRequest => return Ok(None), action => { warn!( sender = event.sender.as_str(), requesting_device_id = event.content.requesting_device_id.as_str(), action = action.as_ref(), "Received a room key request with an unknown action", ); return Ok(None); } }; let session = self .store .get_inbound_group_session( &key_info.room_id, &key_info.sender_key, &key_info.session_id, ) .await?; let session = if let Some(s) = session { s } else { debug!( user_id = event.sender.as_str(), device_id = event.content.requesting_device_id.as_str(), session_id = key_info.session_id.as_str(), room_id = key_info.room_id.as_str(), "Received a room key request for an unknown inbound group session", ); return Ok(None); }; let device = self.store.get_device(&event.sender, &event.content.requesting_device_id).await?; if let Some(device) = device { match self.should_share_key(&device, &session).await { Err(e) => { if let KeyForwardDecision::ChangedSenderKey = e { warn!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), "Received a key request from a device that changed \ their curve25519 sender key" ); } else { debug!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), reason =? e, "Received a key request that we won't serve", ); } Ok(None) } Ok(message_index) => { info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), session_id = key_info.session_id.as_str(), room_id = key_info.room_id.as_str(), message_index =? message_index, "Serving a room key request", ); match self.share_session(&session, &device, message_index).await { Ok(s) => Ok(Some(s)), Err(OlmError::MissingSession) => { info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), session_id = key_info.session_id.as_str(), "Key request is missing an Olm session, \ putting the request in the wait queue", ); self.handle_key_share_without_session(device, event.to_owned().into()); Ok(None) } Err(e) => Err(e), } } } } else { warn!( user_id = event.sender.as_str(), device_id = event.content.requesting_device_id.as_str(), "Received a key request from an unknown device", ); self.store.update_tracked_user(&event.sender, true).await?; Ok(None) } } async fn share_secret( &self, device: &Device, content: SecretSendEventContent, ) -> OlmResult { let (used_session, content) = device.encrypt(AnyToDeviceEventContent::SecretSend(content)).await?; let request = ToDeviceRequest::new( device.user_id(), device.device_id().to_owned(), AnyToDeviceEventContent::RoomEncrypted(content), ); let request = OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }; self.outgoing_requests.insert(request.request_id, request); Ok(used_session) } async fn share_session( &self, session: &InboundGroupSession, device: &Device, message_index: Option, ) -> OlmResult { let (used_session, content) = device.encrypt_session(session.clone(), message_index).await?; let request = ToDeviceRequest::new( device.user_id(), device.device_id().to_owned(), AnyToDeviceEventContent::RoomEncrypted(content), ); let request = OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }; self.outgoing_requests.insert(request.request_id, request); Ok(used_session) } /// Check if it's ok to share a session with the given device. /// /// The logic for this currently is as follows: /// /// * Share any session with our own devices as long as they are trusted. /// /// * Share with devices of other users only sessions that were meant to be /// shared with them in the first place, in other words if an outbound /// session still exists and the session was shared with that user/device /// pair. /// /// # Arguments /// /// * `device` - The device that is requesting a session from us. /// /// * `session` - The session that was requested to be shared. async fn should_share_key( &self, device: &Device, session: &InboundGroupSession, ) -> Result, KeyForwardDecision> { let outbound_session = self .outbound_group_sessions .get_with_id(session.room_id(), session.session_id()) .await .ok() .flatten(); let own_device_check = || { if device.verified() { Ok(None) } else { Err(KeyForwardDecision::UntrustedDevice) } }; // If we have a matching outbound session we can check the list of // users/devices that received the session, if it wasn't shared check if // it's our own device and if it's trusted. if let Some(outbound) = outbound_session { match outbound.is_shared_with(device) { ShareState::Shared(message_index) => Ok(Some(message_index)), _ if device.user_id() == self.user_id() => own_device_check(), ShareState::SharedButChangedSenderKey => Err(KeyForwardDecision::ChangedSenderKey), ShareState::NotShared => Err(KeyForwardDecision::OutboundSessionNotShared), } // Else just check if it's one of our own devices that requested the key // and check if the device is trusted. } else if device.user_id() == self.user_id() { own_device_check() // Otherwise, there's not enough info to decide if we can safely share // the session. } else { Err(KeyForwardDecision::MissingOutboundSession) } } /// Check if it's ok, or rather if it makes sense to automatically request /// a key from our other devices. /// /// # Arguments /// /// * `key_info` - The info of our key request containing information about /// the key we wish to request. async fn should_request_key(&self, key_info: &SecretInfo) -> Result { let request = self.store.get_secret_request_by_info(key_info).await?; // Don't send out duplicate requests, users can re-request them if they // think a second request might succeed. if request.is_none() { let devices = self.store.get_user_devices(self.user_id()).await?; // Devices will only respond to key requests if the devices are // verified, if the device isn't verified by us it's unlikely that // we're verified by them either. Don't request keys if there isn't // at least one verified device. if devices.is_any_verified() { Ok(true) } else { Ok(false) } } else { Ok(false) } } /// Create a new outgoing key request for the key with the given session id. /// /// This will queue up a new to-device request and store the key info so /// once we receive a forwarded room key we can check that it matches the /// key we requested. /// /// This method will return a cancel request and a new key request if the /// key was already requested, otherwise it will return just the key /// request. /// /// # Arguments /// /// * `room_id` - The id of the room where the key is used in. /// /// * `sender_key` - The curve25519 key of the sender that owns the key. /// /// * `session_id` - The id that uniquely identifies the session. pub async fn request_key( &self, room_id: &RoomId, sender_key: &str, session_id: &str, ) -> Result<(Option, OutgoingRequest), CryptoStoreError> { let key_info = RequestedKeyInfo::new( EventEncryptionAlgorithm::MegolmV1AesSha2, room_id.to_owned(), sender_key.to_owned(), session_id.to_owned(), ) .into(); let request = self.store.get_secret_request_by_info(&key_info).await?; if let Some(request) = request { let cancel = request.to_cancellation(self.device_id()); let request = request.to_request(self.device_id()); Ok((Some(cancel), request)) } else { let request = self.request_key_helper(key_info).await?; Ok((None, request)) } } /// Create outgoing secret requests for the given pub fn request_missing_secrets( own_user_id: &UserId, secret_names: Vec, ) -> Vec { if !secret_names.is_empty() { info!(secret_names =? secret_names, "Creating new outgoing secret requests"); secret_names .into_iter() .map(|n| GossipRequest::from_secret_name(own_user_id.to_owned(), n)) .collect() } else { trace!("No secrets are missing from our store, not requesting them"); vec![] } } async fn request_key_helper( &self, key_info: SecretInfo, ) -> Result { let request = GossipRequest { request_recipient: self.user_id().to_owned(), request_id: Uuid::new_v4(), info: key_info, sent_out: false, }; let outgoing_request = request.to_request(self.device_id()); self.save_outgoing_key_info(request).await?; Ok(outgoing_request) } /// Create a new outgoing key request for the key with the given session id. /// /// This will queue up a new to-device request and store the key info so /// once we receive a forwarded room key we can check that it matches the /// key we requested. /// /// This does nothing if a request for this key has already been sent out. /// /// # Arguments /// * `room_id` - The id of the room where the key is used in. /// /// * `sender_key` - The curve25519 key of the sender that owns the key. /// /// * `session_id` - The id that uniquely identifies the session. pub async fn create_outgoing_key_request( &self, room_id: &RoomId, sender_key: &str, session_id: &str, ) -> Result<(), CryptoStoreError> { let key_info = RequestedKeyInfo::new( EventEncryptionAlgorithm::MegolmV1AesSha2, room_id.to_owned(), sender_key.to_owned(), session_id.to_owned(), ) .into(); if self.should_request_key(&key_info).await? { self.request_key_helper(key_info).await?; } Ok(()) } /// Save an outgoing key info. async fn save_outgoing_key_info(&self, info: GossipRequest) -> Result<(), CryptoStoreError> { let mut changes = Changes::default(); changes.key_requests.push(info); self.store.save_changes(changes).await?; Ok(()) } /// Get an outgoing key info that matches the forwarded room key content. async fn get_key_info( &self, content: &ForwardedRoomKeyToDeviceEventContent, ) -> Result, CryptoStoreError> { let info = RequestedKeyInfo::new( content.algorithm.clone(), content.room_id.clone(), content.sender_key.clone(), content.session_id.clone(), ) .into(); self.store.get_secret_request_by_info(&info).await } /// Delete the given outgoing key info. async fn delete_key_info(&self, info: &GossipRequest) -> Result<(), CryptoStoreError> { self.store.delete_outgoing_secret_requests(info.request_id).await } /// Mark the outgoing request as sent. pub async fn mark_outgoing_request_as_sent(&self, id: Uuid) -> Result<(), CryptoStoreError> { let info = self.store.get_outgoing_secret_requests(id).await?; if let Some(mut info) = info { trace!( recipient = info.request_recipient.as_str(), request_type = info.request_type(), request_id = info.request_id.to_string().as_str(), "Marking outgoing key request as sent" ); info.sent_out = true; self.save_outgoing_key_info(info).await?; } self.outgoing_requests.remove(&id); Ok(()) } /// Mark the given outgoing key info as done. /// /// This will queue up a request cancellation. async fn mark_as_done(&self, key_info: GossipRequest) -> Result<(), CryptoStoreError> { trace!( recipient = key_info.request_recipient.as_str(), request_type = key_info.request_type(), request_id = key_info.request_id.to_string().as_str(), "Successfully received a secret, removing the request" ); self.outgoing_requests.remove(&key_info.request_id); // TODO return the key info instead of deleting it so the sync handler // can delete it in one transaction. self.delete_key_info(&key_info).await?; let request = key_info.to_cancellation(self.device_id()); self.outgoing_requests.insert(request.request_id, request); Ok(()) } pub async fn receive_secret( &self, sender_key: &str, event: &mut ToDeviceEvent, ) -> Result, CryptoStoreError> { debug!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), "Received a m.secret.send event" ); let request_id = if let Ok(r) = Uuid::parse_str(&event.content.request_id) { r } else { warn!("Received a m.secret.send event but the request ID is invalid"); return Ok(None); }; if let Some(request) = self.store.get_outgoing_secret_requests(request_id).await? { match &request.info { SecretInfo::KeyRequest(_) => { warn!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), "Received a m.secret.send event but the request was for a room key" ); } SecretInfo::SecretRequest(secret_name) => { debug!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), secret_name = secret_name.as_ref(), "Received a m.secret.send event with a matching request" ); if let Some(device) = self.store.get_device_from_curve_key(&event.sender, sender_key).await? { if device.verified() { match self .store .import_secret( secret_name, std::mem::take(&mut event.content.secret), ) .await { Ok(_) => self.mark_as_done(request).await?, Err(e) => { // If this is a store error propagate it up // the call stack. if let SecretImportError::Store(e) = e { return Err(e); } else { // Otherwise warn that there was // something wrong with the secret. warn!( secret_name = secret_name.as_ref(), error =? e, "Error while importing a secret" ) } } } } else { warn!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), secret_name = secret_name.as_ref(), "Received a m.secret.send event from an unverified device" ); } } else { warn!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), secret_name = secret_name.as_ref(), "Received a m.secret.send event from an unknown device" ); self.store.update_tracked_user(&event.sender, true).await?; } } } } Ok(Some(AnyToDeviceEvent::SecretSend(event.clone()))) } /// Receive a forwarded room key event. pub async fn receive_forwarded_room_key( &self, sender_key: &str, event: &mut ToDeviceEvent, ) -> Result<(Option, Option), CryptoStoreError> { let key_info = self.get_key_info(&event.content).await?; if let Some(info) = key_info { let session = InboundGroupSession::from_forwarded_key(sender_key, &mut event.content)?; let old_session = self .store .get_inbound_group_session( session.room_id(), &session.sender_key, session.session_id(), ) .await?; // If we have a previous session, check if we have a better version // and store the new one if so. let session = if let Some(old_session) = old_session { let first_old_index = old_session.first_known_index(); let first_index = session.first_known_index(); if first_old_index > first_index { self.mark_as_done(info).await?; Some(session) } else { None } // If we didn't have a previous session, store it. } else { self.mark_as_done(info).await?; Some(session) }; if let Some(s) = &session { info!( sender = event.sender.as_str(), room_id = s.room_id().as_str(), session_id = s.session_id(), "Received a forwarded room key", ); } Ok((Some(AnyToDeviceEvent::ForwardedRoomKey(event.clone())), session)) } else { info!( sender = event.sender.as_str(), "Received a forwarded room key but no key info was found.", ); Ok((None, None)) } } } #[cfg(test)] mod test { use std::{convert::TryInto, sync::Arc}; use dashmap::DashMap; use matches::assert_matches; use matrix_sdk_common::locks::Mutex; use matrix_sdk_test::async_test; use ruma::{ events::{ forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, room::encrypted::EncryptedToDeviceEventContent, room_key_request::RoomKeyRequestToDeviceEventContent, secret::request::{RequestAction, RequestToDeviceEventContent, SecretName}, AnyToDeviceEvent, ToDeviceEvent, }, room_id, to_device::DeviceIdOrAllDevices, user_id, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId, }; use super::{GossipMachine, KeyForwardDecision}; use crate::{ identities::{LocalTrust, ReadOnlyDevice}, olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount}, session_manager::GroupSessionCache, store::{Changes, CryptoStore, MemoryStore, Store}, verification::VerificationMachine, OutgoingRequests, }; fn alice_id() -> UserId { user_id!("@alice:example.org") } fn alice_device_id() -> DeviceIdBox { "JLAFKJWSCS".into() } fn bob_id() -> UserId { user_id!("@bob:example.org") } fn bob_device_id() -> DeviceIdBox { "ILMLKASTES".into() } fn alice2_device_id() -> DeviceIdBox { "ILMLKASTES".into() } fn room_id() -> RoomId { room_id!("!test:example.org") } fn account() -> ReadOnlyAccount { ReadOnlyAccount::new(&alice_id(), &alice_device_id()) } fn bob_account() -> ReadOnlyAccount { ReadOnlyAccount::new(&bob_id(), &bob_device_id()) } fn alice_2_account() -> ReadOnlyAccount { ReadOnlyAccount::new(&alice_id(), &alice2_device_id()) } fn bob_machine() -> GossipMachine { let user_id = Arc::new(bob_id()); let account = ReadOnlyAccount::new(&user_id, &alice_device_id()); let store: Arc = Arc::new(MemoryStore::new()); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(bob_id()))); let verification = VerificationMachine::new(account, identity.clone(), store.clone()); let store = Store::new(user_id.clone(), identity, store, verification); let session_cache = GroupSessionCache::new(store.clone()); GossipMachine::new( user_id, bob_device_id().into(), store, session_cache, Arc::new(DashMap::new()), ) } async fn get_machine() -> GossipMachine { let user_id: Arc = alice_id().into(); let account = ReadOnlyAccount::new(&user_id, &alice_device_id()); let device = ReadOnlyDevice::from_account(&account).await; let another_device = ReadOnlyDevice::from_account(&ReadOnlyAccount::new(&user_id, &alice2_device_id())) .await; let store: Arc = Arc::new(MemoryStore::new()); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id()))); let verification = VerificationMachine::new(account, identity.clone(), store.clone()); let store = Store::new(user_id.clone(), identity, store, verification); store.save_devices(&[device, another_device]).await.unwrap(); let session_cache = GroupSessionCache::new(store.clone()); GossipMachine::new( user_id, alice_device_id().into(), store, session_cache, Arc::new(DashMap::new()), ) } #[async_test] async fn create_machine() { let machine = get_machine().await; assert!(machine.outgoing_to_device_requests().await.unwrap().is_empty()); } #[async_test] async fn re_request_keys() { let machine = get_machine().await; let account = account(); let (_, session) = account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); assert!(machine.outgoing_to_device_requests().await.unwrap().is_empty()); let (cancel, request) = machine .request_key(session.room_id(), &session.sender_key, session.session_id()) .await .unwrap(); assert!(cancel.is_none()); machine.mark_outgoing_request_as_sent(request.request_id).await.unwrap(); let (cancel, _) = machine .request_key(session.room_id(), &session.sender_key, session.session_id()) .await .unwrap(); assert!(cancel.is_some()); } #[async_test] async fn create_key_request() { let machine = get_machine().await; let account = account(); let second_account = alice_2_account(); let alice_device = ReadOnlyDevice::from_account(&second_account).await; // We need a trusted device, otherwise we won't request keys alice_device.set_trust_state(LocalTrust::Verified); machine.store.save_devices(&[alice_device]).await.unwrap(); let (_, session) = account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); assert!(machine.outgoing_to_device_requests().await.unwrap().is_empty()); machine .create_outgoing_key_request( session.room_id(), &session.sender_key, session.session_id(), ) .await .unwrap(); assert!(!machine.outgoing_to_device_requests().await.unwrap().is_empty()); assert_eq!(machine.outgoing_to_device_requests().await.unwrap().len(), 1); machine .create_outgoing_key_request( session.room_id(), &session.sender_key, session.session_id(), ) .await .unwrap(); let requests = machine.outgoing_to_device_requests().await.unwrap(); assert_eq!(requests.len(), 1); let request = requests.get(0).unwrap(); machine.mark_outgoing_request_as_sent(request.request_id).await.unwrap(); assert!(machine.outgoing_to_device_requests().await.unwrap().is_empty()); } #[async_test] async fn receive_forwarded_key() { let machine = get_machine().await; let account = account(); let second_account = alice_2_account(); let alice_device = ReadOnlyDevice::from_account(&second_account).await; // We need a trusted device, otherwise we won't request keys alice_device.set_trust_state(LocalTrust::Verified); machine.store.save_devices(&[alice_device]).await.unwrap(); let (_, session) = account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); machine .create_outgoing_key_request( session.room_id(), &session.sender_key, session.session_id(), ) .await .unwrap(); let requests = machine.outgoing_to_device_requests().await.unwrap(); let request = requests.get(0).unwrap(); let id = request.request_id; machine.mark_outgoing_request_as_sent(id).await.unwrap(); let export = session.export_at_index(10).await; let content: ForwardedRoomKeyToDeviceEventContent = export.try_into().unwrap(); let mut event = ToDeviceEvent { sender: alice_id(), content }; assert!( machine .store .get_inbound_group_session( session.room_id(), &session.sender_key, session.session_id(), ) .await .unwrap() .is_none() ); let (_, first_session) = machine.receive_forwarded_room_key(&session.sender_key, &mut event).await.unwrap(); let first_session = first_session.unwrap(); assert_eq!(first_session.first_known_index(), 10); machine.store.save_inbound_group_sessions(&[first_session.clone()]).await.unwrap(); // Get the cancel request. let request = machine.outgoing_requests.iter().next().unwrap(); let id = request.request_id; drop(request); machine.mark_outgoing_request_as_sent(id).await.unwrap(); machine .create_outgoing_key_request( session.room_id(), &session.sender_key, session.session_id(), ) .await .unwrap(); let requests = machine.outgoing_to_device_requests().await.unwrap(); let request = &requests[0]; machine.mark_outgoing_request_as_sent(request.request_id).await.unwrap(); let export = session.export_at_index(15).await; let content: ForwardedRoomKeyToDeviceEventContent = export.try_into().unwrap(); let mut event = ToDeviceEvent { sender: alice_id(), content }; let (_, second_session) = machine.receive_forwarded_room_key(&session.sender_key, &mut event).await.unwrap(); assert!(second_session.is_none()); let export = session.export_at_index(0).await; let content: ForwardedRoomKeyToDeviceEventContent = export.try_into().unwrap(); let mut event = ToDeviceEvent { sender: alice_id(), content }; let (_, second_session) = machine.receive_forwarded_room_key(&session.sender_key, &mut event).await.unwrap(); assert_eq!(second_session.unwrap().first_known_index(), 0); } #[async_test] async fn should_share_key_test() { let machine = get_machine().await; let account = account(); let own_device = machine.store.get_device(&alice_id(), &alice2_device_id()).await.unwrap().unwrap(); let (outbound, inbound) = account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); // We don't share keys with untrusted devices. assert_matches!( machine.should_share_key(&own_device, &inbound).await, Err(KeyForwardDecision::UntrustedDevice) ); own_device.set_trust_state(LocalTrust::Verified); // Now we do want to share the keys. assert!(machine.should_share_key(&own_device, &inbound).await.is_ok()); let bob_device = ReadOnlyDevice::from_account(&bob_account()).await; machine.store.save_devices(&[bob_device]).await.unwrap(); let bob_device = machine.store.get_device(&bob_id(), &bob_device_id()).await.unwrap().unwrap(); // We don't share sessions with other user's devices if no outbound // session was provided. assert_matches!( machine.should_share_key(&bob_device, &inbound).await, Err(KeyForwardDecision::MissingOutboundSession) ); let mut changes = Changes::default(); changes.outbound_group_sessions.push(outbound.clone()); changes.inbound_group_sessions.push(inbound.clone()); machine.store.save_changes(changes).await.unwrap(); machine.outbound_group_sessions.insert(outbound.clone()); // We don't share sessions with other user's devices if the session // wasn't shared in the first place. assert_matches!( machine.should_share_key(&bob_device, &inbound).await, Err(KeyForwardDecision::OutboundSessionNotShared) ); bob_device.set_trust_state(LocalTrust::Verified); // We don't share sessions with other user's devices if the session // wasn't shared in the first place even if the device is trusted. assert_matches!( machine.should_share_key(&bob_device, &inbound).await, Err(KeyForwardDecision::OutboundSessionNotShared) ); // We now share the session, since it was shared before. outbound.mark_shared_with( bob_device.user_id(), bob_device.device_id(), bob_device.get_key(DeviceKeyAlgorithm::Curve25519).unwrap(), ); assert!(machine.should_share_key(&bob_device, &inbound).await.is_ok()); // But we don't share some other session that doesn't match our outbound // session let (_, other_inbound) = account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); assert_matches!( machine.should_share_key(&bob_device, &other_inbound).await, Err(KeyForwardDecision::MissingOutboundSession) ); // And we don't share the session with a device that rotated its // curve25519 key. let bob_device = ReadOnlyDevice::from_account(&bob_account()).await; machine.store.save_devices(&[bob_device]).await.unwrap(); let bob_device = machine.store.get_device(&bob_id(), &bob_device_id()).await.unwrap().unwrap(); assert_matches!( machine.should_share_key(&bob_device, &inbound).await, Err(KeyForwardDecision::ChangedSenderKey) ); } #[async_test] async fn key_share_cycle() { let alice_machine = get_machine().await; let alice_account = Account { inner: account(), store: alice_machine.store.clone() }; let bob_machine = bob_machine(); let bob_account = bob_account(); let second_account = alice_2_account(); let alice_device = ReadOnlyDevice::from_account(&second_account).await; // We need a trusted device, otherwise we won't request keys alice_device.set_trust_state(LocalTrust::Verified); alice_machine.store.save_devices(&[alice_device]).await.unwrap(); // Create Olm sessions for our two accounts. let (alice_session, bob_session) = alice_account.create_session_for(&bob_account).await; let alice_device = ReadOnlyDevice::from_account(&alice_account).await; let bob_device = ReadOnlyDevice::from_account(&bob_account).await; // Populate our stores with Olm sessions and a Megolm session. alice_machine.store.save_sessions(&[alice_session]).await.unwrap(); alice_machine.store.save_devices(&[bob_device]).await.unwrap(); bob_machine.store.save_sessions(&[bob_session]).await.unwrap(); bob_machine.store.save_devices(&[alice_device.clone()]).await.unwrap(); let (group_session, inbound_group_session) = bob_account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); bob_machine.store.save_inbound_group_sessions(&[inbound_group_session]).await.unwrap(); // Alice wants to request the outbound group session from bob. alice_machine .create_outgoing_key_request( &room_id(), bob_account.identity_keys.curve25519(), group_session.session_id(), ) .await .unwrap(); group_session.mark_shared_with( alice_device.user_id(), alice_device.device_id(), alice_device.get_key(DeviceKeyAlgorithm::Curve25519).unwrap(), ); // Put the outbound session into bobs store. bob_machine.outbound_group_sessions.insert(group_session.clone()); // Get the request and convert it into a event. let requests = alice_machine.outgoing_to_device_requests().await.unwrap(); let request = &requests[0]; let id = request.request_id; let content = request .request .to_device() .unwrap() .messages .get(&alice_id()) .unwrap() .get(&DeviceIdOrAllDevices::AllDevices) .unwrap(); let content: RoomKeyRequestToDeviceEventContent = content.deserialize_as().unwrap(); alice_machine.mark_outgoing_request_as_sent(id).await.unwrap(); let event = ToDeviceEvent { sender: alice_id(), content }; // Bob doesn't have any outgoing requests. assert!(bob_machine.outgoing_requests.is_empty()); // Receive the room key request from alice. bob_machine.receive_incoming_key_request(&event); bob_machine.collect_incoming_key_requests().await.unwrap(); // Now bob does have an outgoing request. assert!(!bob_machine.outgoing_requests.is_empty()); // Get the request and convert it to a encrypted to-device event. let requests = bob_machine.outgoing_to_device_requests().await.unwrap(); let request = &requests[0]; let id = request.request_id; let content = request .request .to_device() .unwrap() .messages .get(&alice_id()) .unwrap() .get(&DeviceIdOrAllDevices::DeviceId(alice_device_id())) .unwrap(); let content: EncryptedToDeviceEventContent = content.deserialize_as().unwrap(); bob_machine.mark_outgoing_request_as_sent(id).await.unwrap(); let event = ToDeviceEvent { sender: bob_id(), content }; // Check that alice doesn't have the session. assert!(alice_machine .store .get_inbound_group_session( &room_id(), bob_account.identity_keys().curve25519(), group_session.session_id() ) .await .unwrap() .is_none()); let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); if let AnyToDeviceEvent::ForwardedRoomKey(mut e) = decrypted.event.deserialize().unwrap() { let (_, session) = alice_machine .receive_forwarded_room_key(&decrypted.sender_key, &mut e) .await .unwrap(); alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); } else { panic!("Invalid decrypted event type"); } // Check that alice now does have the session. let session = alice_machine .store .get_inbound_group_session( &room_id(), &decrypted.sender_key, group_session.session_id(), ) .await .unwrap() .unwrap(); assert_eq!(session.session_id(), group_session.session_id()) } #[async_test] async fn secret_share_cycle() { let alice_machine = get_machine().await; let alice_account = Account { inner: account(), store: alice_machine.store.clone() }; let second_account = alice_2_account(); let alice_device = ReadOnlyDevice::from_account(&second_account).await; let bob_account = bob_account(); let bob_device = ReadOnlyDevice::from_account(&bob_account).await; alice_machine.store.save_devices(&[alice_device.clone()]).await.unwrap(); // Create Olm sessions for our two accounts. let (alice_session, _) = alice_account.create_session_for(&second_account).await; alice_machine.store.save_sessions(&[alice_session]).await.unwrap(); let event = ToDeviceEvent { sender: bob_account.user_id().to_owned(), content: RequestToDeviceEventContent::new( RequestAction::Request(SecretName::CrossSigningMasterKey), bob_account.device_id().to_owned(), "request_id".to_owned(), ), }; // No secret found assert!(alice_machine.outgoing_requests.is_empty()); alice_machine.receive_incoming_secret_request(&event); alice_machine.collect_incoming_key_requests().await.unwrap(); assert!(alice_machine.outgoing_requests.is_empty()); // No device found alice_machine.store.reset_cross_signing_identity().await; alice_machine.receive_incoming_secret_request(&event); alice_machine.collect_incoming_key_requests().await.unwrap(); assert!(alice_machine.outgoing_requests.is_empty()); alice_machine.store.save_devices(&[bob_device]).await.unwrap(); // The device doesn't belong to us alice_machine.store.reset_cross_signing_identity().await; alice_machine.receive_incoming_secret_request(&event); alice_machine.collect_incoming_key_requests().await.unwrap(); assert!(alice_machine.outgoing_requests.is_empty()); let event = ToDeviceEvent { sender: alice_id(), content: RequestToDeviceEventContent::new( RequestAction::Request(SecretName::CrossSigningMasterKey), second_account.device_id().into(), "request_id".to_owned(), ), }; // The device isn't trusted alice_machine.receive_incoming_secret_request(&event); alice_machine.collect_incoming_key_requests().await.unwrap(); assert!(alice_machine.outgoing_requests.is_empty()); // We need a trusted device, otherwise we won't serve secrets alice_device.set_trust_state(LocalTrust::Verified); alice_machine.store.save_devices(&[alice_device.clone()]).await.unwrap(); alice_machine.receive_incoming_secret_request(&event); alice_machine.collect_incoming_key_requests().await.unwrap(); assert!(!alice_machine.outgoing_requests.is_empty()); } #[async_test] async fn key_share_cycle_without_session() { let alice_machine = get_machine().await; let alice_account = Account { inner: account(), store: alice_machine.store.clone() }; let bob_machine = bob_machine(); let bob_account = bob_account(); let second_account = alice_2_account(); let alice_device = ReadOnlyDevice::from_account(&second_account).await; // We need a trusted device, otherwise we won't request keys alice_device.set_trust_state(LocalTrust::Verified); alice_machine.store.save_devices(&[alice_device]).await.unwrap(); // Create Olm sessions for our two accounts. let (alice_session, bob_session) = alice_account.create_session_for(&bob_account).await; let alice_device = ReadOnlyDevice::from_account(&alice_account).await; let bob_device = ReadOnlyDevice::from_account(&bob_account).await; // Populate our stores with Olm sessions and a Megolm session. alice_machine.store.save_devices(&[bob_device]).await.unwrap(); bob_machine.store.save_devices(&[alice_device.clone()]).await.unwrap(); let (group_session, inbound_group_session) = bob_account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); bob_machine.store.save_inbound_group_sessions(&[inbound_group_session]).await.unwrap(); // Alice wants to request the outbound group session from bob. alice_machine .create_outgoing_key_request( &room_id(), bob_account.identity_keys.curve25519(), group_session.session_id(), ) .await .unwrap(); group_session.mark_shared_with( alice_device.user_id(), alice_device.device_id(), alice_device.get_key(DeviceKeyAlgorithm::Curve25519).unwrap(), ); // Put the outbound session into bobs store. bob_machine.outbound_group_sessions.insert(group_session.clone()); // Get the request and convert it into a event. let requests = alice_machine.outgoing_to_device_requests().await.unwrap(); let request = &requests[0]; let id = request.request_id; let content = request .request .to_device() .unwrap() .messages .get(&alice_id()) .unwrap() .get(&DeviceIdOrAllDevices::AllDevices) .unwrap(); let content: RoomKeyRequestToDeviceEventContent = content.deserialize_as().unwrap(); alice_machine.mark_outgoing_request_as_sent(id).await.unwrap(); let event = ToDeviceEvent { sender: alice_id(), content }; // Bob doesn't have any outgoing requests. assert!(bob_machine.outgoing_to_device_requests().await.unwrap().is_empty()); assert!(bob_machine.users_for_key_claim.is_empty()); assert!(bob_machine.wait_queue.is_empty()); // Receive the room key request from alice. bob_machine.receive_incoming_key_request(&event); bob_machine.collect_incoming_key_requests().await.unwrap(); // Bob only has a keys claim request, since we're lacking a session assert_eq!(bob_machine.outgoing_to_device_requests().await.unwrap().len(), 1); assert_matches!( bob_machine.outgoing_to_device_requests().await.unwrap().first().unwrap().request(), OutgoingRequests::KeysClaim(_) ); assert!(!bob_machine.users_for_key_claim.is_empty()); assert!(!bob_machine.wait_queue.is_empty()); // We create a session now. alice_machine.store.save_sessions(&[alice_session]).await.unwrap(); bob_machine.store.save_sessions(&[bob_session]).await.unwrap(); bob_machine.retry_keyshare(&alice_id(), &alice_device_id()); assert!(bob_machine.users_for_key_claim.is_empty()); bob_machine.collect_incoming_key_requests().await.unwrap(); // Bob now has an outgoing requests. assert!(!bob_machine.outgoing_to_device_requests().await.unwrap().is_empty()); assert!(bob_machine.wait_queue.is_empty()); // Get the request and convert it to a encrypted to-device event. let requests = bob_machine.outgoing_to_device_requests().await.unwrap(); let request = &requests[0]; let id = request.request_id; let content = request .request .to_device() .unwrap() .messages .get(&alice_id()) .unwrap() .get(&DeviceIdOrAllDevices::DeviceId(alice_device_id())) .unwrap(); let content: EncryptedToDeviceEventContent = content.deserialize_as().unwrap(); bob_machine.mark_outgoing_request_as_sent(id).await.unwrap(); let event = ToDeviceEvent { sender: bob_id(), content }; // Check that alice doesn't have the session. assert!(alice_machine .store .get_inbound_group_session( &room_id(), bob_account.identity_keys().curve25519(), group_session.session_id() ) .await .unwrap() .is_none()); let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); if let AnyToDeviceEvent::ForwardedRoomKey(mut e) = decrypted.event.deserialize().unwrap() { let (_, session) = alice_machine .receive_forwarded_room_key(&decrypted.sender_key, &mut e) .await .unwrap(); alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); } else { panic!("Invalid decrypted event type"); } // Check that alice now does have the session. let session = alice_machine .store .get_inbound_group_session( &room_id(), &decrypted.sender_key, group_session.session_id(), ) .await .unwrap() .unwrap(); assert_eq!(session.session_id(), group_session.session_id()) } }