Abstract event validation/fetching, add outlier and signing key DB trees
Fixed the miss named commented out keys in conduit-example.toml.
This commit is contained in:
parent
52392628e9
commit
4cf530c55b
7 changed files with 415 additions and 413 deletions
|
@ -1,7 +1,10 @@
|
|||
use crate::{database::Config, utils, Error, Result};
|
||||
use log::error;
|
||||
use ruma::ServerName;
|
||||
use std::collections::HashMap;
|
||||
use ruma::{
|
||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
ServerName, ServerSigningKeyId,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
@ -20,10 +23,15 @@ pub struct Globals {
|
|||
reqwest_client: reqwest::Client,
|
||||
dns_resolver: TokioAsyncResolver,
|
||||
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
|
||||
pub(super) servertimeout_signingkey: sled::Tree, // ServerName -> algorithm:key + pubkey
|
||||
}
|
||||
|
||||
impl Globals {
|
||||
pub async fn load(globals: sled::Tree, config: Config) -> Result<Self> {
|
||||
pub async fn load(
|
||||
globals: sled::Tree,
|
||||
server_keys: sled::Tree,
|
||||
config: Config,
|
||||
) -> Result<Self> {
|
||||
let bytes = &*globals
|
||||
.update_and_fetch("keypair", utils::generate_keypair)?
|
||||
.expect("utils::generate_keypair always returns Some");
|
||||
|
@ -82,6 +90,7 @@ impl Globals {
|
|||
})?,
|
||||
actual_destination_cache: Arc::new(RwLock::new(HashMap::new())),
|
||||
jwt_decoding_key,
|
||||
servertimeout_signingkey: server_keys,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -139,4 +148,66 @@ impl Globals {
|
|||
pub fn jwt_decoding_key(&self) -> Option<&jsonwebtoken::DecodingKey<'_>> {
|
||||
self.jwt_decoding_key.as_ref()
|
||||
}
|
||||
|
||||
/// TODO: the key valid until timestamp is only honored in room version > 4
|
||||
/// Remove the outdated keys and insert the new ones.
|
||||
///
|
||||
/// This doesn't actually check that the keys provided are newer than the old set.
|
||||
pub fn add_signing_key(&self, origin: &ServerName, keys: &ServerSigningKeys) -> Result<()> {
|
||||
// Remove outdated keys
|
||||
let now = crate::utils::millis_since_unix_epoch();
|
||||
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
|
||||
let (k, _) = item?;
|
||||
let valid_until = k
|
||||
.splitn(2, |&b| b == 0xff)
|
||||
.nth(1)
|
||||
.map(crate::utils::u64_from_bytes)
|
||||
.ok_or_else(|| Error::bad_database("Invalid signing keys."))?
|
||||
.map_err(|_| Error::bad_database("Invalid signing key valid until bytes"))?;
|
||||
|
||||
if now > valid_until {
|
||||
self.servertimeout_signingkey.remove(k)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut key = origin.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(
|
||||
&(keys
|
||||
.valid_until_ts
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time is valid")
|
||||
.as_millis() as u64)
|
||||
.to_be_bytes(),
|
||||
);
|
||||
|
||||
self.servertimeout_signingkey.insert(
|
||||
key,
|
||||
serde_json::to_vec(&keys.verify_keys).expect("ServerSigningKeys are a valid string"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found for the server.
|
||||
pub fn signing_keys_for(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> {
|
||||
let now = crate::utils::millis_since_unix_epoch();
|
||||
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
|
||||
let (k, bytes) = item?;
|
||||
let valid_until = k
|
||||
.splitn(2, |&b| b == 0xff)
|
||||
.nth(1)
|
||||
.map(crate::utils::u64_from_bytes)
|
||||
.ok_or_else(|| Error::bad_database("Invalid signing keys."))?
|
||||
.map_err(|_| Error::bad_database("Invalid signing key valid until bytes"))?;
|
||||
// If these keys are still valid use em!
|
||||
if valid_until > now {
|
||||
return serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"));
|
||||
}
|
||||
}
|
||||
Ok(BTreeMap::default())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,9 @@ pub struct Rooms {
|
|||
/// The state for a given state hash.
|
||||
pub(super) statekey_short: sled::Tree, // StateKey = EventType + StateKey, Short = Count
|
||||
pub(super) stateid_pduid: sled::Tree, // StateId = StateHash + Short, PduId = Count (without roomid)
|
||||
|
||||
/// Any pdu that has passed the steps up to auth with auth_events.
|
||||
pub(super) eventid_outlierpdu: sled::Tree,
|
||||
}
|
||||
|
||||
impl Rooms {
|
||||
|
@ -188,72 +191,6 @@ impl Rooms {
|
|||
Ok(events)
|
||||
}
|
||||
|
||||
/// Returns a Vec of the related auth events to the given `event`.
|
||||
///
|
||||
/// A recursive list of all the auth_events going back to `RoomCreate` for each event in `event_ids`.
|
||||
pub fn auth_events_full(
|
||||
&self,
|
||||
_room_id: &RoomId,
|
||||
event_ids: &[EventId],
|
||||
) -> Result<Vec<PduEvent>> {
|
||||
let mut result = BTreeMap::new();
|
||||
let mut stack = event_ids.to_vec();
|
||||
|
||||
// DFS for auth event chain
|
||||
while !stack.is_empty() {
|
||||
let ev_id = stack.pop().unwrap();
|
||||
if result.contains_key(&ev_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ev) = self.get_pdu(&ev_id)? {
|
||||
stack.extend(ev.auth_events());
|
||||
result.insert(ev.event_id().clone(), ev);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result.into_iter().map(|(_, v)| v).collect())
|
||||
}
|
||||
|
||||
/// Returns a Vec<EventId> representing the difference in auth chains of the given `events`.
|
||||
///
|
||||
/// Each inner `Vec` of `event_ids` represents a state set (state at each forward extremity).
|
||||
pub fn auth_chain_diff(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_ids: Vec<Vec<EventId>>,
|
||||
) -> Result<Vec<EventId>> {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
let mut chains = vec![];
|
||||
for ids in event_ids {
|
||||
// TODO state store `auth_event_ids` returns self in the event ids list
|
||||
// when an event returns `auth_event_ids` self is not contained
|
||||
let chain = self
|
||||
.auth_events_full(room_id, &ids)?
|
||||
.into_iter()
|
||||
.map(|pdu| pdu.event_id)
|
||||
.collect::<BTreeSet<_>>();
|
||||
chains.push(chain);
|
||||
}
|
||||
|
||||
if let Some(chain) = chains.first() {
|
||||
let rest = chains.iter().skip(1).flatten().cloned().collect();
|
||||
let common = chain.intersection(&rest).collect::<Vec<_>>();
|
||||
|
||||
Ok(chains
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|id| !common.contains(&id))
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect())
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a new StateHash.
|
||||
///
|
||||
/// A unique hash made from hashing all PDU ids of the state joined with 0xff.
|
||||
|
@ -475,6 +412,31 @@ impl Rooms {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the pdu from the outlier tree.
|
||||
pub fn get_pdu_outlier(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||
self.eventid_outlierpdu
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or(Ok(None), |pdu| {
|
||||
Ok(Some(
|
||||
serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the event_id was previously inserted.
|
||||
pub fn append_pdu_outlier(&self, event_id: &EventId, pdu: &PduEvent) -> Result<bool> {
|
||||
log::info!("Number of outlier pdu's {}", self.eventid_outlierpdu.len());
|
||||
let res = self
|
||||
.eventid_outlierpdu
|
||||
.insert(
|
||||
event_id.as_bytes(),
|
||||
&*serde_json::to_string(&pdu).expect("PduEvent is always a valid String"),
|
||||
)
|
||||
.map(|op| op.is_some())?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Creates a new persisted data unit and adds it to a room.
|
||||
///
|
||||
/// By this point the incoming event should be fully authenticated, no auth happens
|
||||
|
@ -516,6 +478,9 @@ impl Rooms {
|
|||
}
|
||||
}
|
||||
|
||||
// We no longer keep this pdu as an outlier
|
||||
self.eventid_outlierpdu.remove(pdu.event_id().as_bytes())?;
|
||||
|
||||
self.replace_pdu_leaves(&pdu.room_id, &pdu.event_id)?;
|
||||
|
||||
// Mark as read first so the sending client doesn't get a notification even if appending
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue