Refactor server_keys service/interface and related callsites

Signed-off-by: Jason Volk <jason@zemos.net>
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
Jason Volk 2024-10-11 18:57:59 +00:00 committed by strawberry
parent d82ea331cf
commit c0939c3e9a
30 changed files with 1025 additions and 1378 deletions

View file

@ -1,17 +1,16 @@
use std::{
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
net::IpAddr,
sync::Arc,
time::Instant,
};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{
debug, debug_error, debug_warn, err, error, info,
debug, debug_info, debug_warn, err, error, info, pdu,
pdu::{gen_event_id_canonical_json, PduBuilder},
trace, utils,
utils::{math::continue_exponential_backoff_secs, IterStream, ReadyExt},
utils::{IterStream, ReadyExt},
warn, Err, Error, PduEvent, Result,
};
use futures::{FutureExt, StreamExt};
@ -36,13 +35,10 @@ use ruma::{
},
StateEventType,
},
serde::Base64,
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
state_res, CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, ServerName, UserId,
};
use serde_json::value::RawValue as RawJsonValue;
use service::{appservice::RegistrationInfo, rooms::state::RoomMutexGuard, Services};
use tokio::sync::RwLock;
use crate::{client::full_user_deactivate, Ruma};
@ -670,20 +666,22 @@ pub async fn join_room_by_id_helper(
if local_join {
join_room_by_id_helper_local(services, sender_user, room_id, reason, servers, third_party_signed, state_lock)
.boxed()
.await
.await?;
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(services, sender_user, room_id, reason, servers, third_party_signed, state_lock)
.boxed()
.await
.await?;
}
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result<join_room_by_id::v3::Response> {
) -> Result {
info!("Joining {room_id} over federation.");
let (make_join_response, remote_server) = make_join_request(services, sender_user, room_id, servers).await?;
@ -751,43 +749,33 @@ async fn join_room_by_id_helper_remote(
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
ruma::signatures::hash_and_sign_event(
services.globals.server_name().as_str(),
services.globals.keypair(),
&mut join_event_stub,
&room_version_id,
)
.expect("event is valid, we just created it");
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = format!(
"${}",
ruma::signatures::reference_hash(&join_event_stub, &room_version_id)
.expect("ruma can calculate reference hashes")
);
let event_id = <&EventId>::try_from(event_id.as_str()).expect("ruma's reference hashes are valid event ids");
let event_id = pdu::gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned()));
join_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let mut join_event = join_event_stub;
info!("Asking {remote_server} for send_join in room {room_id}");
let send_join_request = federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
};
let send_join_response = services
.sending
.send_federation_request(
&remote_server,
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.to_owned(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
},
)
.send_federation_request(&remote_server, send_join_request)
.await?;
info!("send_join finished");
@ -805,7 +793,7 @@ async fn join_room_by_id_helper_remote(
// validate and send signatures
_ => {
if let Some(signed_raw) = &send_join_response.room_state.event {
info!(
debug_info!(
"There is a signed event. This room is probably using restricted joins. Adding signature to \
our event"
);
@ -862,25 +850,25 @@ async fn join_room_by_id_helper_remote(
.await;
info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(event_id, join_event.clone())
let parsed_join_pdu = PduEvent::from_id_val(&event_id, join_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
let mut state = HashMap::new();
let pub_key_map = RwLock::new(BTreeMap::new());
info!("Fetching join signing keys");
info!("Acquiring server signing keys for response events");
let resp_events = &send_join_response.room_state;
let resp_state = &resp_events.state;
let resp_auth = &resp_events.auth_chain;
services
.server_keys
.fetch_join_signing_keys(&send_join_response, &room_version_id, &pub_key_map)
.await?;
.acquire_events_pubkeys(resp_auth.iter().chain(resp_state.iter()))
.await;
info!("Going through send_join response room_state");
for result in send_join_response
.room_state
.state
.iter()
.map(|pdu| validate_and_add_event_id(services, pdu, &room_version_id, &pub_key_map))
{
let mut state = HashMap::new();
for result in send_join_response.room_state.state.iter().map(|pdu| {
services
.server_keys
.validate_and_add_event_id(pdu, &room_version_id)
}) {
let Ok((event_id, value)) = result.await else {
continue;
};
@ -902,12 +890,11 @@ async fn join_room_by_id_helper_remote(
}
info!("Going through send_join response auth_chain");
for result in send_join_response
.room_state
.auth_chain
.iter()
.map(|pdu| validate_and_add_event_id(services, pdu, &room_version_id, &pub_key_map))
{
for result in send_join_response.room_state.auth_chain.iter().map(|pdu| {
services
.server_keys
.validate_and_add_event_id(pdu, &room_version_id)
}) {
let Ok((event_id, value)) = result.await else {
continue;
};
@ -937,29 +924,22 @@ async fn join_room_by_id_helper_remote(
return Err!(Request(Forbidden("Auth check failed")));
}
info!("Saving state from send_join");
info!("Compressing state from send_join");
let compressed = state
.iter()
.stream()
.then(|(&k, id)| services.rooms.state_compressor.compress_state_event(k, id))
.collect()
.await;
debug!("Saving compressed state");
let (statehash_before_join, new, removed) = services
.rooms
.state_compressor
.save_state(
room_id,
Arc::new(
state
.into_iter()
.stream()
.then(|(k, id)| async move {
services
.rooms
.state_compressor
.compress_state_event(k, &id)
.await
})
.collect()
.await,
),
)
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
@ -1002,14 +982,14 @@ async fn join_room_by_id_helper_remote(
.state
.set_room_state(room_id, statehash_after_join, &state_lock);
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
Ok(())
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result<join_room_by_id::v3::Response> {
) -> Result {
debug!("We can join locally");
let join_rules_event_content = services
@ -1089,7 +1069,7 @@ async fn join_room_by_id_helper_local(
)
.await
{
Ok(_event_id) => return Ok(join_room_by_id::v3::Response::new(room_id.to_owned())),
Ok(_) => return Ok(()),
Err(e) => e,
};
@ -1159,24 +1139,15 @@ async fn join_room_by_id_helper_local(
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
ruma::signatures::hash_and_sign_event(
services.globals.server_name().as_str(),
services.globals.keypair(),
&mut join_event_stub,
&room_version_id,
)
.expect("event is valid, we just created it");
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = format!(
"${}",
ruma::signatures::reference_hash(&join_event_stub, &room_version_id)
.expect("ruma can calculate reference hashes")
);
let event_id = <&EventId>::try_from(event_id.as_str()).expect("ruma's reference hashes are valid event ids");
let event_id = pdu::gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned()));
join_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let join_event = join_event_stub;
@ -1187,7 +1158,7 @@ async fn join_room_by_id_helper_local(
&remote_server,
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
@ -1214,15 +1185,10 @@ async fn join_room_by_id_helper_local(
}
drop(state_lock);
let pub_key_map = RwLock::new(BTreeMap::new());
services
.server_keys
.fetch_required_signing_keys([&signed_value], &pub_key_map)
.await?;
services
.rooms
.event_handler
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true, &pub_key_map)
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
.await?;
} else {
return Err(error);
@ -1231,7 +1197,7 @@ async fn join_room_by_id_helper_local(
return Err(error);
}
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
Ok(())
}
async fn make_join_request(
@ -1301,62 +1267,6 @@ async fn make_join_request(
make_join_response_and_server
}
pub async fn validate_and_add_event_id(
services: &Services, pdu: &RawJsonValue, room_version: &RoomVersionId,
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
let mut value: CanonicalJsonObject = serde_json::from_str(pdu.get())
.map_err(|e| err!(BadServerResponse(debug_error!("Invalid PDU in server response: {e:?}"))))?;
let event_id = EventId::parse(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| async {
match services
.globals
.bad_event_ratelimiter
.write()
.expect("locked")
.entry(id)
{
Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
},
Entry::Occupied(mut e) => {
*e.get_mut() = (Instant::now(), e.get().1.saturating_add(1));
},
}
};
if let Some((time, tries)) = services
.globals
.bad_event_ratelimiter
.read()
.expect("locked")
.get(&event_id)
{
// Exponential backoff
const MIN: u64 = 60 * 5;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
return Err!(BadServerResponse("bad event {event_id:?}, still backing off"));
}
}
if let Err(e) = ruma::signatures::verify_event(&*pub_key_map.read().await, &value, room_version) {
debug_error!("Event {event_id} failed verification {pdu:#?}");
let e = Err!(BadServerResponse(debug_error!("Event {event_id} failed verification: {e:?}")));
back_off(event_id).await;
return e;
}
value.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned()));
Ok((event_id, value))
}
pub(crate) async fn invite_helper(
services: &Services, sender_user: &UserId, user_id: &UserId, room_id: &RoomId, reason: Option<String>,
is_direct: bool,
@ -1423,8 +1333,6 @@ pub(crate) async fn invite_helper(
)
.await?;
let pub_key_map = RwLock::new(BTreeMap::new());
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let Ok((event_id, value)) = gen_event_id_canonical_json(&response.event, &room_version_id) else {
@ -1452,15 +1360,10 @@ pub(crate) async fn invite_helper(
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?;
services
.server_keys
.fetch_required_signing_keys([&value], &pub_key_map)
.await?;
let pdu_id: Vec<u8> = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true, &pub_key_map)
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.await?
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
@ -1714,24 +1617,15 @@ async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &Room
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
ruma::signatures::hash_and_sign_event(
services.globals.server_name().as_str(),
services.globals.keypair(),
&mut leave_event_stub,
&room_version_id,
)
.expect("event is valid, we just created it");
services
.server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// Generate event id
let event_id = EventId::parse(format!(
"${}",
ruma::signatures::reference_hash(&leave_event_stub, &room_version_id)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
let event_id = pdu::gen_event_id(&leave_event_stub, &room_version_id)?;
// Add event_id back
leave_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned()));
leave_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let leave_event = leave_event_stub;