From e4a9cf0bbaafa0b96dbf1d1a027c1ed5a058ea3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 11 Aug 2021 14:12:31 +0200 Subject: [PATCH] crypto: Disallow re-sharing room keys with devices with changed curve keys --- matrix_sdk_crypto/src/gossiping/machine.rs | 125 ++++++++++-------- matrix_sdk_crypto/src/gossiping/mod.rs | 6 +- .../src/olm/group_sessions/mod.rs | 2 +- .../src/olm/group_sessions/outbound.rs | 107 ++++++++------- matrix_sdk_crypto/src/olm/mod.rs | 2 +- .../src/session_manager/group_sessions.rs | 58 +++++--- matrix_sdk_crypto/src/store/sled.rs | 35 ++++- 7 files changed, 207 insertions(+), 128 deletions(-) diff --git a/matrix_sdk_crypto/src/gossiping/machine.rs b/matrix_sdk_crypto/src/gossiping/machine.rs index 82415e17a..aa64a7129 100644 --- a/matrix_sdk_crypto/src/gossiping/machine.rs +++ b/matrix_sdk_crypto/src/gossiping/machine.rs @@ -41,7 +41,7 @@ use ruma::{ }; use tracing::{debug, info, trace, warn}; -use super::{GossipRequest, KeyforwardDecision, RequestEvent, RequestInfo, SecretInfo, WaitQueue}; +use super::{GossipRequest, KeyForwardDecision, RequestEvent, RequestInfo, SecretInfo, WaitQueue}; use crate::{ error::{OlmError, OlmResult}, olm::{InboundGroupSession, Session, ShareState}, @@ -370,12 +370,21 @@ impl GossipMachine { if let Some(device) = device { match self.should_share_key(&device, &session).await { Err(e) => { - 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", - ); + 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) } @@ -482,7 +491,7 @@ impl GossipMachine { &self, device: &Device, session: &InboundGroupSession, - ) -> Result, KeyforwardDecision> { + ) -> Result, KeyForwardDecision> { let outbound_session = self .outbound_group_sessions .get_with_id(session.room_id(), session.session_id()) @@ -494,7 +503,7 @@ impl GossipMachine { if device.verified() { Ok(None) } else { - Err(KeyforwardDecision::UntrustedDevice) + Err(KeyForwardDecision::UntrustedDevice) } }; @@ -502,14 +511,11 @@ impl GossipMachine { // 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 { - if let ShareState::Shared(message_index) = - outbound.is_shared_with(device.user_id(), device.device_id()) - { - Ok(Some(message_index)) - } else if device.user_id() == self.user_id() { - own_device_check() - } else { - Err(KeyforwardDecision::OutboundSessionNotShared) + 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. @@ -518,7 +524,7 @@ impl GossipMachine { // Otherwise, there's not enough info to decide if we can safely share // the session. } else { - Err(KeyforwardDecision::MissingOutboundSession) + Err(KeyForwardDecision::MissingOutboundSession) } } @@ -902,10 +908,10 @@ mod test { }, room_id, to_device::DeviceIdOrAllDevices, - user_id, DeviceIdBox, RoomId, UserId, + user_id, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId, }; - use super::{GossipMachine, KeyforwardDecision}; + use super::{GossipMachine, KeyForwardDecision}; use crate::{ identities::{LocalTrust, ReadOnlyDevice}, olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount}, @@ -1182,12 +1188,9 @@ mod test { account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); // We don't share keys with untrusted devices. - assert_eq!( - machine - .should_share_key(&own_device, &inbound) - .await - .expect_err("Should not share with untrusted"), - KeyforwardDecision::UntrustedDevice + 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. @@ -1201,12 +1204,9 @@ mod test { // We don't share sessions with other user's devices if no outbound // session was provided. - assert_eq!( - machine - .should_share_key(&bob_device, &inbound) - .await - .expect_err("Should not share with other."), - KeyforwardDecision::MissingOutboundSession + assert_matches!( + machine.should_share_key(&bob_device, &inbound).await, + Err(KeyForwardDecision::MissingOutboundSession) ); let mut changes = Changes::default(); @@ -1218,28 +1218,26 @@ mod test { // We don't share sessions with other user's devices if the session // wasn't shared in the first place. - assert_eq!( - machine - .should_share_key(&bob_device, &inbound) - .await - .expect_err("Should not share with other unless shared."), - KeyforwardDecision::OutboundSessionNotShared + 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_eq!( - machine - .should_share_key(&bob_device, &inbound) - .await - .expect_err("Should not share with other unless shared."), - KeyforwardDecision::OutboundSessionNotShared + 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()); + 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 @@ -1247,12 +1245,21 @@ mod test { let (_, other_inbound) = account.create_group_session_pair_with_defaults(&room_id()).await.unwrap(); - assert_eq!( - machine - .should_share_key(&bob_device, &other_inbound) - .await - .expect_err("Should not share with other unless shared."), - KeyforwardDecision::MissingOutboundSession + 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) ); } @@ -1282,7 +1289,7 @@ mod test { 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]).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(); @@ -1298,7 +1305,11 @@ mod test { ) .await .unwrap(); - group_session.mark_shared_with(&alice_id(), &alice_device_id()); + 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()); @@ -1484,7 +1495,7 @@ mod test { // 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]).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(); @@ -1500,7 +1511,11 @@ mod test { ) .await .unwrap(); - group_session.mark_shared_with(&alice_id(), &alice_device_id()); + 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()); diff --git a/matrix_sdk_crypto/src/gossiping/mod.rs b/matrix_sdk_crypto/src/gossiping/mod.rs index 9057920d4..9a94225e7 100644 --- a/matrix_sdk_crypto/src/gossiping/mod.rs +++ b/matrix_sdk_crypto/src/gossiping/mod.rs @@ -41,7 +41,7 @@ use crate::{ /// An error describing why a key share request won't be honored. #[derive(Debug, Clone, Error, PartialEq)] -pub enum KeyforwardDecision { +pub enum KeyForwardDecision { /// The key request is from a device that we don't own, we're only sharing /// sessions that we know the requesting device already was supposed to get. #[error("can't find an active outbound group session")] @@ -53,6 +53,10 @@ pub enum KeyforwardDecision { /// The key request is from a device we own, yet we don't trust it. #[error("requesting device isn't trusted")] UntrustedDevice, + /// The outbound session was shared with the device, but the device either + /// accidentally or maliciously changed their curve25519 sender key. + #[error("the device has changed their curve25519 sender key")] + ChangedSenderKey, } /// A struct describing an outgoing key request. diff --git a/matrix_sdk_crypto/src/olm/group_sessions/mod.rs b/matrix_sdk_crypto/src/olm/group_sessions/mod.rs index f1ea29d2b..afd27b042 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/mod.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/mod.rs @@ -28,7 +28,7 @@ mod outbound; pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession}; pub use outbound::{ - EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareState, + EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo, ShareState, }; /// The private session key of a group session. diff --git a/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs index b432e44e5..db0e08127 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs @@ -43,8 +43,7 @@ use ruma::{ room_key::RoomKeyToDeviceEventContent, AnyMessageEventContent, AnyToDeviceEventContent, EventContent, }, - to_device::DeviceIdOrAllDevices, - DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -54,13 +53,15 @@ use super::{ super::{deserialize_instant, serialize_instant}, GroupSessionKey, }; -use crate::ToDeviceRequest; +use crate::{Device, ToDeviceRequest}; const ROTATION_PERIOD: Duration = Duration::from_millis(604800000); const ROTATION_MESSAGES: u64 = 100; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ShareState { NotShared, + SharedButChangedSenderKey, Shared(u32), } @@ -125,8 +126,23 @@ pub struct OutboundGroupSession { shared: Arc, invalidated: Arc, settings: Arc, - pub(crate) shared_with_set: Arc>>, - to_share_with_set: Arc, u32)>>, + pub(crate) shared_with_set: Arc>>, + to_share_with_set: Arc, ShareInfoSet)>>, +} + +/// A a map of userid/device it to a `ShareInfo`. +/// +/// Holds the `ShareInfo` for all the user/device pairs that will receive the +/// room key. +pub type ShareInfoSet = BTreeMap>; + +/// Struct holding info about the share state of a outbound group session. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ShareInfo { + /// The sender key of the device that was used to encrypt the room key. + pub sender_key: String, + /// The message index that the device received. + pub message_index: u32, } impl OutboundGroupSession { @@ -174,9 +190,9 @@ impl OutboundGroupSession { &self, request_id: Uuid, request: Arc, - message_index: u32, + share_infos: ShareInfoSet, ) { - self.to_share_with_set.insert(request_id, (request, message_index)); + self.to_share_with_set.insert(request_id, (request, share_infos)); } /// This should be called if an the user wishes to rotate this session. @@ -194,28 +210,15 @@ impl OutboundGroupSession { /// This removes the request from the queue and marks the set of /// users/devices that received the session. pub fn mark_request_as_sent(&self, request_id: &Uuid) { - if let Some((_, r)) = self.to_share_with_set.remove(request_id) { + if let Some((_, (_, r))) = self.to_share_with_set.remove(request_id) { trace!( request_id = request_id.to_string().as_str(), "Marking to-device request carrying a room key as sent" ); - let user_pairs = r.0.messages.iter().map(|(u, v)| { - ( - u.clone(), - v.iter().filter_map(|d| { - if let DeviceIdOrAllDevices::DeviceId(d) = d.0 { - Some((d.clone(), r.1)) - } else { - None - } - }), - ) - }); - - user_pairs.for_each(|(u, d)| { - self.shared_with_set.entry(u).or_insert_with(DashMap::new).extend(d); - }); + for (user_id, info) in r.into_iter() { + self.shared_with_set.entry(user_id).or_insert_with(DashMap::new).extend(info) + } if self.to_share_with_set.is_empty() { debug!( @@ -368,36 +371,39 @@ impl OutboundGroupSession { } /// 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) -> ShareState { + pub(crate) fn is_shared_with(&self, device: &Device) -> ShareState { // Check if we shared the session. - let shared_state = self - .shared_with_set - .get(user_id) - .and_then(|d| d.get(device_id).map(|m| ShareState::Shared(*m.value()))); + let shared_state = self.shared_with_set.get(device.user_id()).and_then(|d| { + d.get(device.device_id()).map(|s| { + if Some(&s.sender_key) == device.get_key(DeviceKeyAlgorithm::Curve25519) { + ShareState::Shared(s.message_index) + } else { + ShareState::SharedButChangedSenderKey + } + }) + }); if let Some(state) = shared_state { state } else { // If we haven't shared the session, check if we're going to share // the session. - let device_id = DeviceIdOrAllDevices::DeviceId(device_id.into()); // Find the first request that contains the given user id and // device id. let shared = self.to_share_with_set.iter().find_map(|item| { - let request = &item.value().0; - let message_index = item.value().1; + let share_info = &item.value().1; - if request - .messages - .get(user_id) - .map(|e| e.contains_key(&device_id)) - .unwrap_or(false) - { - Some(ShareState::Shared(message_index)) - } else { - None - } + share_info.get(device.user_id()).and_then(|d| { + d.get(device.device_id()).map(|info| { + if Some(&info.sender_key) == device.get_key(DeviceKeyAlgorithm::Curve25519) + { + ShareState::Shared(info.message_index) + } else { + ShareState::SharedButChangedSenderKey + } + }) + }) }); shared.unwrap_or(ShareState::NotShared) @@ -406,11 +412,11 @@ impl OutboundGroupSession { /// Mark that the session was shared with the given user/device pair. #[cfg(test)] - pub fn mark_shared_with(&self, user_id: &UserId, device_id: &DeviceId) { - self.shared_with_set - .entry(user_id.to_owned()) - .or_insert_with(DashMap::new) - .insert(device_id.to_owned(), 0); + pub fn mark_shared_with(&self, user_id: &UserId, device_id: &DeviceId, sender_key: &str) { + self.shared_with_set.entry(user_id.to_owned()).or_insert_with(DashMap::new).insert( + device_id.to_owned(), + ShareInfo { sender_key: sender_key.to_owned(), message_index: 0 }, + ); } /// Get the list of requests that need to be sent out for this session to be @@ -498,8 +504,7 @@ impl OutboundGroupSession { .map(|u| { ( u.key().clone(), - #[allow(clippy::map_clone)] - u.value().iter().map(|d| (d.key().clone(), *d.value())).collect(), + u.value().iter().map(|d| (d.key().clone(), d.value().clone())).collect(), ) }) .collect(), @@ -555,9 +560,9 @@ pub struct PickledOutboundGroupSession { /// Has the session been invalidated. pub invalidated: bool, /// The set of users the session has been already shared with. - pub shared_with_set: BTreeMap>, + pub shared_with_set: BTreeMap>, /// Requests that need to be sent out to share the session. - pub requests: BTreeMap, u32)>, + pub requests: BTreeMap, ShareInfoSet)>, } #[cfg(test)] diff --git a/matrix_sdk_crypto/src/olm/mod.rs b/matrix_sdk_crypto/src/olm/mod.rs index 9814cf06a..75b232a62 100644 --- a/matrix_sdk_crypto/src/olm/mod.rs +++ b/matrix_sdk_crypto/src/olm/mod.rs @@ -27,7 +27,7 @@ pub(crate) use account::{Account, OlmDecryptionInfo, SessionType}; pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount}; pub use group_sessions::{ EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle, - OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, + OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, ShareInfo, }; pub(crate) use group_sessions::{GroupSessionKey, ShareState}; use matrix_sdk_common::instant::{Duration, Instant}; diff --git a/matrix_sdk_crypto/src/session_manager/group_sessions.rs b/matrix_sdk_crypto/src/session_manager/group_sessions.rs index e9b6bd3fd..79b1af743 100644 --- a/matrix_sdk_crypto/src/session_manager/group_sessions.rs +++ b/matrix_sdk_crypto/src/session_manager/group_sessions.rs @@ -33,7 +33,7 @@ use tracing::{debug, info, trace}; use crate::{ error::{EventError, MegolmResult, OlmResult}, - olm::{Account, InboundGroupSession, OutboundGroupSession, Session, ShareState}, + olm::{Account, InboundGroupSession, OutboundGroupSession, Session, ShareInfo, ShareState}, store::{Changes, Result as StoreResult, Store}, Device, EncryptionSettings, OlmError, ToDeviceRequest, }; @@ -226,21 +226,43 @@ impl GroupSessionManager { async fn encrypt_session_for( content: AnyToDeviceEventContent, devices: Vec, - ) -> OlmResult<(Uuid, ToDeviceRequest, Vec)> { + message_index: u32, + ) -> OlmResult<( + Uuid, + ToDeviceRequest, + BTreeMap>, + Vec, + )> { let mut messages = BTreeMap::new(); let mut changed_sessions = Vec::new(); + let mut share_infos = BTreeMap::new(); let encrypt = |device: Device, content: AnyToDeviceEventContent| async move { let mut message = BTreeMap::new(); + let mut share_infos = BTreeMap::new(); let encrypted = device.encrypt(content.clone()).await; let used_session = match encrypted { Ok((session, encrypted)) => { - message.entry(device.user_id().clone()).or_insert_with(BTreeMap::new).insert( - DeviceIdOrAllDevices::DeviceId(device.device_id().into()), - Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)), - ); + message + .entry(device.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + DeviceIdOrAllDevices::DeviceId(device.device_id().into()), + Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)), + ); + share_infos + .entry(device.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + device.device_id().to_owned(), + ShareInfo { + sender_key: session.sender_key().to_owned(), + message_index, + }, + ); + Some(session) } // TODO we'll want to create m.room_key.withheld here. @@ -249,7 +271,7 @@ impl GroupSessionManager { Err(e) => return Err(e), }; - Ok((used_session, message)) + Ok((used_session, share_infos, message)) }; let tasks: Vec<_> = @@ -258,7 +280,7 @@ impl GroupSessionManager { let results = join_all(tasks).await; for result in results { - let (used_session, message) = result.expect("Encryption task panicked")?; + let (used_session, infos, message) = result.expect("Encryption task panicked")?; if let Some(session) = used_session { changed_sessions.push(session); @@ -267,6 +289,10 @@ impl GroupSessionManager { for (user, device_messages) in message.into_iter() { messages.entry(user).or_insert_with(BTreeMap::new).extend(device_messages); } + + for (user, infos) in infos.into_iter() { + share_infos.entry(user).or_insert_with(BTreeMap::new).extend(infos); + } } let id = Uuid::new_v4(); @@ -280,7 +306,7 @@ impl GroupSessionManager { "Created a to-device request carrying a room_key" ); - Ok((id, request, changed_sessions)) + Ok((id, request, share_infos, changed_sessions)) } /// Given a list of user and an outbound session, return the list of users @@ -380,11 +406,11 @@ impl GroupSessionManager { message_index: u32, being_shared: Arc>, ) -> OlmResult> { - let (id, request, used_sessions) = - Self::encrypt_session_for(content.clone(), chunk).await?; + let (id, request, share_infos, used_sessions) = + Self::encrypt_session_for(content.clone(), chunk, message_index).await?; if !request.messages.is_empty() { - outbound.add_request(id, request.into(), message_index); + outbound.add_request(id, request.into(), share_infos); being_shared.insert(id, outbound.clone()); } @@ -453,12 +479,8 @@ impl GroupSessionManager { let devices: Vec = devices .into_iter() .map(|(_, d)| { - d.into_iter().filter(|d| { - matches!( - outbound.is_shared_with(d.user_id(), d.device_id()), - ShareState::NotShared - ) - }) + d.into_iter() + .filter(|d| matches!(outbound.is_shared_with(d), ShareState::NotShared)) }) .flatten() .collect(); diff --git a/matrix_sdk_crypto/src/store/sled.rs b/matrix_sdk_crypto/src/store/sled.rs index 8a474360e..d24bd4c8a 100644 --- a/matrix_sdk_crypto/src/store/sled.rs +++ b/matrix_sdk_crypto/src/store/sled.rs @@ -14,7 +14,7 @@ use std::{ collections::{HashMap, HashSet}, - convert::TryFrom, + convert::{TryFrom, TryInto}, path::{Path, PathBuf}, sync::{Arc, RwLock}, }; @@ -31,6 +31,7 @@ use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Config, Db, Transactional, Tree, }; +use tracing::trace; use uuid::Uuid; use super::{ @@ -46,6 +47,7 @@ use crate::{ /// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will /// panic once we try to pickle a Signing object. const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456"; +const DATABASE_VERSION: u8 = 1; trait EncodeKey { const SEPARATOR: u8 = 0xff; @@ -204,12 +206,43 @@ impl SledStore { self.account_info.read().unwrap().clone() } + fn upgrade_databse(db: &Db) -> Result<()> { + let version = db + .get("version")? + .map(|v| { + let (version_bytes, _) = v.split_at(std::mem::size_of::()); + u8::from_be_bytes(version_bytes.try_into().unwrap_or_default()) + }) + .unwrap_or_default(); + + if version != DATABASE_VERSION { + trace!( + version = version, + new_version = DATABASE_VERSION, + "Upgrading the Sled crypto store" + ); + } + + if version == 0 { + // We changed the schema but migrating this isn't important since we + // rotate the group sessions relatively often anyways so we just + // drop it. + db.drop_tree("outbound_group_sessions")?; + } + + db.insert("version", DATABASE_VERSION.to_be_bytes().as_ref())?; + + Ok(()) + } + fn open_helper(db: Db, path: Option, passphrase: Option<&str>) -> Result { + Self::upgrade_databse(&db)?; let account = db.open_tree("account")?; let private_identity = db.open_tree("private_identity")?; let sessions = db.open_tree("session")?; let inbound_group_sessions = db.open_tree("inbound_group_sessions")?; + let outbound_group_sessions = db.open_tree("outbound_group_sessions")?; let tracked_users = db.open_tree("tracked_users")?;