Merge branch 'e2ee-fed' into 'master'

Initial E2EE over federation support

See merge request famedly/conduit!135
This commit is contained in:
Timo Kösters 2021-07-21 08:41:00 +00:00
commit f43fd6a475
8 changed files with 247 additions and 77 deletions

View file

@ -38,8 +38,8 @@ are not aware of such a bug today, but we would like to do more testing.
There are still a few important features missing: There are still a few important features missing:
- Database stability (currently you might have to do manual upgrades or even wipe the db for new versions) - Database stability (currently you might have to do manual upgrades or even wipe the db for new versions)
- End-to-end encrypted chats over federation - Edge cases for end-to-end encryption over federation
- Typing, presence, read receipts etc. over federation - Typing and presence over federation
- Lots of testing - Lots of testing
Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit/-/milestones/3). Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit/-/milestones/3).

View file

@ -1,19 +1,23 @@
use super::SESSION_ID_LENGTH; use super::SESSION_ID_LENGTH;
use crate::{database::DatabaseGuard, utils, ConduitResult, Database, Error, Result, Ruma}; use crate::{database::DatabaseGuard, utils, ConduitResult, Database, Error, Result, Ruma};
use ruma::{ use ruma::{
api::client::{ api::{
error::ErrorKind, client::{
r0::{ error::ErrorKind,
keys::{ r0::{
claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures, keys::{
upload_signing_keys, claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures,
upload_signing_keys,
},
uiaa::{AuthFlow, UiaaInfo},
}, },
uiaa::{AuthFlow, UiaaInfo},
}, },
federation,
}, },
encryption::UnsignedDeviceInfo, encryption::UnsignedDeviceInfo,
DeviceId, DeviceKeyAlgorithm, UserId, DeviceId, DeviceKeyAlgorithm, UserId,
}; };
use serde_json::json;
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@ -84,7 +88,8 @@ pub async fn get_keys_route(
&body.device_keys, &body.device_keys,
|u| u == sender_user, |u| u == sender_user,
&db, &db,
)?; )
.await?;
Ok(response.into()) Ok(response.into())
} }
@ -98,7 +103,7 @@ pub async fn claim_keys_route(
db: DatabaseGuard, db: DatabaseGuard,
body: Ruma<claim_keys::Request>, body: Ruma<claim_keys::Request>,
) -> ConduitResult<claim_keys::Response> { ) -> ConduitResult<claim_keys::Response> {
let response = claim_keys_helper(&body.one_time_keys, &db)?; let response = claim_keys_helper(&body.one_time_keys, &db).await?;
db.flush().await?; db.flush().await?;
@ -278,7 +283,7 @@ pub async fn get_key_changes_route(
.into()) .into())
} }
pub fn get_keys_helper<F: Fn(&UserId) -> bool>( pub async fn get_keys_helper<F: Fn(&UserId) -> bool>(
sender_user: Option<&UserId>, sender_user: Option<&UserId>,
device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>, device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>,
allowed_signatures: F, allowed_signatures: F,
@ -289,7 +294,17 @@ pub fn get_keys_helper<F: Fn(&UserId) -> bool>(
let mut user_signing_keys = BTreeMap::new(); let mut user_signing_keys = BTreeMap::new();
let mut device_keys = BTreeMap::new(); let mut device_keys = BTreeMap::new();
let mut get_over_federation = BTreeMap::new();
for (user_id, device_ids) in device_keys_input { for (user_id, device_ids) in device_keys_input {
if user_id.server_name() != db.globals.server_name() {
get_over_federation
.entry(user_id.server_name())
.or_insert_with(Vec::new)
.push((user_id, device_ids));
continue;
}
if device_ids.is_empty() { if device_ids.is_empty() {
let mut container = BTreeMap::new(); let mut container = BTreeMap::new();
for device_id in db.users.all_device_ids(user_id) { for device_id in db.users.all_device_ids(user_id) {
@ -347,21 +362,60 @@ pub fn get_keys_helper<F: Fn(&UserId) -> bool>(
} }
} }
let mut failures = BTreeMap::new();
for (server, vec) in get_over_federation {
let mut device_keys_input_fed = BTreeMap::new();
for (user_id, keys) in vec {
device_keys_input_fed.insert(user_id.clone(), keys.clone());
}
match db
.sending
.send_federation_request(
&db.globals,
server,
federation::keys::get_keys::v1::Request {
device_keys: device_keys_input_fed,
},
)
.await
{
Ok(response) => {
master_keys.extend(response.master_keys);
self_signing_keys.extend(response.self_signing_keys);
device_keys.extend(response.device_keys);
}
Err(_e) => {
failures.insert(server.to_string(), json!({}));
}
}
}
Ok(get_keys::Response { Ok(get_keys::Response {
master_keys, master_keys,
self_signing_keys, self_signing_keys,
user_signing_keys, user_signing_keys,
device_keys, device_keys,
failures: BTreeMap::new(), failures,
}) })
} }
pub fn claim_keys_helper( pub async fn claim_keys_helper(
one_time_keys_input: &BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>, one_time_keys_input: &BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>,
db: &Database, db: &Database,
) -> Result<claim_keys::Response> { ) -> Result<claim_keys::Response> {
let mut one_time_keys = BTreeMap::new(); let mut one_time_keys = BTreeMap::new();
let mut get_over_federation = BTreeMap::new();
for (user_id, map) in one_time_keys_input { for (user_id, map) in one_time_keys_input {
if user_id.server_name() != db.globals.server_name() {
get_over_federation
.entry(user_id.server_name())
.or_insert_with(Vec::new)
.push((user_id, map));
}
let mut container = BTreeMap::new(); let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map { for (device_id, key_algorithm) in map {
if let Some(one_time_keys) = if let Some(one_time_keys) =
@ -376,6 +430,26 @@ pub fn claim_keys_helper(
one_time_keys.insert(user_id.clone(), container); one_time_keys.insert(user_id.clone(), container);
} }
for (server, vec) in get_over_federation {
let mut one_time_keys_input_fed = BTreeMap::new();
for (user_id, keys) in vec {
one_time_keys_input_fed.insert(user_id.clone(), keys.clone());
}
// Ignore failures
let keys = db
.sending
.send_federation_request(
&db.globals,
server,
federation::keys::claim_keys::v1::Request {
one_time_keys: one_time_keys_input_fed,
},
)
.await?;
one_time_keys.extend(keys.one_time_keys);
}
Ok(claim_keys::Response { Ok(claim_keys::Response {
failures: BTreeMap::new(), failures: BTreeMap::new(),
one_time_keys, one_time_keys,

View file

@ -836,7 +836,7 @@ pub async fn invite_helper<'a>(
is_direct: Some(is_direct), is_direct: Some(is_direct),
membership: MembershipState::Invite, membership: MembershipState::Invite,
third_party_invite: None, third_party_invite: None,
blurhash: db.users.blurhash(&sender_user)?, blurhash: None,
}) })
.expect("member event is valid value"); .expect("member event is valid value");
@ -1012,7 +1012,7 @@ pub async fn invite_helper<'a>(
avatar_url: db.users.avatar_url(&user_id)?, avatar_url: db.users.avatar_url(&user_id)?,
is_direct: Some(is_direct), is_direct: Some(is_direct),
third_party_invite: None, third_party_invite: None,
blurhash: db.users.blurhash(&sender_user)?, blurhash: db.users.blurhash(&user_id)?,
}) })
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
unsigned: None, unsigned: None,

View file

@ -1,10 +1,13 @@
use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma}; use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma};
use ruma::{ use ruma::{
api::client::{ api::{
error::ErrorKind, client::{
r0::profile::{ error::ErrorKind,
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name, r0::profile::{
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
},
}, },
federation::{self, query::get_profile_information::v1::ProfileField},
}, },
events::EventType, events::EventType,
serde::Raw, serde::Raw,
@ -120,6 +123,25 @@ pub async fn get_displayname_route(
db: DatabaseGuard, db: DatabaseGuard,
body: Ruma<get_display_name::Request<'_>>, body: Ruma<get_display_name::Request<'_>>,
) -> ConduitResult<get_display_name::Response> { ) -> ConduitResult<get_display_name::Response> {
if body.user_id.server_name() != db.globals.server_name() {
let response = db
.sending
.send_federation_request(
&db.globals,
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: &body.user_id,
field: Some(&ProfileField::DisplayName),
},
)
.await?;
return Ok(get_display_name::Response {
displayname: response.displayname,
}
.into());
}
Ok(get_display_name::Response { Ok(get_display_name::Response {
displayname: db.users.displayname(&body.user_id)?, displayname: db.users.displayname(&body.user_id)?,
} }
@ -234,6 +256,26 @@ pub async fn get_avatar_url_route(
db: DatabaseGuard, db: DatabaseGuard,
body: Ruma<get_avatar_url::Request<'_>>, body: Ruma<get_avatar_url::Request<'_>>,
) -> ConduitResult<get_avatar_url::Response> { ) -> ConduitResult<get_avatar_url::Response> {
if body.user_id.server_name() != db.globals.server_name() {
let response = db
.sending
.send_federation_request(
&db.globals,
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: &body.user_id,
field: Some(&ProfileField::AvatarUrl),
},
)
.await?;
return Ok(get_avatar_url::Response {
avatar_url: response.avatar_url,
blurhash: response.blurhash,
}
.into());
}
Ok(get_avatar_url::Response { Ok(get_avatar_url::Response {
avatar_url: db.users.avatar_url(&body.user_id)?, avatar_url: db.users.avatar_url(&body.user_id)?,
blurhash: db.users.blurhash(&body.user_id)?, blurhash: db.users.blurhash(&body.user_id)?,
@ -250,6 +292,27 @@ pub async fn get_profile_route(
db: DatabaseGuard, db: DatabaseGuard,
body: Ruma<get_profile::Request<'_>>, body: Ruma<get_profile::Request<'_>>,
) -> ConduitResult<get_profile::Response> { ) -> ConduitResult<get_profile::Response> {
if body.user_id.server_name() != db.globals.server_name() {
let response = db
.sending
.send_federation_request(
&db.globals,
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: &body.user_id,
field: None,
},
)
.await?;
return Ok(get_profile::Response {
displayname: response.displayname,
avatar_url: response.avatar_url,
blurhash: response.blurhash,
}
.into());
}
if !db.users.exists(&body.user_id)? { if !db.users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist // Return 404 if this user doesn't exist
return Err(Error::BadRequest( return Err(Error::BadRequest(

View file

@ -421,7 +421,10 @@ async fn sync_helper(
let send_member_count = state_events let send_member_count = state_events
.iter() .iter()
.any(|event| event.kind == EventType::RoomMember); .any(|event| event.kind == EventType::RoomMember)
|| timeline_pdus.iter().any(|(_, event)| {
event.state_key.is_some() && event.kind == EventType::RoomMember
});
if encrypted_room { if encrypted_room {
for (user_id, current_member) in db for (user_id, current_member) in db

View file

@ -1,6 +1,12 @@
use std::collections::BTreeMap;
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma}; use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
use ruma::{ use ruma::{
api::client::{error::ErrorKind, r0::to_device::send_event_to_device}, api::{
client::{error::ErrorKind, r0::to_device::send_event_to_device},
federation::{self, transactions::edu::DirectDeviceContent},
},
events::EventType,
to_device::DeviceIdOrAllDevices, to_device::DeviceIdOrAllDevices,
}; };
@ -33,6 +39,28 @@ pub async fn send_event_to_device_route(
for (target_user_id, map) in &body.messages { for (target_user_id, map) in &body.messages {
for (target_device_id_maybe, event) in map { for (target_device_id_maybe, event) in map {
if target_user_id.server_name() != db.globals.server_name() {
let mut map = BTreeMap::new();
map.insert(target_device_id_maybe.clone(), event.clone());
let mut messages = BTreeMap::new();
messages.insert(target_user_id.clone(), map);
db.sending.send_reliable_edu(
target_user_id.server_name(),
&serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(
DirectDeviceContent {
sender: sender_user.clone(),
ev_type: EventType::from(&body.event_type),
message_id: body.txn_id.clone(),
messages,
},
))
.expect("DirectToDevice EDU can be serialized"),
)?;
continue;
}
match target_device_id_maybe { match target_device_id_maybe {
DeviceIdOrAllDevices::DeviceId(target_device_id) => db.users.add_to_device_event( DeviceIdOrAllDevices::DeviceId(target_device_id) => db.users.add_to_device_event(
sender_user, sender_user,

View file

@ -164,8 +164,8 @@ impl Sending {
// Find events that have been added since starting the last request // Find events that have been added since starting the last request
let new_events = guard.sending.servernamepduids let new_events = guard.sending.servernamepduids
.scan_prefix(prefix.clone()) .scan_prefix(prefix.clone())
.map(|(k, _)| { .filter_map(|(k, _)| {
SendingEventType::Pdu(k[prefix.len()..].to_vec()) Self::parse_servercurrentevent(&k).ok().map(|ev| (ev, k))
}) })
.take(30) .take(30)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -174,16 +174,9 @@ impl Sending {
if !new_events.is_empty() { if !new_events.is_empty() {
// Insert pdus we found // Insert pdus we found
for event in &new_events { for (_, key) in &new_events {
let mut current_key = prefix.clone(); guard.sending.servercurrentevents.insert(&key, &[]).unwrap();
match event { guard.sending.servernamepduids.remove(&key).unwrap();
SendingEventType::Pdu(b) |
SendingEventType::Edu(b) => {
current_key.extend_from_slice(&b);
guard.sending.servercurrentevents.insert(&current_key, &[]).unwrap();
guard.sending.servernamepduids.remove(&current_key).unwrap();
}
}
} }
drop(guard); drop(guard);
@ -191,7 +184,7 @@ impl Sending {
futures.push( futures.push(
Self::handle_events( Self::handle_events(
outgoing_kind.clone(), outgoing_kind.clone(),
new_events, new_events.into_iter().map(|(event, _)| event.1).collect(),
Arc::clone(&db), Arc::clone(&db),
) )
); );
@ -290,7 +283,8 @@ impl Sending {
if let OutgoingKind::Normal(server_name) = outgoing_kind { if let OutgoingKind::Normal(server_name) = outgoing_kind {
if let Ok((select_edus, last_count)) = Self::select_edus(db, server_name) { if let Ok((select_edus, last_count)) = Self::select_edus(db, server_name) {
events.extend_from_slice(&select_edus); events.extend(select_edus.into_iter().map(SendingEventType::Edu));
db.sending db.sending
.servername_educount .servername_educount
.insert(server_name.as_bytes(), &last_count.to_be_bytes())?; .insert(server_name.as_bytes(), &last_count.to_be_bytes())?;
@ -301,7 +295,7 @@ impl Sending {
Ok(Some(events)) Ok(Some(events))
} }
pub fn select_edus(db: &Database, server: &ServerName) -> Result<(Vec<SendingEventType>, u64)> { pub fn select_edus(db: &Database, server: &ServerName) -> Result<(Vec<Vec<u8>>, u64)> {
// u64: count of last edu // u64: count of last edu
let since = db let since = db
.sending .sending
@ -366,9 +360,7 @@ impl Sending {
} }
}; };
events.push(SendingEventType::Edu( events.push(serde_json::to_vec(&federation_event).expect("json can be serialized"));
serde_json::to_vec(&federation_event).expect("json can be serialized"),
));
if events.len() >= 20 { if events.len() >= 20 {
break 'outer; break 'outer;
@ -402,6 +394,18 @@ impl Sending {
Ok(()) Ok(())
} }
#[tracing::instrument(skip(self))]
pub fn send_reliable_edu(&self, server: &ServerName, serialized: &[u8]) -> Result<()> {
let mut key = server.as_bytes().to_vec();
key.push(0xff);
key.push(b'*');
key.extend_from_slice(serialized);
self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
Ok(())
}
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn send_pdu_appservice(&self, appservice_id: &str, pdu_id: &[u8]) -> Result<()> { pub fn send_pdu_appservice(&self, appservice_id: &str, pdu_id: &[u8]) -> Result<()> {
let mut key = b"+".to_vec(); let mut key = b"+".to_vec();

View file

@ -1027,42 +1027,39 @@ pub fn handle_incoming_pdu<'a>(
.map_err(|_| "Failed talking to db".to_owned())? .map_err(|_| "Failed talking to db".to_owned())?
.map(|shortstatehash| db.rooms.state_full_ids(shortstatehash).ok()) .map(|shortstatehash| db.rooms.state_full_ids(shortstatehash).ok())
.flatten(); .flatten();
if let Some(mut state) = state { if let Some(state) = state {
if db let mut state = fetch_and_handle_events(
.rooms db,
.get_pdu(prev_event) origin,
.ok() &state.into_iter().collect::<Vec<_>>(),
.flatten() &room_id,
.ok_or_else(|| "Could not find prev event, but we know the state.".to_owned())? pub_key_map,
.state_key )
.is_some() .await
{ .map_err(|_| "Failed to fetch state events locally".to_owned())?
state.insert(prev_event.clone()); .into_iter()
} .map(|pdu| {
state_at_incoming_event = Some( (
fetch_and_handle_events(
db,
origin,
&state.into_iter().collect::<Vec<_>>(),
&room_id,
pub_key_map,
)
.await
.map_err(|_| "Failed to fetch state events locally".to_owned())?
.into_iter()
.map(|pdu| {
( (
( pdu.kind.clone(),
pdu.kind.clone(), pdu.state_key
pdu.state_key .clone()
.clone() .expect("events from state_full_ids are state events"),
.expect("events from state_full_ids are state events"), ),
), pdu,
pdu, )
) })
}) .collect::<HashMap<_, _>>();
.collect(),
); let prev_pdu = db.rooms.get_pdu(prev_event).ok().flatten().ok_or_else(|| {
"Could not find prev event, but we know the state.".to_owned()
})?;
if let Some(state_key) = &prev_pdu.state_key {
state.insert((prev_pdu.kind.clone(), state_key.clone()), prev_pdu);
}
state_at_incoming_event = Some(state);
} }
// TODO: set incoming_auth_events? // TODO: set incoming_auth_events?
} }
@ -2461,7 +2458,8 @@ pub async fn get_keys_route(
&body.device_keys, &body.device_keys,
|u| Some(u.server_name()) == body.sender_servername.as_deref(), |u| Some(u.server_name()) == body.sender_servername.as_deref(),
&db, &db,
)?; )
.await?;
db.flush().await?; db.flush().await?;
@ -2486,7 +2484,7 @@ pub async fn claim_keys_route(
return Err(Error::bad_config("Federation is disabled.")); return Err(Error::bad_config("Federation is disabled."));
} }
let result = claim_keys_helper(&body.one_time_keys, &db)?; let result = claim_keys_helper(&body.one_time_keys, &db).await?;
db.flush().await?; db.flush().await?;