crypto: Initial support for group session invalidation.

master
Damir Jelić 2020-10-08 11:16:02 +02:00
parent 4019ebf121
commit 23ac00c8ec
5 changed files with 120 additions and 14 deletions

View File

@ -12,13 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{collections::BTreeMap, sync::Arc}; use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
};
use dashmap::DashMap; use dashmap::DashMap;
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::to_device::DeviceIdOrAllDevices, api::r0::to_device::DeviceIdOrAllDevices,
events::{room::encrypted::EncryptedEventContent, AnyMessageEventContent, EventType}, events::{room::encrypted::EncryptedEventContent, AnyMessageEventContent, EventType},
identifiers::{RoomId, UserId}, identifiers::{DeviceId, RoomId, UserId},
uuid::Uuid, uuid::Uuid,
}; };
use tracing::debug; use tracing::debug;
@ -30,7 +33,7 @@ use crate::{
Device, EncryptionSettings, OlmError, ToDeviceRequest, Device, EncryptionSettings, OlmError, ToDeviceRequest,
}; };
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct GroupSessionManager { pub struct GroupSessionManager {
account: Account, account: Account,
/// Store for the encryption keys. /// Store for the encryption keys.
@ -64,6 +67,29 @@ impl GroupSessionManager {
} }
} }
pub fn invalidate_sessions_new_devices(&self, users: &HashSet<&UserId>) {
for session in self.outbound_group_sessions.iter() {
if users.iter().any(|u| session.contains_recipient(u)) {
session.invalidate_session()
}
}
}
/// Invalidate the sessions that were sent to the given user/device pair.
pub fn invalidate_sessions(&self, user_id: &UserId, device_id: &DeviceId) {
for session in self.outbound_group_sessions.iter() {
if session.is_shared_with(user_id, device_id) {
session.invalidate_session();
if !session.shared() {
for request_id in session.clear_requests() {
self.outbound_sessions_being_shared.remove(&request_id);
}
}
}
}
}
/// Get an outbound group session for a room, if one exists. /// Get an outbound group session for a room, if one exists.
/// ///
/// # Arguments /// # Arguments
@ -105,7 +131,7 @@ impl GroupSessionManager {
let session = self.outbound_group_sessions.get(room_id); let session = self.outbound_group_sessions.get(room_id);
match session { match session {
Some(s) => !s.shared() || s.expired(), Some(s) => !s.shared() || s.expired() || s.invalidated(),
None => true, None => true,
} }
} }
@ -158,6 +184,7 @@ impl GroupSessionManager {
let mut devices: Vec<Device> = Vec::new(); let mut devices: Vec<Device> = Vec::new();
for user_id in users { for user_id in users {
session.add_recipient(user_id);
let user_devices = self.store.get_user_devices(&user_id).await?; let user_devices = self.store.get_user_devices(&user_id).await?;
devices.extend(user_devices.devices().filter(|d| !d.is_blacklisted())); devices.extend(user_devices.devices().filter(|d| !d.is_blacklisted()));
} }

View File

@ -27,6 +27,7 @@ use matrix_sdk_common::{
use crate::{ use crate::{
error::OlmResult, error::OlmResult,
group_manager::GroupSessionManager,
identities::{ identities::{
MasterPubkey, OwnUserIdentity, ReadOnlyDevice, SelfSigningPubkey, UserIdentities, MasterPubkey, OwnUserIdentity, ReadOnlyDevice, SelfSigningPubkey, UserIdentities,
UserIdentity, UserSigningPubkey, UserIdentity, UserSigningPubkey,
@ -39,15 +40,22 @@ use crate::{
pub(crate) struct IdentityManager { pub(crate) struct IdentityManager {
user_id: Arc<UserId>, user_id: Arc<UserId>,
device_id: Arc<DeviceIdBox>, device_id: Arc<DeviceIdBox>,
group_manager: GroupSessionManager,
store: Store, store: Store,
} }
impl IdentityManager { impl IdentityManager {
pub fn new(user_id: Arc<UserId>, device_id: Arc<DeviceIdBox>, store: Store) -> Self { pub fn new(
user_id: Arc<UserId>,
device_id: Arc<DeviceIdBox>,
store: Store,
group_manager: GroupSessionManager,
) -> Self {
IdentityManager { IdentityManager {
user_id, user_id,
device_id, device_id,
store, store,
group_manager,
} }
} }
@ -104,6 +112,7 @@ impl IdentityManager {
&self, &self,
device_keys_map: &BTreeMap<UserId, BTreeMap<DeviceIdBox, DeviceKeys>>, device_keys_map: &BTreeMap<UserId, BTreeMap<DeviceIdBox, DeviceKeys>>,
) -> StoreResult<Vec<ReadOnlyDevice>> { ) -> StoreResult<Vec<ReadOnlyDevice>> {
let mut users_with_new_devices = HashSet::new();
let mut changed_devices = Vec::new(); let mut changed_devices = Vec::new();
for (user_id, device_map) in device_keys_map { for (user_id, device_map) in device_keys_map {
@ -149,6 +158,7 @@ impl IdentityManager {
} }
}; };
info!("Adding a new device to the device store {:?}", device); info!("Adding a new device to the device store {:?}", device);
users_with_new_devices.insert(user_id);
device device
}; };
@ -164,12 +174,17 @@ impl IdentityManager {
for device_id in deleted_devices { for device_id in deleted_devices {
if let Some(device) = stored_devices.get(device_id) { if let Some(device) = stored_devices.get(device_id) {
self.group_manager
.invalidate_sessions(device.user_id(), device.device_id());
device.mark_as_deleted(); device.mark_as_deleted();
self.store.delete_device(device).await?; self.store.delete_device(device).await?;
} }
} }
} }
self.group_manager
.invalidate_sessions_new_devices(&users_with_new_devices);
Ok(changed_devices) Ok(changed_devices)
} }
@ -362,9 +377,10 @@ pub(crate) mod test {
use serde_json::json; use serde_json::json;
use crate::{ use crate::{
group_manager::GroupSessionManager,
identities::IdentityManager, identities::IdentityManager,
machine::test::response_from_file, machine::test::response_from_file,
olm::ReadOnlyAccount, olm::{Account, ReadOnlyAccount},
store::{CryptoStore, MemoryStore, Store}, store::{CryptoStore, MemoryStore, Store},
verification::VerificationMachine, verification::VerificationMachine,
}; };
@ -385,13 +401,18 @@ pub(crate) mod test {
let user_id = Arc::new(user_id()); let user_id = Arc::new(user_id());
let account = ReadOnlyAccount::new(&user_id, &device_id()); let account = ReadOnlyAccount::new(&user_id, &device_id());
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new())); let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
let verification = VerificationMachine::new(account, store); let verification = VerificationMachine::new(account.clone(), store);
let store = Store::new( let store = Store::new(
user_id.clone(), user_id.clone(),
Arc::new(Box::new(MemoryStore::new())), Arc::new(Box::new(MemoryStore::new())),
verification, verification,
); );
IdentityManager::new(user_id, Arc::new(device_id()), store) let account = Account {
inner: account,
store: store.clone(),
};
let group = GroupSessionManager::new(account.clone(), store.clone());
IdentityManager::new(user_id, Arc::new(device_id()), store, group)
} }
pub(crate) fn other_key_query() -> KeyQueryResponse { pub(crate) fn other_key_query() -> KeyQueryResponse {

View File

@ -136,14 +136,19 @@ impl OlmMachine {
store.clone(), store.clone(),
outbound_group_sessions, outbound_group_sessions,
); );
let identity_manager =
IdentityManager::new(user_id.clone(), device_id.clone(), store.clone());
let account = Account { let account = Account {
inner: account, inner: account,
store: store.clone(), store: store.clone(),
}; };
let group_session_manager = GroupSessionManager::new(account.clone(), store.clone()); let group_session_manager = GroupSessionManager::new(account.clone(), store.clone());
let identity_manager = IdentityManager::new(
user_id.clone(),
device_id.clone(),
store.clone(),
group_session_manager.clone(),
);
OlmMachine { OlmMachine {
user_id, user_id,

View File

@ -58,7 +58,7 @@ use crate::{
use super::{EncryptionSettings, InboundGroupSession, OutboundGroupSession, Session}; use super::{EncryptionSettings, InboundGroupSession, OutboundGroupSession, Session};
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct Account { pub struct Account {
pub(crate) inner: ReadOnlyAccount, pub(crate) inner: ReadOnlyAccount,
pub(crate) store: Store, pub(crate) store: Store,

View File

@ -104,6 +104,7 @@ pub struct OutboundGroupSession {
pub(crate) creation_time: Arc<Instant>, pub(crate) creation_time: Arc<Instant>,
message_count: Arc<AtomicU64>, message_count: Arc<AtomicU64>,
shared: Arc<AtomicBool>, shared: Arc<AtomicBool>,
invalidated: Arc<AtomicBool>,
settings: Arc<EncryptionSettings>, settings: Arc<EncryptionSettings>,
shared_with_set: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>, shared_with_set: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>,
to_share_with_set: Arc<DashMap<Uuid, Arc<ToDeviceRequest>>>, to_share_with_set: Arc<DashMap<Uuid, Arc<ToDeviceRequest>>>,
@ -143,6 +144,7 @@ impl OutboundGroupSession {
creation_time: Arc::new(Instant::now()), creation_time: Arc::new(Instant::now()),
message_count: Arc::new(AtomicU64::new(0)), message_count: Arc::new(AtomicU64::new(0)),
shared: Arc::new(AtomicBool::new(false)), shared: Arc::new(AtomicBool::new(false)),
invalidated: Arc::new(AtomicBool::new(false)),
settings: Arc::new(settings), settings: Arc::new(settings),
shared_with_set: Arc::new(DashMap::new()), shared_with_set: Arc::new(DashMap::new()),
to_share_with_set: Arc::new(DashMap::new()), to_share_with_set: Arc::new(DashMap::new()),
@ -153,6 +155,16 @@ impl OutboundGroupSession {
self.to_share_with_set.insert(request_id, request); self.to_share_with_set.insert(request_id, request);
} }
pub fn add_recipient(&self, user_id: &UserId) {
self.shared_with_set
.entry(user_id.to_owned())
.or_insert_with(DashSet::new);
}
pub fn contains_recipient(&self, user_id: &UserId) -> bool {
self.shared_with_set.contains_key(user_id)
}
/// Mark the request with the given request id as sent. /// Mark the request with the given request id as sent.
/// ///
/// This removes the request from the queue and marks the set of /// This removes the request from the queue and marks the set of
@ -263,6 +275,11 @@ impl OutboundGroupSession {
>= min(self.settings.rotation_period, Duration::from_secs(3600)) >= min(self.settings.rotation_period, Duration::from_secs(3600))
} }
/// Has the session been invalidated.
pub fn invalidated(&self) -> bool {
self.invalidated.load(Ordering::Relaxed)
}
/// Mark the session as shared. /// Mark the session as shared.
/// ///
/// Messages shouldn't be encrypted with the session before it has been /// Messages shouldn't be encrypted with the session before it has been
@ -315,12 +332,48 @@ impl OutboundGroupSession {
}) })
} }
/// The set of users this session is shared with. /// Mark the session as invalid.
///
/// This should be called if an user/device deletes a device that received
/// this session.
pub fn invalidate_session(&self) {
self.invalidated.store(true, Ordering::Relaxed)
}
/// Clear out the requests returning the request ids.
pub fn clear_requests(&self) -> Vec<Uuid> {
let request_ids = self
.to_share_with_set
.iter()
.map(|item| *item.key())
.collect();
self.to_share_with_set.clear();
request_ids
}
/// Has or will the session be shared with the given user/device pair.
pub(crate) fn is_shared_with(&self, user_id: &UserId, device_id: &DeviceId) -> bool { pub(crate) fn is_shared_with(&self, user_id: &UserId, device_id: &DeviceId) -> bool {
self.shared_with_set let shared_with = self
.shared_with_set
.get(user_id) .get(user_id)
.map(|d| d.contains(device_id)) .map(|d| d.contains(device_id))
.unwrap_or(false) .unwrap_or(false);
let should_be_shared_with = if self.shared() {
false
} else {
let device_id = DeviceIdOrAllDevices::DeviceId(device_id.into());
self.to_share_with_set.iter().any(|item| {
if let Some(e) = item.value().messages.get(user_id) {
e.contains_key(&device_id)
} else {
false
}
})
};
shared_with || should_be_shared_with
} }
/// Mark that the session was shared with the given user/device pair. /// Mark that the session was shared with the given user/device pair.