crypto: Disallow re-sharing room keys with devices with changed curve keys
This commit is contained in:
parent
f9de77a75d
commit
e4a9cf0bba
7 changed files with 207 additions and 128 deletions
|
@ -41,7 +41,7 @@ use ruma::{
|
||||||
};
|
};
|
||||||
use tracing::{debug, info, trace, warn};
|
use tracing::{debug, info, trace, warn};
|
||||||
|
|
||||||
use super::{GossipRequest, KeyforwardDecision, RequestEvent, RequestInfo, SecretInfo, WaitQueue};
|
use super::{GossipRequest, KeyForwardDecision, RequestEvent, RequestInfo, SecretInfo, WaitQueue};
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{OlmError, OlmResult},
|
error::{OlmError, OlmResult},
|
||||||
olm::{InboundGroupSession, Session, ShareState},
|
olm::{InboundGroupSession, Session, ShareState},
|
||||||
|
@ -370,12 +370,21 @@ impl GossipMachine {
|
||||||
if let Some(device) = device {
|
if let Some(device) = device {
|
||||||
match self.should_share_key(&device, &session).await {
|
match self.should_share_key(&device, &session).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(
|
if let KeyForwardDecision::ChangedSenderKey = e {
|
||||||
user_id = device.user_id().as_str(),
|
warn!(
|
||||||
device_id = device.device_id().as_str(),
|
user_id = device.user_id().as_str(),
|
||||||
reason =? e,
|
device_id = device.device_id().as_str(),
|
||||||
"Received a key request that we won't serve",
|
"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(None)
|
||||||
}
|
}
|
||||||
|
@ -482,7 +491,7 @@ impl GossipMachine {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
session: &InboundGroupSession,
|
session: &InboundGroupSession,
|
||||||
) -> Result<Option<u32>, KeyforwardDecision> {
|
) -> Result<Option<u32>, KeyForwardDecision> {
|
||||||
let outbound_session = self
|
let outbound_session = self
|
||||||
.outbound_group_sessions
|
.outbound_group_sessions
|
||||||
.get_with_id(session.room_id(), session.session_id())
|
.get_with_id(session.room_id(), session.session_id())
|
||||||
|
@ -494,7 +503,7 @@ impl GossipMachine {
|
||||||
if device.verified() {
|
if device.verified() {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} 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
|
// users/devices that received the session, if it wasn't shared check if
|
||||||
// it's our own device and if it's trusted.
|
// it's our own device and if it's trusted.
|
||||||
if let Some(outbound) = outbound_session {
|
if let Some(outbound) = outbound_session {
|
||||||
if let ShareState::Shared(message_index) =
|
match outbound.is_shared_with(device) {
|
||||||
outbound.is_shared_with(device.user_id(), device.device_id())
|
ShareState::Shared(message_index) => Ok(Some(message_index)),
|
||||||
{
|
_ if device.user_id() == self.user_id() => own_device_check(),
|
||||||
Ok(Some(message_index))
|
ShareState::SharedButChangedSenderKey => Err(KeyForwardDecision::ChangedSenderKey),
|
||||||
} else if device.user_id() == self.user_id() {
|
ShareState::NotShared => Err(KeyForwardDecision::OutboundSessionNotShared),
|
||||||
own_device_check()
|
|
||||||
} else {
|
|
||||||
Err(KeyforwardDecision::OutboundSessionNotShared)
|
|
||||||
}
|
}
|
||||||
// Else just check if it's one of our own devices that requested the key
|
// Else just check if it's one of our own devices that requested the key
|
||||||
// and check if the device is trusted.
|
// 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
|
// Otherwise, there's not enough info to decide if we can safely share
|
||||||
// the session.
|
// the session.
|
||||||
} else {
|
} else {
|
||||||
Err(KeyforwardDecision::MissingOutboundSession)
|
Err(KeyForwardDecision::MissingOutboundSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -902,10 +908,10 @@ mod test {
|
||||||
},
|
},
|
||||||
room_id,
|
room_id,
|
||||||
to_device::DeviceIdOrAllDevices,
|
to_device::DeviceIdOrAllDevices,
|
||||||
user_id, DeviceIdBox, RoomId, UserId,
|
user_id, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{GossipMachine, KeyforwardDecision};
|
use super::{GossipMachine, KeyForwardDecision};
|
||||||
use crate::{
|
use crate::{
|
||||||
identities::{LocalTrust, ReadOnlyDevice},
|
identities::{LocalTrust, ReadOnlyDevice},
|
||||||
olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount},
|
olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||||
|
@ -1182,12 +1188,9 @@ mod test {
|
||||||
account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
||||||
|
|
||||||
// We don't share keys with untrusted devices.
|
// We don't share keys with untrusted devices.
|
||||||
assert_eq!(
|
assert_matches!(
|
||||||
machine
|
machine.should_share_key(&own_device, &inbound).await,
|
||||||
.should_share_key(&own_device, &inbound)
|
Err(KeyForwardDecision::UntrustedDevice)
|
||||||
.await
|
|
||||||
.expect_err("Should not share with untrusted"),
|
|
||||||
KeyforwardDecision::UntrustedDevice
|
|
||||||
);
|
);
|
||||||
own_device.set_trust_state(LocalTrust::Verified);
|
own_device.set_trust_state(LocalTrust::Verified);
|
||||||
// Now we do want to share the keys.
|
// 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
|
// We don't share sessions with other user's devices if no outbound
|
||||||
// session was provided.
|
// session was provided.
|
||||||
assert_eq!(
|
assert_matches!(
|
||||||
machine
|
machine.should_share_key(&bob_device, &inbound).await,
|
||||||
.should_share_key(&bob_device, &inbound)
|
Err(KeyForwardDecision::MissingOutboundSession)
|
||||||
.await
|
|
||||||
.expect_err("Should not share with other."),
|
|
||||||
KeyforwardDecision::MissingOutboundSession
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut changes = Changes::default();
|
let mut changes = Changes::default();
|
||||||
|
@ -1218,28 +1218,26 @@ mod test {
|
||||||
|
|
||||||
// We don't share sessions with other user's devices if the session
|
// We don't share sessions with other user's devices if the session
|
||||||
// wasn't shared in the first place.
|
// wasn't shared in the first place.
|
||||||
assert_eq!(
|
assert_matches!(
|
||||||
machine
|
machine.should_share_key(&bob_device, &inbound).await,
|
||||||
.should_share_key(&bob_device, &inbound)
|
Err(KeyForwardDecision::OutboundSessionNotShared)
|
||||||
.await
|
|
||||||
.expect_err("Should not share with other unless shared."),
|
|
||||||
KeyforwardDecision::OutboundSessionNotShared
|
|
||||||
);
|
);
|
||||||
|
|
||||||
bob_device.set_trust_state(LocalTrust::Verified);
|
bob_device.set_trust_state(LocalTrust::Verified);
|
||||||
|
|
||||||
// We don't share sessions with other user's devices if the session
|
// 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.
|
// wasn't shared in the first place even if the device is trusted.
|
||||||
assert_eq!(
|
assert_matches!(
|
||||||
machine
|
machine.should_share_key(&bob_device, &inbound).await,
|
||||||
.should_share_key(&bob_device, &inbound)
|
Err(KeyForwardDecision::OutboundSessionNotShared)
|
||||||
.await
|
|
||||||
.expect_err("Should not share with other unless shared."),
|
|
||||||
KeyforwardDecision::OutboundSessionNotShared
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// We now share the session, since it was shared before.
|
// 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());
|
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
|
// But we don't share some other session that doesn't match our outbound
|
||||||
|
@ -1247,12 +1245,21 @@ mod test {
|
||||||
let (_, other_inbound) =
|
let (_, other_inbound) =
|
||||||
account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_matches!(
|
||||||
machine
|
machine.should_share_key(&bob_device, &other_inbound).await,
|
||||||
.should_share_key(&bob_device, &other_inbound)
|
Err(KeyForwardDecision::MissingOutboundSession)
|
||||||
.await
|
);
|
||||||
.expect_err("Should not share with other unless shared."),
|
|
||||||
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_sessions(&[alice_session]).await.unwrap();
|
||||||
alice_machine.store.save_devices(&[bob_device]).await.unwrap();
|
alice_machine.store.save_devices(&[bob_device]).await.unwrap();
|
||||||
bob_machine.store.save_sessions(&[bob_session]).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) =
|
let (group_session, inbound_group_session) =
|
||||||
bob_account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
bob_account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
||||||
|
@ -1298,7 +1305,11 @@ mod test {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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.
|
// Put the outbound session into bobs store.
|
||||||
bob_machine.outbound_group_sessions.insert(group_session.clone());
|
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.
|
// Populate our stores with Olm sessions and a Megolm session.
|
||||||
|
|
||||||
alice_machine.store.save_devices(&[bob_device]).await.unwrap();
|
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) =
|
let (group_session, inbound_group_session) =
|
||||||
bob_account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
bob_account.create_group_session_pair_with_defaults(&room_id()).await.unwrap();
|
||||||
|
@ -1500,7 +1511,11 @@ mod test {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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.
|
// Put the outbound session into bobs store.
|
||||||
bob_machine.outbound_group_sessions.insert(group_session.clone());
|
bob_machine.outbound_group_sessions.insert(group_session.clone());
|
||||||
|
|
|
@ -41,7 +41,7 @@ use crate::{
|
||||||
|
|
||||||
/// An error describing why a key share request won't be honored.
|
/// An error describing why a key share request won't be honored.
|
||||||
#[derive(Debug, Clone, Error, PartialEq)]
|
#[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
|
/// 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.
|
/// sessions that we know the requesting device already was supposed to get.
|
||||||
#[error("can't find an active outbound group session")]
|
#[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.
|
/// The key request is from a device we own, yet we don't trust it.
|
||||||
#[error("requesting device isn't trusted")]
|
#[error("requesting device isn't trusted")]
|
||||||
UntrustedDevice,
|
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.
|
/// A struct describing an outgoing key request.
|
||||||
|
|
|
@ -28,7 +28,7 @@ mod outbound;
|
||||||
|
|
||||||
pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession};
|
pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession};
|
||||||
pub use outbound::{
|
pub use outbound::{
|
||||||
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareState,
|
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo, ShareState,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The private session key of a group session.
|
/// The private session key of a group session.
|
||||||
|
|
|
@ -43,8 +43,7 @@ use ruma::{
|
||||||
room_key::RoomKeyToDeviceEventContent,
|
room_key::RoomKeyToDeviceEventContent,
|
||||||
AnyMessageEventContent, AnyToDeviceEventContent, EventContent,
|
AnyMessageEventContent, AnyToDeviceEventContent, EventContent,
|
||||||
},
|
},
|
||||||
to_device::DeviceIdOrAllDevices,
|
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
|
||||||
DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -54,13 +53,15 @@ use super::{
|
||||||
super::{deserialize_instant, serialize_instant},
|
super::{deserialize_instant, serialize_instant},
|
||||||
GroupSessionKey,
|
GroupSessionKey,
|
||||||
};
|
};
|
||||||
use crate::ToDeviceRequest;
|
use crate::{Device, ToDeviceRequest};
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ShareState {
|
pub enum ShareState {
|
||||||
NotShared,
|
NotShared,
|
||||||
|
SharedButChangedSenderKey,
|
||||||
Shared(u32),
|
Shared(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +126,23 @@ pub struct OutboundGroupSession {
|
||||||
shared: Arc<AtomicBool>,
|
shared: Arc<AtomicBool>,
|
||||||
invalidated: Arc<AtomicBool>,
|
invalidated: Arc<AtomicBool>,
|
||||||
settings: Arc<EncryptionSettings>,
|
settings: Arc<EncryptionSettings>,
|
||||||
pub(crate) shared_with_set: Arc<DashMap<UserId, DashMap<DeviceIdBox, u32>>>,
|
pub(crate) shared_with_set: Arc<DashMap<UserId, DashMap<DeviceIdBox, ShareInfo>>>,
|
||||||
to_share_with_set: Arc<DashMap<Uuid, (Arc<ToDeviceRequest>, u32)>>,
|
to_share_with_set: Arc<DashMap<Uuid, (Arc<ToDeviceRequest>, 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<UserId, BTreeMap<DeviceIdBox, ShareInfo>>;
|
||||||
|
|
||||||
|
/// 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 {
|
impl OutboundGroupSession {
|
||||||
|
@ -174,9 +190,9 @@ impl OutboundGroupSession {
|
||||||
&self,
|
&self,
|
||||||
request_id: Uuid,
|
request_id: Uuid,
|
||||||
request: Arc<ToDeviceRequest>,
|
request: Arc<ToDeviceRequest>,
|
||||||
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.
|
/// 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
|
/// This removes the request from the queue and marks the set of
|
||||||
/// users/devices that received the session.
|
/// users/devices that received the session.
|
||||||
pub fn mark_request_as_sent(&self, request_id: &Uuid) {
|
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!(
|
trace!(
|
||||||
request_id = request_id.to_string().as_str(),
|
request_id = request_id.to_string().as_str(),
|
||||||
"Marking to-device request carrying a room key as sent"
|
"Marking to-device request carrying a room key as sent"
|
||||||
);
|
);
|
||||||
|
|
||||||
let user_pairs = r.0.messages.iter().map(|(u, v)| {
|
for (user_id, info) in r.into_iter() {
|
||||||
(
|
self.shared_with_set.entry(user_id).or_insert_with(DashMap::new).extend(info)
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.to_share_with_set.is_empty() {
|
if self.to_share_with_set.is_empty() {
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -368,36 +371,39 @@ impl OutboundGroupSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Has or will the session be shared with the given user/device pair.
|
/// 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.
|
// Check if we shared the session.
|
||||||
let shared_state = self
|
let shared_state = self.shared_with_set.get(device.user_id()).and_then(|d| {
|
||||||
.shared_with_set
|
d.get(device.device_id()).map(|s| {
|
||||||
.get(user_id)
|
if Some(&s.sender_key) == device.get_key(DeviceKeyAlgorithm::Curve25519) {
|
||||||
.and_then(|d| d.get(device_id).map(|m| ShareState::Shared(*m.value())));
|
ShareState::Shared(s.message_index)
|
||||||
|
} else {
|
||||||
|
ShareState::SharedButChangedSenderKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(state) = shared_state {
|
if let Some(state) = shared_state {
|
||||||
state
|
state
|
||||||
} else {
|
} else {
|
||||||
// If we haven't shared the session, check if we're going to share
|
// If we haven't shared the session, check if we're going to share
|
||||||
// the session.
|
// the session.
|
||||||
let device_id = DeviceIdOrAllDevices::DeviceId(device_id.into());
|
|
||||||
|
|
||||||
// Find the first request that contains the given user id and
|
// Find the first request that contains the given user id and
|
||||||
// device id.
|
// device id.
|
||||||
let shared = self.to_share_with_set.iter().find_map(|item| {
|
let shared = self.to_share_with_set.iter().find_map(|item| {
|
||||||
let request = &item.value().0;
|
let share_info = &item.value().1;
|
||||||
let message_index = item.value().1;
|
|
||||||
|
|
||||||
if request
|
share_info.get(device.user_id()).and_then(|d| {
|
||||||
.messages
|
d.get(device.device_id()).map(|info| {
|
||||||
.get(user_id)
|
if Some(&info.sender_key) == device.get_key(DeviceKeyAlgorithm::Curve25519)
|
||||||
.map(|e| e.contains_key(&device_id))
|
{
|
||||||
.unwrap_or(false)
|
ShareState::Shared(info.message_index)
|
||||||
{
|
} else {
|
||||||
Some(ShareState::Shared(message_index))
|
ShareState::SharedButChangedSenderKey
|
||||||
} else {
|
}
|
||||||
None
|
})
|
||||||
}
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
shared.unwrap_or(ShareState::NotShared)
|
shared.unwrap_or(ShareState::NotShared)
|
||||||
|
@ -406,11 +412,11 @@ impl OutboundGroupSession {
|
||||||
|
|
||||||
/// Mark that the session was shared with the given user/device pair.
|
/// Mark that the session was shared with the given user/device pair.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn mark_shared_with(&self, user_id: &UserId, device_id: &DeviceId) {
|
pub fn mark_shared_with(&self, user_id: &UserId, device_id: &DeviceId, sender_key: &str) {
|
||||||
self.shared_with_set
|
self.shared_with_set.entry(user_id.to_owned()).or_insert_with(DashMap::new).insert(
|
||||||
.entry(user_id.to_owned())
|
device_id.to_owned(),
|
||||||
.or_insert_with(DashMap::new)
|
ShareInfo { sender_key: sender_key.to_owned(), message_index: 0 },
|
||||||
.insert(device_id.to_owned(), 0);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the list of requests that need to be sent out for this session to be
|
/// Get the list of requests that need to be sent out for this session to be
|
||||||
|
@ -498,8 +504,7 @@ impl OutboundGroupSession {
|
||||||
.map(|u| {
|
.map(|u| {
|
||||||
(
|
(
|
||||||
u.key().clone(),
|
u.key().clone(),
|
||||||
#[allow(clippy::map_clone)]
|
u.value().iter().map(|d| (d.key().clone(), d.value().clone())).collect(),
|
||||||
u.value().iter().map(|d| (d.key().clone(), *d.value())).collect(),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -555,9 +560,9 @@ pub struct PickledOutboundGroupSession {
|
||||||
/// Has the session been invalidated.
|
/// Has the session been invalidated.
|
||||||
pub invalidated: bool,
|
pub invalidated: bool,
|
||||||
/// The set of users the session has been already shared with.
|
/// The set of users the session has been already shared with.
|
||||||
pub shared_with_set: BTreeMap<UserId, BTreeMap<DeviceIdBox, u32>>,
|
pub shared_with_set: BTreeMap<UserId, BTreeMap<DeviceIdBox, ShareInfo>>,
|
||||||
/// Requests that need to be sent out to share the session.
|
/// Requests that need to be sent out to share the session.
|
||||||
pub requests: BTreeMap<Uuid, (Arc<ToDeviceRequest>, u32)>,
|
pub requests: BTreeMap<Uuid, (Arc<ToDeviceRequest>, ShareInfoSet)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub(crate) use account::{Account, OlmDecryptionInfo, SessionType};
|
||||||
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
|
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
|
||||||
pub use group_sessions::{
|
pub use group_sessions::{
|
||||||
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
|
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
|
||||||
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession,
|
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, ShareInfo,
|
||||||
};
|
};
|
||||||
pub(crate) use group_sessions::{GroupSessionKey, ShareState};
|
pub(crate) use group_sessions::{GroupSessionKey, ShareState};
|
||||||
use matrix_sdk_common::instant::{Duration, Instant};
|
use matrix_sdk_common::instant::{Duration, Instant};
|
||||||
|
|
|
@ -33,7 +33,7 @@ use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{EventError, MegolmResult, OlmResult},
|
error::{EventError, MegolmResult, OlmResult},
|
||||||
olm::{Account, InboundGroupSession, OutboundGroupSession, Session, ShareState},
|
olm::{Account, InboundGroupSession, OutboundGroupSession, Session, ShareInfo, ShareState},
|
||||||
store::{Changes, Result as StoreResult, Store},
|
store::{Changes, Result as StoreResult, Store},
|
||||||
Device, EncryptionSettings, OlmError, ToDeviceRequest,
|
Device, EncryptionSettings, OlmError, ToDeviceRequest,
|
||||||
};
|
};
|
||||||
|
@ -226,21 +226,43 @@ impl GroupSessionManager {
|
||||||
async fn encrypt_session_for(
|
async fn encrypt_session_for(
|
||||||
content: AnyToDeviceEventContent,
|
content: AnyToDeviceEventContent,
|
||||||
devices: Vec<Device>,
|
devices: Vec<Device>,
|
||||||
) -> OlmResult<(Uuid, ToDeviceRequest, Vec<Session>)> {
|
message_index: u32,
|
||||||
|
) -> OlmResult<(
|
||||||
|
Uuid,
|
||||||
|
ToDeviceRequest,
|
||||||
|
BTreeMap<UserId, BTreeMap<DeviceIdBox, ShareInfo>>,
|
||||||
|
Vec<Session>,
|
||||||
|
)> {
|
||||||
let mut messages = BTreeMap::new();
|
let mut messages = BTreeMap::new();
|
||||||
let mut changed_sessions = Vec::new();
|
let mut changed_sessions = Vec::new();
|
||||||
|
let mut share_infos = BTreeMap::new();
|
||||||
|
|
||||||
let encrypt = |device: Device, content: AnyToDeviceEventContent| async move {
|
let encrypt = |device: Device, content: AnyToDeviceEventContent| async move {
|
||||||
let mut message = BTreeMap::new();
|
let mut message = BTreeMap::new();
|
||||||
|
let mut share_infos = BTreeMap::new();
|
||||||
|
|
||||||
let encrypted = device.encrypt(content.clone()).await;
|
let encrypted = device.encrypt(content.clone()).await;
|
||||||
|
|
||||||
let used_session = match encrypted {
|
let used_session = match encrypted {
|
||||||
Ok((session, encrypted)) => {
|
Ok((session, encrypted)) => {
|
||||||
message.entry(device.user_id().clone()).or_insert_with(BTreeMap::new).insert(
|
message
|
||||||
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
|
.entry(device.user_id().to_owned())
|
||||||
Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)),
|
.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)
|
Some(session)
|
||||||
}
|
}
|
||||||
// TODO we'll want to create m.room_key.withheld here.
|
// TODO we'll want to create m.room_key.withheld here.
|
||||||
|
@ -249,7 +271,7 @@ impl GroupSessionManager {
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((used_session, message))
|
Ok((used_session, share_infos, message))
|
||||||
};
|
};
|
||||||
|
|
||||||
let tasks: Vec<_> =
|
let tasks: Vec<_> =
|
||||||
|
@ -258,7 +280,7 @@ impl GroupSessionManager {
|
||||||
let results = join_all(tasks).await;
|
let results = join_all(tasks).await;
|
||||||
|
|
||||||
for result in results {
|
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 {
|
if let Some(session) = used_session {
|
||||||
changed_sessions.push(session);
|
changed_sessions.push(session);
|
||||||
|
@ -267,6 +289,10 @@ impl GroupSessionManager {
|
||||||
for (user, device_messages) in message.into_iter() {
|
for (user, device_messages) in message.into_iter() {
|
||||||
messages.entry(user).or_insert_with(BTreeMap::new).extend(device_messages);
|
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();
|
let id = Uuid::new_v4();
|
||||||
|
@ -280,7 +306,7 @@ impl GroupSessionManager {
|
||||||
"Created a to-device request carrying a room_key"
|
"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
|
/// Given a list of user and an outbound session, return the list of users
|
||||||
|
@ -380,11 +406,11 @@ impl GroupSessionManager {
|
||||||
message_index: u32,
|
message_index: u32,
|
||||||
being_shared: Arc<DashMap<Uuid, OutboundGroupSession>>,
|
being_shared: Arc<DashMap<Uuid, OutboundGroupSession>>,
|
||||||
) -> OlmResult<Vec<Session>> {
|
) -> OlmResult<Vec<Session>> {
|
||||||
let (id, request, used_sessions) =
|
let (id, request, share_infos, used_sessions) =
|
||||||
Self::encrypt_session_for(content.clone(), chunk).await?;
|
Self::encrypt_session_for(content.clone(), chunk, message_index).await?;
|
||||||
|
|
||||||
if !request.messages.is_empty() {
|
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());
|
being_shared.insert(id, outbound.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,12 +479,8 @@ impl GroupSessionManager {
|
||||||
let devices: Vec<Device> = devices
|
let devices: Vec<Device> = devices
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, d)| {
|
.map(|(_, d)| {
|
||||||
d.into_iter().filter(|d| {
|
d.into_iter()
|
||||||
matches!(
|
.filter(|d| matches!(outbound.is_shared_with(d), ShareState::NotShared))
|
||||||
outbound.is_shared_with(d.user_id(), d.device_id()),
|
|
||||||
ShareState::NotShared
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
convert::TryFrom,
|
convert::{TryFrom, TryInto},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,7 @@ use sled::{
|
||||||
transaction::{ConflictableTransactionError, TransactionError},
|
transaction::{ConflictableTransactionError, TransactionError},
|
||||||
Config, Db, Transactional, Tree,
|
Config, Db, Transactional, Tree,
|
||||||
};
|
};
|
||||||
|
use tracing::trace;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -46,6 +47,7 @@ use crate::{
|
||||||
/// 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
|
||||||
/// panic once we try to pickle a Signing object.
|
/// panic once we try to pickle a Signing object.
|
||||||
const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456";
|
const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456";
|
||||||
|
const DATABASE_VERSION: u8 = 1;
|
||||||
|
|
||||||
trait EncodeKey {
|
trait EncodeKey {
|
||||||
const SEPARATOR: u8 = 0xff;
|
const SEPARATOR: u8 = 0xff;
|
||||||
|
@ -204,12 +206,43 @@ impl SledStore {
|
||||||
self.account_info.read().unwrap().clone()
|
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>());
|
||||||
|
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<PathBuf>, passphrase: Option<&str>) -> Result<Self> {
|
fn open_helper(db: Db, path: Option<PathBuf>, passphrase: Option<&str>) -> Result<Self> {
|
||||||
|
Self::upgrade_databse(&db)?;
|
||||||
let account = db.open_tree("account")?;
|
let account = db.open_tree("account")?;
|
||||||
let private_identity = db.open_tree("private_identity")?;
|
let private_identity = db.open_tree("private_identity")?;
|
||||||
|
|
||||||
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 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")?;
|
||||||
|
|
Loading…
Reference in a new issue