crypto: Store and restore outbound group sessions
parent
ac6dad3f35
commit
6cb2c8b468
|
@ -24,7 +24,7 @@ mod inbound;
|
||||||
mod outbound;
|
mod outbound;
|
||||||
|
|
||||||
pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession};
|
pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession};
|
||||||
pub use outbound::{EncryptionSettings, OutboundGroupSession};
|
pub use outbound::{EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession};
|
||||||
|
|
||||||
/// The private session key of a group session.
|
/// The private session key of a group session.
|
||||||
/// Can be used to create a new inbound group session.
|
/// Can be used to create a new inbound group session.
|
||||||
|
|
|
@ -23,6 +23,7 @@ use matrix_sdk_common::{
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::max,
|
cmp::max,
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
fmt,
|
fmt,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||||
|
@ -41,18 +42,24 @@ use matrix_sdk_common::{
|
||||||
instant::Instant,
|
instant::Instant,
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use olm_rs::outbound_group_session::OlmOutboundGroupSession;
|
|
||||||
pub use olm_rs::{
|
pub use olm_rs::{
|
||||||
account::IdentityKeys,
|
account::IdentityKeys,
|
||||||
session::{OlmMessage, PreKeyMessage},
|
session::{OlmMessage, PreKeyMessage},
|
||||||
utility::OlmUtility,
|
utility::OlmUtility,
|
||||||
};
|
};
|
||||||
|
use olm_rs::{
|
||||||
|
errors::OlmGroupSessionError, outbound_group_session::OlmOutboundGroupSession, PicklingMode,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ToDeviceRequest;
|
use crate::ToDeviceRequest;
|
||||||
|
|
||||||
use super::GroupSessionKey;
|
use super::{
|
||||||
|
super::{deserialize_instant, serialize_instant},
|
||||||
|
GroupSessionKey,
|
||||||
|
};
|
||||||
|
|
||||||
const ROTATION_PERIOD: Duration = Duration::from_millis(604800000);
|
const ROTATION_PERIOD: Duration = Duration::from_millis(604800000);
|
||||||
const ROTATION_MESSAGES: u64 = 100;
|
const ROTATION_MESSAGES: u64 = 100;
|
||||||
|
@ -60,7 +67,7 @@ const ROTATION_MESSAGES: u64 = 100;
|
||||||
/// Settings for an encrypted room.
|
/// Settings for an encrypted room.
|
||||||
///
|
///
|
||||||
/// This determines the algorithm and rotation periods of a group session.
|
/// This determines the algorithm and rotation periods of a group session.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct EncryptionSettings {
|
pub struct EncryptionSettings {
|
||||||
/// The encryption algorithm that should be used in the room.
|
/// The encryption algorithm that should be used in the room.
|
||||||
pub algorithm: EventEncryptionAlgorithm,
|
pub algorithm: EventEncryptionAlgorithm,
|
||||||
|
@ -158,7 +165,7 @@ impl OutboundGroupSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_request(&self, request_id: Uuid, request: Arc<ToDeviceRequest>) {
|
pub(crate) fn add_request(&self, request_id: Uuid, request: Arc<ToDeviceRequest>) {
|
||||||
self.to_share_with_set.insert(request_id, request);
|
self.to_share_with_set.insert(request_id, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,6 +390,101 @@ impl OutboundGroupSession {
|
||||||
.map(|i| i.value().clone())
|
.map(|i| i.value().clone())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restore a Session from a previously pickled string.
|
||||||
|
///
|
||||||
|
/// Returns the restored group session or a `OlmGroupSessionError` if there
|
||||||
|
/// was an error.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `device_id` - The device id of the device that created this session.
|
||||||
|
/// Put differently, our own device id.
|
||||||
|
///
|
||||||
|
/// * `identity_keys` - The identity keys of the device that created this
|
||||||
|
/// session, our own identity keys.
|
||||||
|
///
|
||||||
|
/// * `pickle` - The pickled version of the `OutboundGroupSession`.
|
||||||
|
///
|
||||||
|
/// * `pickle_mode` - The mode that was used to pickle the session, either
|
||||||
|
/// an unencrypted mode or an encrypted using passphrase.
|
||||||
|
pub fn from_pickle(
|
||||||
|
device_id: Arc<DeviceIdBox>,
|
||||||
|
identity_keys: Arc<IdentityKeys>,
|
||||||
|
pickle: PickledOutboundGroupSession,
|
||||||
|
pickling_mode: PicklingMode,
|
||||||
|
) -> Result<Self, OlmGroupSessionError> {
|
||||||
|
let inner = OlmOutboundGroupSession::unpickle(pickle.pickle.0, pickling_mode)?;
|
||||||
|
let session_id = inner.session_id();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: Arc::new(Mutex::new(inner)),
|
||||||
|
device_id,
|
||||||
|
account_identity_keys: identity_keys,
|
||||||
|
session_id: session_id.into(),
|
||||||
|
room_id: pickle.room_id,
|
||||||
|
creation_time: pickle.creation_time.into(),
|
||||||
|
message_count: AtomicU64::from(pickle.message_count).into(),
|
||||||
|
shared: AtomicBool::from(pickle.shared).into(),
|
||||||
|
invalidated: AtomicBool::from(pickle.invalidated).into(),
|
||||||
|
settings: pickle.settings,
|
||||||
|
shared_with_set: Arc::new(
|
||||||
|
pickle
|
||||||
|
.shared_with_set
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k, v.into_iter().collect()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
to_share_with_set: Arc::new(pickle.requests.into_iter().collect()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store the group session as a base64 encoded string and associated data
|
||||||
|
/// belonging to the session.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `pickle_mode` - The mode that should be used to pickle the group session,
|
||||||
|
/// either an unencrypted mode or an encrypted using passphrase.
|
||||||
|
pub async fn pickle(&self, pickling_mode: PicklingMode) -> PickledOutboundGroupSession {
|
||||||
|
let pickle: OutboundGroupSessionPickle =
|
||||||
|
self.inner.lock().await.pickle(pickling_mode).into();
|
||||||
|
|
||||||
|
PickledOutboundGroupSession {
|
||||||
|
pickle,
|
||||||
|
room_id: self.room_id.clone(),
|
||||||
|
settings: self.settings.clone(),
|
||||||
|
creation_time: *self.creation_time,
|
||||||
|
message_count: self.message_count.load(Ordering::SeqCst),
|
||||||
|
shared: self.shared(),
|
||||||
|
invalidated: self.invalidated(),
|
||||||
|
shared_with_set: self
|
||||||
|
.shared_with_set
|
||||||
|
.iter()
|
||||||
|
.map(|u| {
|
||||||
|
(
|
||||||
|
u.key().clone(),
|
||||||
|
#[allow(clippy::map_clone)]
|
||||||
|
u.value().iter().map(|d| d.clone()).collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
requests: self
|
||||||
|
.to_share_with_set
|
||||||
|
.iter()
|
||||||
|
.map(|r| (*r.key(), r.value().clone()))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct OutboundGroupSessionPickle(String);
|
||||||
|
|
||||||
|
impl From<String> for OutboundGroupSessionPickle {
|
||||||
|
fn from(p: String) -> Self {
|
||||||
|
Self(p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
@ -397,6 +499,36 @@ impl std::fmt::Debug for OutboundGroupSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pickled version of an `InboundGroupSession`.
|
||||||
|
///
|
||||||
|
/// Holds all the information that needs to be stored in a database to restore
|
||||||
|
/// an InboundGroupSession.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PickledOutboundGroupSession {
|
||||||
|
/// The pickle string holding the OutboundGroupSession.
|
||||||
|
pub pickle: OutboundGroupSessionPickle,
|
||||||
|
/// The settings this session adheres to.
|
||||||
|
pub settings: Arc<EncryptionSettings>,
|
||||||
|
/// The room id this session is used for.
|
||||||
|
pub room_id: Arc<RoomId>,
|
||||||
|
/// The timestamp when this session was created.
|
||||||
|
#[serde(
|
||||||
|
deserialize_with = "deserialize_instant",
|
||||||
|
serialize_with = "serialize_instant"
|
||||||
|
)]
|
||||||
|
pub creation_time: Instant,
|
||||||
|
/// The number of messages this session has already encrypted.
|
||||||
|
pub message_count: u64,
|
||||||
|
/// Is the session shared.
|
||||||
|
pub shared: bool,
|
||||||
|
/// Has the session been invalidated.
|
||||||
|
pub invalidated: bool,
|
||||||
|
/// The set of users the session has been already shared with.
|
||||||
|
pub shared_with_set: BTreeMap<UserId, BTreeSet<DeviceIdBox>>,
|
||||||
|
/// Requests that need to be sent out to share the session.
|
||||||
|
pub requests: BTreeMap<Uuid, Arc<ToDeviceRequest>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
|
@ -25,16 +25,39 @@ mod utility;
|
||||||
|
|
||||||
pub(crate) use account::{Account, OlmDecryptionInfo, SessionType};
|
pub(crate) use account::{Account, OlmDecryptionInfo, SessionType};
|
||||||
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
|
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
|
||||||
|
pub(crate) use group_sessions::GroupSessionKey;
|
||||||
pub use group_sessions::{
|
pub use group_sessions::{
|
||||||
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
|
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
|
||||||
PickledInboundGroupSession,
|
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession,
|
||||||
};
|
};
|
||||||
pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession};
|
|
||||||
pub use olm_rs::{account::IdentityKeys, PicklingMode};
|
pub use olm_rs::{account::IdentityKeys, PicklingMode};
|
||||||
pub use session::{PickledSession, Session, SessionPickle};
|
pub use session::{PickledSession, Session, SessionPickle};
|
||||||
pub use signing::{PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
|
pub use signing::{PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
|
||||||
pub(crate) use utility::Utility;
|
pub(crate) use utility::Utility;
|
||||||
|
|
||||||
|
use matrix_sdk_common::instant::{Duration, Instant};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
pub(crate) fn serialize_instant<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let duration = instant.elapsed();
|
||||||
|
duration.serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize_instant<'de, D>(deserializer: D) -> Result<Instant, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let duration = Duration::deserialize(deserializer)?;
|
||||||
|
let now = Instant::now();
|
||||||
|
let instant = now
|
||||||
|
.checked_sub(duration)
|
||||||
|
.ok_or_else(|| serde::de::Error::custom("Can't substract the the current instant"))?;
|
||||||
|
Ok(instant)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
use crate::olm::{InboundGroupSession, ReadOnlyAccount, Session};
|
use crate::olm::{InboundGroupSession, ReadOnlyAccount, Session};
|
||||||
|
|
|
@ -20,14 +20,13 @@ use matrix_sdk_common::{
|
||||||
EventType,
|
EventType,
|
||||||
},
|
},
|
||||||
identifiers::{DeviceId, DeviceKeyAlgorithm, UserId},
|
identifiers::{DeviceId, DeviceKeyAlgorithm, UserId},
|
||||||
instant::{Duration, Instant},
|
instant::Instant,
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
};
|
};
|
||||||
use olm_rs::{errors::OlmSessionError, session::OlmSession, PicklingMode};
|
use olm_rs::{errors::OlmSessionError, session::OlmSession, PicklingMode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use super::IdentityKeys;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{EventError, OlmResult, SessionUnpicklingError},
|
error::{EventError, OlmResult, SessionUnpicklingError},
|
||||||
ReadOnlyDevice,
|
ReadOnlyDevice,
|
||||||
|
@ -38,6 +37,8 @@ pub use olm_rs::{
|
||||||
utility::OlmUtility,
|
utility::OlmUtility,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{deserialize_instant, serialize_instant, IdentityKeys};
|
||||||
|
|
||||||
/// Cryptographic session that enables secure communication between two
|
/// Cryptographic session that enables secure communication between two
|
||||||
/// `Account`s
|
/// `Account`s
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -187,9 +188,8 @@ impl Session {
|
||||||
PickledSession {
|
PickledSession {
|
||||||
pickle: SessionPickle::from(pickle),
|
pickle: SessionPickle::from(pickle),
|
||||||
sender_key: self.sender_key.to_string(),
|
sender_key: self.sender_key.to_string(),
|
||||||
// FIXME this should use the duration from the unix epoch.
|
creation_time: *self.creation_time,
|
||||||
creation_time: self.creation_time.elapsed(),
|
last_use_time: *self.last_use_time,
|
||||||
last_use_time: self.last_use_time.elapsed(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,16 +220,6 @@ impl Session {
|
||||||
let session = OlmSession::unpickle(pickle.pickle.0, pickle_mode)?;
|
let session = OlmSession::unpickle(pickle.pickle.0, pickle_mode)?;
|
||||||
let session_id = session.session_id();
|
let session_id = session.session_id();
|
||||||
|
|
||||||
// FIXME this should use the UNIX epoch.
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
let creation_time = now
|
|
||||||
.checked_sub(pickle.creation_time)
|
|
||||||
.ok_or(SessionUnpicklingError::SessionTimestampError)?;
|
|
||||||
let last_use_time = now
|
|
||||||
.checked_sub(pickle.last_use_time)
|
|
||||||
.ok_or(SessionUnpicklingError::SessionTimestampError)?;
|
|
||||||
|
|
||||||
Ok(Session {
|
Ok(Session {
|
||||||
user_id,
|
user_id,
|
||||||
device_id,
|
device_id,
|
||||||
|
@ -237,8 +227,8 @@ impl Session {
|
||||||
inner: Arc::new(Mutex::new(session)),
|
inner: Arc::new(Mutex::new(session)),
|
||||||
session_id: session_id.into(),
|
session_id: session_id.into(),
|
||||||
sender_key: pickle.sender_key.into(),
|
sender_key: pickle.sender_key.into(),
|
||||||
creation_time: Arc::new(creation_time),
|
creation_time: Arc::new(pickle.creation_time),
|
||||||
last_use_time: Arc::new(last_use_time),
|
last_use_time: Arc::new(pickle.last_use_time),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,9 +250,17 @@ pub struct PickledSession {
|
||||||
/// The curve25519 key of the other user that we share this session with.
|
/// The curve25519 key of the other user that we share this session with.
|
||||||
pub sender_key: String,
|
pub sender_key: String,
|
||||||
/// The relative time elapsed since the session was created.
|
/// The relative time elapsed since the session was created.
|
||||||
pub creation_time: Duration,
|
#[serde(
|
||||||
|
deserialize_with = "deserialize_instant",
|
||||||
|
serialize_with = "serialize_instant"
|
||||||
|
)]
|
||||||
|
pub creation_time: Instant,
|
||||||
/// The relative time elapsed since the session was last used.
|
/// The relative time elapsed since the session was last used.
|
||||||
pub last_use_time: Duration,
|
#[serde(
|
||||||
|
deserialize_with = "deserialize_instant",
|
||||||
|
serialize_with = "serialize_instant"
|
||||||
|
)]
|
||||||
|
pub last_use_time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The typed representation of a base64 encoded string of the Olm Session pickle.
|
/// The typed representation of a base64 encoded string of the Olm Session pickle.
|
||||||
|
|
|
@ -36,11 +36,12 @@ use matrix_sdk_common::{
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
/// Customized version of `ruma_client_api::r0::to_device::send_event_to_device::Request`, using a
|
/// Customized version of `ruma_client_api::r0::to_device::send_event_to_device::Request`, using a
|
||||||
/// UUID for the transaction ID.
|
/// UUID for the transaction ID.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ToDeviceRequest {
|
pub struct ToDeviceRequest {
|
||||||
/// Type of event being sent to each device.
|
/// Type of event being sent to each device.
|
||||||
pub event_type: EventType,
|
pub event_type: EventType,
|
||||||
|
|
|
@ -101,7 +101,13 @@ impl GroupSessionManager {
|
||||||
panic!("Session expired");
|
panic!("Session expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(session.encrypt(content).await)
|
let content = session.encrypt(content).await;
|
||||||
|
|
||||||
|
let mut changes = Changes::default();
|
||||||
|
changes.outbound_group_sessions.push(session);
|
||||||
|
self.store.save_changes(changes).await?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new outbound group session.
|
/// Create a new outbound group session.
|
||||||
|
@ -130,8 +136,22 @@ impl GroupSessionManager {
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
settings: EncryptionSettings,
|
settings: EncryptionSettings,
|
||||||
) -> OlmResult<(OutboundGroupSession, Option<InboundGroupSession>)> {
|
) -> OlmResult<(OutboundGroupSession, Option<InboundGroupSession>)> {
|
||||||
#[allow(clippy::map_clone)]
|
// Get the cached session, if there isn't one load one from the store
|
||||||
if let Some(s) = self.outbound_group_sessions.get(room_id).map(|s| s.clone()) {
|
// and put it in the cache.
|
||||||
|
let outbound_session = if let Some(s) = self.outbound_group_sessions.get(room_id) {
|
||||||
|
Some(s.clone())
|
||||||
|
} else if let Some(s) = self.store.get_outbound_group_sessions(room_id).await? {
|
||||||
|
self.outbound_group_sessions
|
||||||
|
.insert(room_id.clone(), s.clone());
|
||||||
|
|
||||||
|
Some(s)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there is no session or the session has expired or is invalid,
|
||||||
|
// create a new one.
|
||||||
|
if let Some(s) = outbound_session {
|
||||||
if s.expired() || s.invalidated() {
|
if s.expired() || s.invalidated() {
|
||||||
self.create_outbound_group_session(room_id, settings)
|
self.create_outbound_group_session(room_id, settings)
|
||||||
.await
|
.await
|
||||||
|
@ -294,6 +314,7 @@ impl GroupSessionManager {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(inbound) = inbound {
|
if let Some(inbound) = inbound {
|
||||||
|
changes.outbound_group_sessions.push(outbound.clone());
|
||||||
changes.inbound_group_sessions.push(inbound);
|
changes.inbound_group_sessions.push(inbound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,6 +324,7 @@ impl GroupSessionManager {
|
||||||
let (outbound, inbound) = self
|
let (outbound, inbound) = self
|
||||||
.create_outbound_group_session(room_id, encryption_settings)
|
.create_outbound_group_session(room_id, encryption_settings)
|
||||||
.await?;
|
.await?;
|
||||||
|
changes.outbound_group_sessions.push(outbound.clone());
|
||||||
changes.inbound_group_sessions.push(inbound);
|
changes.inbound_group_sessions.push(inbound);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
|
|
@ -30,7 +30,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
identities::{ReadOnlyDevice, UserIdentities},
|
identities::{ReadOnlyDevice, UserIdentities},
|
||||||
olm::PrivateCrossSigningIdentity,
|
olm::{OutboundGroupSession, PrivateCrossSigningIdentity},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An in-memory only store that will forget all the E2EE key once it's dropped.
|
/// An in-memory only store that will forget all the E2EE key once it's dropped.
|
||||||
|
@ -232,6 +232,13 @@ impl CryptoStore for MemoryStore {
|
||||||
.or_insert_with(DashSet::new)
|
.or_insert_with(DashSet::new)
|
||||||
.contains(&message_hash.hash))
|
.contains(&message_hash.hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_outbound_group_sessions(
|
||||||
|
&self,
|
||||||
|
_: &RoomId,
|
||||||
|
) -> Result<Option<OutboundGroupSession>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -86,7 +86,8 @@ use crate::{
|
||||||
error::SessionUnpicklingError,
|
error::SessionUnpicklingError,
|
||||||
identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities},
|
identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities},
|
||||||
olm::{
|
olm::{
|
||||||
InboundGroupSession, OlmMessageHash, PrivateCrossSigningIdentity, ReadOnlyAccount, Session,
|
InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
|
||||||
|
ReadOnlyAccount, Session,
|
||||||
},
|
},
|
||||||
verification::VerificationMachine,
|
verification::VerificationMachine,
|
||||||
};
|
};
|
||||||
|
@ -116,6 +117,7 @@ pub struct Changes {
|
||||||
pub sessions: Vec<Session>,
|
pub sessions: Vec<Session>,
|
||||||
pub message_hashes: Vec<OlmMessageHash>,
|
pub message_hashes: Vec<OlmMessageHash>,
|
||||||
pub inbound_group_sessions: Vec<InboundGroupSession>,
|
pub inbound_group_sessions: Vec<InboundGroupSession>,
|
||||||
|
pub outbound_group_sessions: Vec<OutboundGroupSession>,
|
||||||
pub identities: IdentityChanges,
|
pub identities: IdentityChanges,
|
||||||
pub devices: DeviceChanges,
|
pub devices: DeviceChanges,
|
||||||
}
|
}
|
||||||
|
@ -388,6 +390,12 @@ pub trait CryptoStore: AsyncTraitDeps {
|
||||||
/// Get all the inbound group sessions we have stored.
|
/// Get all the inbound group sessions we have stored.
|
||||||
async fn get_inbound_group_sessions(&self) -> Result<Vec<InboundGroupSession>>;
|
async fn get_inbound_group_sessions(&self) -> Result<Vec<InboundGroupSession>>;
|
||||||
|
|
||||||
|
/// Get the outobund group sessions we have stored that is used for the given room.
|
||||||
|
async fn get_outbound_group_sessions(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
) -> Result<Option<OutboundGroupSession>>;
|
||||||
|
|
||||||
/// Is the given user already tracked.
|
/// Is the given user already tracked.
|
||||||
fn is_user_tracked(&self, user_id: &UserId) -> bool;
|
fn is_user_tracked(&self, user_id: &UserId) -> bool;
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
identities::{ReadOnlyDevice, UserIdentities},
|
identities::{ReadOnlyDevice, UserIdentities},
|
||||||
olm::{PickledInboundGroupSession, PrivateCrossSigningIdentity},
|
olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will
|
/// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will
|
||||||
|
@ -62,6 +62,7 @@ pub struct SledStore {
|
||||||
olm_hashes: Tree,
|
olm_hashes: Tree,
|
||||||
sessions: Tree,
|
sessions: Tree,
|
||||||
inbound_group_sessions: Tree,
|
inbound_group_sessions: Tree,
|
||||||
|
outbound_group_sessions: Tree,
|
||||||
|
|
||||||
devices: Tree,
|
devices: Tree,
|
||||||
identities: Tree,
|
identities: Tree,
|
||||||
|
@ -102,6 +103,7 @@ impl SledStore {
|
||||||
|
|
||||||
let sessions = db.open_tree("session")?;
|
let sessions = db.open_tree("session")?;
|
||||||
let inbound_group_sessions = db.open_tree("inbound_group_sessions")?;
|
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")?;
|
let tracked_users = db.open_tree("tracked_users")?;
|
||||||
let users_for_key_query = db.open_tree("users_for_key_query")?;
|
let users_for_key_query = db.open_tree("users_for_key_query")?;
|
||||||
let olm_hashes = db.open_tree("olm_hashes")?;
|
let olm_hashes = db.open_tree("olm_hashes")?;
|
||||||
|
@ -129,6 +131,7 @@ impl SledStore {
|
||||||
tracked_users_cache: DashSet::new().into(),
|
tracked_users_cache: DashSet::new().into(),
|
||||||
users_for_key_query_cache: DashSet::new().into(),
|
users_for_key_query_cache: DashSet::new().into(),
|
||||||
inbound_group_sessions,
|
inbound_group_sessions,
|
||||||
|
outbound_group_sessions,
|
||||||
devices,
|
devices,
|
||||||
tracked_users,
|
tracked_users,
|
||||||
users_for_key_query,
|
users_for_key_query,
|
||||||
|
@ -179,6 +182,34 @@ impl SledStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_outbound_group_session(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
) -> Result<Option<OutboundGroupSession>> {
|
||||||
|
let account = self
|
||||||
|
.load_account()
|
||||||
|
.await?
|
||||||
|
.ok_or(CryptoStoreError::AccountUnset)?;
|
||||||
|
|
||||||
|
let device_id: Arc<DeviceIdBox> = account.device_id().to_owned().into();
|
||||||
|
let identity_keys = account.identity_keys;
|
||||||
|
|
||||||
|
self.outbound_group_sessions
|
||||||
|
.get(room_id.as_str())?
|
||||||
|
.map(|p| serde_json::from_slice(&p).map_err(CryptoStoreError::Serialization))
|
||||||
|
.transpose()?
|
||||||
|
.map(|p| {
|
||||||
|
OutboundGroupSession::from_pickle(
|
||||||
|
device_id,
|
||||||
|
identity_keys,
|
||||||
|
p,
|
||||||
|
self.get_pickle_mode(),
|
||||||
|
)
|
||||||
|
.map_err(CryptoStoreError::OlmGroupSession)
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
async fn save_changes(&self, changes: Changes) -> Result<()> {
|
async fn save_changes(&self, changes: Changes) -> Result<()> {
|
||||||
let account_pickle = if let Some(a) = changes.account {
|
let account_pickle = if let Some(a) = changes.account {
|
||||||
Some(a.pickle(self.get_pickle_mode()).await)
|
Some(a.pickle(self.get_pickle_mode()).await)
|
||||||
|
@ -218,6 +249,15 @@ impl SledStore {
|
||||||
inbound_session_changes.insert(key, pickle);
|
inbound_session_changes.insert(key, pickle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut outbound_session_changes = HashMap::new();
|
||||||
|
|
||||||
|
for session in changes.outbound_group_sessions {
|
||||||
|
let room_id = session.room_id();
|
||||||
|
let pickle = session.pickle(self.get_pickle_mode()).await;
|
||||||
|
|
||||||
|
outbound_session_changes.insert(room_id.clone(), pickle);
|
||||||
|
}
|
||||||
|
|
||||||
let identity_changes = changes.identities;
|
let identity_changes = changes.identities;
|
||||||
let olm_hashes = changes.message_hashes;
|
let olm_hashes = changes.message_hashes;
|
||||||
|
|
||||||
|
@ -228,6 +268,7 @@ impl SledStore {
|
||||||
&self.identities,
|
&self.identities,
|
||||||
&self.sessions,
|
&self.sessions,
|
||||||
&self.inbound_group_sessions,
|
&self.inbound_group_sessions,
|
||||||
|
&self.outbound_group_sessions,
|
||||||
&self.olm_hashes,
|
&self.olm_hashes,
|
||||||
)
|
)
|
||||||
.transaction(
|
.transaction(
|
||||||
|
@ -238,6 +279,7 @@ impl SledStore {
|
||||||
identities,
|
identities,
|
||||||
sessions,
|
sessions,
|
||||||
inbound_sessions,
|
inbound_sessions,
|
||||||
|
outbound_sessions,
|
||||||
hashes,
|
hashes,
|
||||||
)| {
|
)| {
|
||||||
if let Some(a) = &account_pickle {
|
if let Some(a) = &account_pickle {
|
||||||
|
@ -290,6 +332,14 @@ impl SledStore {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (key, session) in &outbound_session_changes {
|
||||||
|
outbound_sessions.insert(
|
||||||
|
key.as_str(),
|
||||||
|
serde_json::to_vec(&session)
|
||||||
|
.map_err(ConflictableTransactionError::Abort)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
for hash in &olm_hashes {
|
for hash in &olm_hashes {
|
||||||
hashes.insert(
|
hashes.insert(
|
||||||
serde_json::to_vec(&hash)
|
serde_json::to_vec(&hash)
|
||||||
|
@ -503,6 +553,13 @@ impl CryptoStore for SledStore {
|
||||||
.olm_hashes
|
.olm_hashes
|
||||||
.contains_key(serde_json::to_vec(message_hash)?)?)
|
.contains_key(serde_json::to_vec(message_hash)?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_outbound_group_sessions(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
) -> Result<Option<OutboundGroupSession>> {
|
||||||
|
self.load_outbound_group_session(room_id).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue