mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
refactor: Ruma upstreaming, half-baked edition
Co-authored-by: Jade Ellis <jade@ellis.link>
This commit is contained in:
@@ -6,6 +6,8 @@ use ruma::{
|
||||
api::federation::discovery::VerifyKey,
|
||||
};
|
||||
|
||||
use crate::server_keys::util::required_keys;
|
||||
|
||||
use super::{PubKeyMap, PubKeys, extract_key};
|
||||
|
||||
#[implement(super::Service)]
|
||||
@@ -14,8 +16,6 @@ pub async fn get_event_keys(
|
||||
object: &CanonicalJsonObject,
|
||||
version: &RoomVersionId,
|
||||
) -> Result<PubKeyMap> {
|
||||
use ruma::signatures::required_keys;
|
||||
|
||||
let required = match required_keys(object, version) {
|
||||
| Ok(required) => required,
|
||||
| Err(e) => {
|
||||
|
||||
@@ -12,9 +12,7 @@ pub(super) fn init(db: &Arc<Database>) -> Result<(Box<Ed25519KeyPair>, VerifyKey
|
||||
remove(db);
|
||||
})?;
|
||||
|
||||
let verify_key = VerifyKey {
|
||||
key: Base64::new(keypair.public_key().to_vec()),
|
||||
};
|
||||
let verify_key = VerifyKey::new(Base64::new(keypair.public_key().to_vec()));
|
||||
|
||||
let id = format!("ed25519:{}", keypair.version());
|
||||
let verify_keys: VerifyKeys = [(id.try_into()?, verify_key)].into();
|
||||
|
||||
@@ -4,6 +4,7 @@ mod keypair;
|
||||
mod request;
|
||||
mod sign;
|
||||
mod verify;
|
||||
mod util;
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
||||
|
||||
@@ -22,7 +23,7 @@ use ruma::{
|
||||
};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
use crate::{Dep, globals, sending};
|
||||
use crate::{Dep, globals, sending, server_keys::util::required_keys};
|
||||
|
||||
pub struct Service {
|
||||
keypair: Box<Ed25519KeyPair>,
|
||||
@@ -118,8 +119,6 @@ pub async fn required_keys_exist(
|
||||
object: &CanonicalJsonObject,
|
||||
version: &RoomVersionId,
|
||||
) -> bool {
|
||||
use ruma::signatures::required_keys;
|
||||
|
||||
trace!(?object, "Checking required keys exist");
|
||||
let Ok(required_keys) = required_keys(object, version) else {
|
||||
debug_error!("Failed to determine required keys");
|
||||
@@ -137,7 +136,7 @@ pub async fn required_keys_exist(
|
||||
#[implement(Service)]
|
||||
#[tracing::instrument(skip(self), level = "debug")]
|
||||
pub async fn verify_key_exists(&self, origin: &ServerName, key_id: &ServerSigningKeyId) -> bool {
|
||||
type KeysMap<'a> = BTreeMap<&'a ServerSigningKeyId, &'a RawJsonValue>;
|
||||
type KeysMap = BTreeMap<OwnedServerSigningKeyId, Box<RawJsonValue>>;
|
||||
|
||||
let Ok(keys) = self
|
||||
.db
|
||||
@@ -150,13 +149,13 @@ pub async fn verify_key_exists(&self, origin: &ServerName, key_id: &ServerSignin
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Ok(Some(verify_keys)) = keys.get_field::<KeysMap<'_>>("verify_keys") {
|
||||
if let Ok(Some(verify_keys)) = keys.get_field::<KeysMap>("verify_keys") {
|
||||
if verify_keys.contains_key(key_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(Some(old_verify_keys)) = keys.get_field::<KeysMap<'_>>("old_verify_keys") {
|
||||
if let Ok(Some(old_verify_keys)) = keys.get_field::<KeysMap>("old_verify_keys") {
|
||||
if old_verify_keys.contains_key(key_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,8 @@ where
|
||||
use get_remote_server_keys_batch::v2::Request;
|
||||
type RumaBatch = BTreeMap<OwnedServerName, BTreeMap<OwnedServerSigningKeyId, QueryCriteria>>;
|
||||
|
||||
let criteria = QueryCriteria {
|
||||
minimum_valid_until_ts: Some(self.minimum_valid_ts()),
|
||||
};
|
||||
let mut criteria = QueryCriteria::new();
|
||||
criteria.minimum_valid_until_ts = Some(self.minimum_valid_ts());
|
||||
|
||||
let mut server_keys = batch.fold(RumaBatch::new(), |mut batch, (server, key_ids)| {
|
||||
batch
|
||||
@@ -46,9 +45,7 @@ where
|
||||
.next_back()
|
||||
.cloned()
|
||||
{
|
||||
let request = Request {
|
||||
server_keys: server_keys.split_off(&batch),
|
||||
};
|
||||
let request = Request::new(server_keys.split_off(&batch));
|
||||
|
||||
debug!(
|
||||
?notary,
|
||||
@@ -61,7 +58,7 @@ where
|
||||
let response = self
|
||||
.services
|
||||
.sending
|
||||
.send_synapse_request(notary, request)
|
||||
.send_unauthenticated_request(notary, request)
|
||||
.await?
|
||||
.server_keys
|
||||
.into_iter()
|
||||
@@ -82,15 +79,12 @@ pub async fn notary_request(
|
||||
) -> Result<impl Iterator<Item = ServerSigningKeys> + Clone + Debug + Send + use<>> {
|
||||
use get_remote_server_keys::v2::Request;
|
||||
|
||||
let request = Request {
|
||||
server_name: target.into(),
|
||||
minimum_valid_until_ts: self.minimum_valid_ts(),
|
||||
};
|
||||
let request = Request::new(target.into(), self.minimum_valid_ts());
|
||||
|
||||
let response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(notary, request)
|
||||
.send_unauthenticated_request(notary, request)
|
||||
.await?
|
||||
.server_keys
|
||||
.into_iter()
|
||||
@@ -107,7 +101,7 @@ pub async fn server_request(&self, target: &ServerName) -> Result<ServerSigningK
|
||||
let server_signing_key = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(target, Request::new())
|
||||
.send_unauthenticated_request(target, Request::new())
|
||||
.await
|
||||
.map(|response| response.server_key)
|
||||
.and_then(|key| key.deserialize().map_err(Into::into))?;
|
||||
|
||||
@@ -18,5 +18,5 @@ pub fn hash_and_sign_event(
|
||||
use ruma::signatures::hash_and_sign_event;
|
||||
|
||||
let server_name = self.services.globals.server_name().as_str();
|
||||
hash_and_sign_event(server_name, self.keypair(), object, room_version).map_err(Into::into)
|
||||
hash_and_sign_event(server_name, self.keypair(), object, &room_version.rules().unwrap().redaction).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, OwnedServerSigningKeyId, RoomVersionId, UserId, canonical_json::JsonType, signatures::{Error, JsonError, ParseError}};
|
||||
|
||||
/// Whether the given event is an `m.room.member` invite that was created as the result of a
|
||||
/// third-party invite.
|
||||
///
|
||||
/// Returns an error if the object has not the expected format of an `m.room.member` event.
|
||||
pub(super) fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, Error> {
|
||||
let Some(CanonicalJsonValue::String(raw_type)) = object.get("type") else {
|
||||
return Err(JsonError::NotOfType { target: "type".to_owned(), of_type: JsonType::String }.into());
|
||||
};
|
||||
|
||||
if raw_type != "m.room.member" {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let Some(CanonicalJsonValue::Object(content)) = object.get("content") else {
|
||||
return Err(JsonError::NotOfType { target: "content".to_owned(), of_type: JsonType::Object }.into());
|
||||
};
|
||||
|
||||
let Some(CanonicalJsonValue::String(membership)) = content.get("membership") else {
|
||||
return Err(JsonError::NotOfType { target: "membership".to_owned(), of_type: JsonType::String }.into());
|
||||
};
|
||||
|
||||
if membership != "invite" {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
match content.get("third_party_invite") {
|
||||
Some(CanonicalJsonValue::Object(_)) => Ok(true),
|
||||
None => Ok(false),
|
||||
_ => Err(JsonError::NotOfType { target: "third_party_invite".to_owned(), of_type: JsonType::Object }.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the server names to check signatures for given event.
|
||||
///
|
||||
/// Respects the rules for [validating signatures on received events] for populating the result:
|
||||
///
|
||||
/// - Add the server of the sender, except if it's an invite event that results from a third-party
|
||||
/// invite.
|
||||
/// - For room versions 1 and 2, add the server of the `event_id`.
|
||||
/// - For room versions that support restricted join rules, if it's a join event with a
|
||||
/// `join_authorised_via_users_server`, add the server of that user.
|
||||
///
|
||||
/// [validating signatures on received events]: https://spec.matrix.org/latest/server-server-api/#validating-hashes-and-signatures-on-received-events
|
||||
pub fn servers_to_check_signatures(
|
||||
object: &CanonicalJsonObject,
|
||||
version: &RoomVersionId,
|
||||
) -> Result<BTreeSet<OwnedServerName>, Error> {
|
||||
let mut servers_to_check = BTreeSet::new();
|
||||
|
||||
if !is_invite_via_third_party_id(object)? {
|
||||
match object.get("sender") {
|
||||
Some(CanonicalJsonValue::String(raw_sender)) => {
|
||||
let user_id = <&UserId>::try_from(raw_sender.as_str())
|
||||
.map_err(|e| Error::from(ParseError::UserId(e)))?;
|
||||
|
||||
servers_to_check.insert(user_id.server_name().to_owned());
|
||||
}
|
||||
_ => return Err(JsonError::NotOfType { target: "sender".to_owned(), of_type: JsonType::String }.into()),
|
||||
};
|
||||
}
|
||||
|
||||
match version {
|
||||
RoomVersionId::V1 | RoomVersionId::V2 => match object.get("event_id") {
|
||||
Some(CanonicalJsonValue::String(raw_event_id)) => {
|
||||
let event_id: OwnedEventId =
|
||||
raw_event_id.parse().map_err(|e| Error::from(ParseError::EventId(e)))?;
|
||||
|
||||
let server_name = event_id
|
||||
.server_name()
|
||||
.ok_or_else(|| ParseError::ServerNameFromEventId(event_id.to_owned()))?
|
||||
.to_owned();
|
||||
|
||||
servers_to_check.insert(server_name);
|
||||
}
|
||||
_ => {
|
||||
return Err(JsonError::JsonFieldMissingFromObject("event_id".to_owned()).into());
|
||||
}
|
||||
},
|
||||
RoomVersionId::V3
|
||||
| RoomVersionId::V4
|
||||
| RoomVersionId::V5
|
||||
| RoomVersionId::V6
|
||||
| RoomVersionId::V7 => {}
|
||||
// TODO: And for all future versions that have join_authorised_via_users_server
|
||||
RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 | RoomVersionId::V11 | RoomVersionId::V12 => {
|
||||
if let Some(authorized_user) = object
|
||||
.get("content")
|
||||
.and_then(|c| c.as_object())
|
||||
.and_then(|c| c.get("join_authorised_via_users_server"))
|
||||
{
|
||||
let authorized_user = authorized_user.as_str().ok_or_else(|| -> Error {
|
||||
JsonError::NotOfType { target: "join_authorised_via_users_server".to_owned(), of_type: JsonType::String }.into()
|
||||
})?;
|
||||
let authorized_user = <&UserId>::try_from(authorized_user)
|
||||
.map_err(|e| Error::from(ParseError::UserId(e)))?;
|
||||
|
||||
servers_to_check.insert(authorized_user.server_name().to_owned());
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
Ok(servers_to_check)
|
||||
}
|
||||
|
||||
/// Extracts the server names and key ids to check signatures for given event.
|
||||
pub fn required_keys(
|
||||
object: &CanonicalJsonObject,
|
||||
version: &RoomVersionId,
|
||||
) -> Result<BTreeMap<OwnedServerName, Vec<OwnedServerSigningKeyId>>, Error> {
|
||||
use CanonicalJsonValue::Object;
|
||||
let mut map = BTreeMap::<OwnedServerName, Vec<OwnedServerSigningKeyId>>::new();
|
||||
let Some(Object(signatures)) = object.get("signatures") else {
|
||||
return Ok(map);
|
||||
};
|
||||
|
||||
for server in servers_to_check_signatures(object, version)? {
|
||||
let Some(Object(set)) = signatures.get(server.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let entry = map.entry(server.clone()).or_default();
|
||||
set.iter()
|
||||
.map(|(k, _)| k.clone())
|
||||
.map(TryInto::try_into)
|
||||
.filter_map(Result::ok)
|
||||
.for_each(|key_id| entry.push(key_id));
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ pub async fn verify_event(
|
||||
) -> Result<Verified> {
|
||||
let room_version = room_version.unwrap_or(&RoomVersionId::V12);
|
||||
let keys = self.get_event_keys(event, room_version).await?;
|
||||
ruma::signatures::verify_event(&keys, event, room_version).map_err(Into::into)
|
||||
ruma::signatures::verify_event(&keys, event, &room_version.rules().unwrap()).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
@@ -74,5 +74,5 @@ pub async fn verify_json(
|
||||
) -> Result {
|
||||
let room_version = room_version.unwrap_or(&RoomVersionId::V12);
|
||||
let keys = self.get_event_keys(event, room_version).await?;
|
||||
ruma::signatures::verify_json(&keys, event.clone()).map_err(Into::into)
|
||||
ruma::signatures::verify_json(&keys, event).map_err(Into::into)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user