improvement: federation get_keys and optimize signingkey storage

- get encryption keys over federation
- optimize signing key storage
- rate limit parsing of bad events
- rate limit signature fetching
- dependency bumps
This commit is contained in:
Timo Kösters 2021-05-20 23:46:52 +02:00
parent ae41bc5067
commit 09157b2096
No known key found for this signature in database
GPG key ID: 24DA7517711A2BA4
18 changed files with 566 additions and 371 deletions

View file

@ -8,11 +8,11 @@ use ruma::{
set_room_account_data,
},
},
events::{custom::CustomEventContent, AnyBasicEventContent, BasicEvent},
events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
serde::Raw,
};
use serde::Deserialize;
use serde_json::value::RawValue as RawJsonValue;
use serde_json::{json, value::RawValue as RawJsonValue};
#[cfg(feature = "conduit_bin")]
use rocket::{get, put};
@ -28,7 +28,7 @@ pub async fn set_global_account_data_route(
) -> ConduitResult<set_global_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = serde_json::from_str(body.data.get())
let data = serde_json::from_str::<serde_json::Value>(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string();
@ -37,9 +37,10 @@ pub async fn set_global_account_data_route(
None,
sender_user,
event_type.clone().into(),
&BasicEvent {
content: CustomEventContent { event_type, data },
},
&json!({
"type": event_type,
"content": data,
}),
&db.globals,
)?;
@ -71,9 +72,10 @@ pub async fn set_room_account_data_route(
Some(&body.room_id),
sender_user,
event_type.clone().into(),
&BasicEvent {
content: CustomEventContent { event_type, data },
},
&json!({
"type": event_type,
"content": data,
}),
&db.globals,
)?;
@ -99,7 +101,7 @@ pub async fn get_global_account_data_route(
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?;
let account_data = serde_json::from_str::<ExtractEventContent>(event.get())
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
@ -130,7 +132,7 @@ pub async fn get_room_account_data_route(
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?;
let account_data = serde_json::from_str::<ExtractEventContent>(event.get())
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
@ -138,6 +140,11 @@ pub async fn get_room_account_data_route(
}
#[derive(Deserialize)]
struct ExtractEventContent {
content: Raw<AnyBasicEventContent>,
struct ExtractRoomEventContent {
content: Raw<AnyRoomAccountDataEventContent>,
}
#[derive(Deserialize)]
struct ExtractGlobalEventContent {
content: Raw<AnyGlobalAccountDataEventContent>,
}

View file

@ -1,5 +1,5 @@
use super::{State, SESSION_ID_LENGTH};
use crate::{utils, ConduitResult, Database, Error, Ruma};
use crate::{utils, ConduitResult, Database, Error, Result, Ruma};
use ruma::{
api::client::{
error::ErrorKind,
@ -12,6 +12,7 @@ use ruma::{
},
},
encryption::UnsignedDeviceInfo,
DeviceId, UserId,
};
use std::collections::{BTreeMap, HashSet};
@ -78,74 +79,14 @@ pub async fn get_keys_route(
) -> ConduitResult<get_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut master_keys = BTreeMap::new();
let mut self_signing_keys = BTreeMap::new();
let mut user_signing_keys = BTreeMap::new();
let mut device_keys = BTreeMap::new();
let response = get_keys_helper(
Some(sender_user),
&body.device_keys,
|u| u == sender_user,
&db,
)?;
for (user_id, device_ids) in &body.device_keys {
if device_ids.is_empty() {
let mut container = BTreeMap::new();
for device_id in db.users.all_device_ids(user_id) {
let device_id = device_id?;
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
let metadata = db
.users
.get_device_metadata(user_id, &device_id)?
.ok_or_else(|| {
Error::bad_database("all_device_keys contained nonexistent device.")
})?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id, keys);
}
}
device_keys.insert(user_id.clone(), container);
} else {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to get keys for nonexistent device.",
),
)?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id.clone(), keys);
}
device_keys.insert(user_id.clone(), container);
}
}
if let Some(master_key) = db.users.get_master_key(user_id, sender_user)? {
master_keys.insert(user_id.clone(), master_key);
}
if let Some(self_signing_key) = db.users.get_self_signing_key(user_id, sender_user)? {
self_signing_keys.insert(user_id.clone(), self_signing_key);
}
if user_id == sender_user {
if let Some(user_signing_key) = db.users.get_user_signing_key(sender_user)? {
user_signing_keys.insert(user_id.clone(), user_signing_key);
}
}
}
Ok(get_keys::Response {
master_keys,
self_signing_keys,
user_signing_keys,
device_keys,
failures: BTreeMap::new(),
}
.into())
Ok(response.into())
}
#[cfg_attr(
@ -356,3 +297,81 @@ pub async fn get_key_changes_route(
}
.into())
}
pub fn get_keys_helper<F: Fn(&UserId) -> bool>(
sender_user: Option<&UserId>,
device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>,
allowed_signatures: F,
db: &Database,
) -> Result<get_keys::Response> {
let mut master_keys = BTreeMap::new();
let mut self_signing_keys = BTreeMap::new();
let mut user_signing_keys = BTreeMap::new();
let mut device_keys = BTreeMap::new();
for (user_id, device_ids) in device_keys_input {
if device_ids.is_empty() {
let mut container = BTreeMap::new();
for device_id in db.users.all_device_ids(user_id) {
let device_id = device_id?;
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
let metadata = db
.users
.get_device_metadata(user_id, &device_id)?
.ok_or_else(|| {
Error::bad_database("all_device_keys contained nonexistent device.")
})?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id, keys);
}
}
device_keys.insert(user_id.clone(), container);
} else {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to get keys for nonexistent device.",
),
)?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id.clone(), keys);
}
device_keys.insert(user_id.clone(), container);
}
}
if let Some(master_key) = db.users.get_master_key(user_id, &allowed_signatures)? {
master_keys.insert(user_id.clone(), master_key);
}
if let Some(self_signing_key) = db
.users
.get_self_signing_key(user_id, &allowed_signatures)?
{
self_signing_keys.insert(user_id.clone(), self_signing_key);
}
if Some(user_id) == sender_user {
if let Some(user_signing_key) = db.users.get_user_signing_key(user_id)? {
user_signing_keys.insert(user_id.clone(), user_signing_key);
}
}
}
Ok(get_keys::Response {
master_keys,
self_signing_keys,
user_signing_keys,
device_keys,
failures: BTreeMap::new(),
})
}

View file

@ -4,7 +4,7 @@ use crate::{
pdu::{PduBuilder, PduEvent},
server_server, utils, ConduitResult, Database, Error, Result, Ruma,
};
use log::{error, warn};
use log::{debug, error, warn};
use member::{MemberEventContent, MembershipState};
use rocket::futures;
use ruma::{
@ -29,9 +29,10 @@ use ruma::{
uint, EventId, RoomId, RoomVersionId, ServerName, UserId,
};
use std::{
collections::{BTreeMap, HashSet},
collections::{btree_map::Entry, BTreeMap, HashSet},
convert::{TryFrom, TryInto},
sync::{Arc, RwLock},
time::{Duration, Instant},
};
#[cfg(feature = "conduit_bin")]
@ -703,6 +704,38 @@ async fn validate_and_add_event_id(
error!("{:?}: {:?}", pdu, e);
Error::BadServerResponse("Invalid PDU in server response")
})?;
let event_id = EventId::try_from(&*format!(
"${}",
ruma::signatures::reference_hash(&value, &room_version)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
let back_off = |id| match db.globals.bad_event_ratelimiter.write().unwrap().entry(id) {
Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
}
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
};
if let Some((time, tries)) = db
.globals
.bad_event_ratelimiter
.read()
.unwrap()
.get(&event_id)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
if time.elapsed() < min_elapsed_duration {
debug!("Backing off from {}", event_id);
return Err(Error::BadServerResponse("bad event, still backing off"));
}
}
server_server::fetch_required_signing_keys(&value, pub_key_map, db).await?;
if let Err(e) = ruma::signatures::verify_event(
@ -712,17 +745,11 @@ async fn validate_and_add_event_id(
&value,
room_version,
) {
warn!("Event failed verification: {}", e);
warn!("Event {} failed verification: {}", event_id, e);
back_off(event_id);
return Err(Error::BadServerResponse("Event failed verification."));
}
let event_id = EventId::try_from(&*format!(
"${}",
ruma::signatures::reference_hash(&value, &room_version)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
value.insert(
"event_id".to_owned(),
CanonicalJsonValue::String(event_id.as_str().to_owned()),

View file

@ -5,12 +5,14 @@ use ruma::{
error::ErrorKind,
r0::{read_marker::set_read_marker, receipt::create_receipt},
},
events::{AnyEphemeralRoomEvent, AnyEvent, EventType},
events::{AnyEphemeralRoomEvent, EventType},
receipt::ReceiptType,
MilliSecondsSinceUnixEpoch,
};
#[cfg(feature = "conduit_bin")]
use rocket::post;
use std::{collections::BTreeMap, time::SystemTime};
use std::collections::BTreeMap;
#[cfg_attr(
feature = "conduit_bin",
@ -27,7 +29,6 @@ pub async fn set_read_marker_route(
content: ruma::events::fully_read::FullyReadEventContent {
event_id: body.fully_read.clone(),
},
room_id: body.room_id.clone(),
};
db.account_data.update(
Some(&body.room_id),
@ -54,26 +55,23 @@ pub async fn set_read_marker_route(
user_receipts.insert(
sender_user.clone(),
ruma::events::receipt::Receipt {
ts: Some(SystemTime::now()),
ts: Some(MilliSecondsSinceUnixEpoch::now()),
},
);
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(
event.to_owned(),
ruma::events::receipt::Receipts {
read: Some(user_receipts),
},
);
receipt_content.insert(event.to_owned(), receipts);
db.rooms.edus.readreceipt_update(
&sender_user,
&body.room_id,
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)),
AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
}),
&db.globals,
)?;
}
@ -112,26 +110,22 @@ pub async fn create_receipt_route(
user_receipts.insert(
sender_user.clone(),
ruma::events::receipt::Receipt {
ts: Some(SystemTime::now()),
ts: Some(MilliSecondsSinceUnixEpoch::now()),
},
);
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(
body.event_id.to_owned(),
ruma::events::receipt::Receipts {
read: Some(user_receipts),
},
);
receipt_content.insert(body.event_id.to_owned(), receipts);
db.rooms.edus.readreceipt_update(
&sender_user,
&body.room_id,
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)),
AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
}),
&db.globals,
)?;

View file

@ -422,7 +422,7 @@ pub async fn sync_events_route(
}
let joined_room = sync_events::JoinedRoom {
account_data: sync_events::AccountData {
account_data: sync_events::RoomAccountData {
events: db
.account_data
.changes_since(Some(&room_id), &sender_user, since)?
@ -506,7 +506,7 @@ pub async fn sync_events_route(
left_rooms.insert(
room_id.clone(),
sync_events::LeftRoom {
account_data: sync_events::AccountData { events: Vec::new() },
account_data: sync_events::RoomAccountData { events: Vec::new() },
timeline: sync_events::Timeline {
limited: false,
prev_batch: Some(next_batch.clone()),
@ -577,7 +577,7 @@ pub async fn sync_events_route(
.map(|(_, v)| Raw::from(v))
.collect(),
},
account_data: sync_events::AccountData {
account_data: sync_events::GlobalAccountData {
events: db
.account_data
.changes_since(None, &sender_user, since)?