fix(keys): only use keys valid at the time of PDU or transaction, and actually refresh keys

Previously, we only fetched keys once, only requesting them again if we have any missing, allowing for ancient keys to be used to sign PDUs and transactions
Now we refresh keys that either have or are about to expire, preventing attacks that make use of leaked private keys of a homeserver
We also ensure that when validating PDUs or transactions, that they are valid at the origin_server_ts or time of us receiving the transaction respectfully
As to not break event authorization for old rooms, we need to keep old keys around
We move verify_keys which we no longer see in direct requests to the origin to old_verify_keys
We keep old_verify_keys indefinitely as mentioned above, as to not break event authorization (at least until a future MSC addresses this)

Original patch by Matthias. Benjamin just rebased it onto grapevine and
fixed clippy/rustc warnings.

Co-authored-by: Benjamin Lee <benjamin@computer.surgery>
This commit is contained in:
Matthias Ahouansou 2024-06-09 11:15:49 +01:00 committed by Charles Hall
parent da99b0706e
commit 9087da91db
No known key found for this signature in database
GPG key ID: 7B8E0645816E07CF
8 changed files with 610 additions and 214 deletions

View file

@ -1,13 +1,75 @@
use std::collections::BTreeMap;
use std::{
collections::BTreeMap,
time::{Duration, SystemTime},
};
use async_trait::async_trait;
use ruma::{
api::federation::discovery::{ServerSigningKeys, VerifyKey},
api::federation::discovery::{OldVerifyKey, ServerSigningKeys, VerifyKey},
serde::Base64,
signatures::Ed25519KeyPair,
DeviceId, OwnedServerSigningKeyId, ServerName, UserId,
DeviceId, MilliSecondsSinceUnixEpoch, ServerName, UserId,
};
use serde::Deserialize;
use crate::Result;
use crate::{services, Result};
/// Similar to [`ServerSigningKeys`], but drops a few unnecessary fields we
/// don't require post-validation
#[derive(Deserialize, Debug, Clone)]
pub(crate) struct SigningKeys {
pub(crate) verify_keys: BTreeMap<String, VerifyKey>,
pub(crate) old_verify_keys: BTreeMap<String, OldVerifyKey>,
pub(crate) valid_until_ts: MilliSecondsSinceUnixEpoch,
}
impl SigningKeys {
/// Creates the `SigningKeys` struct, using the keys of the current server
pub(crate) fn load_own_keys() -> Self {
let mut keys = Self {
verify_keys: BTreeMap::new(),
old_verify_keys: BTreeMap::new(),
valid_until_ts: MilliSecondsSinceUnixEpoch::from_system_time(
SystemTime::now() + Duration::from_secs(7 * 86400),
)
.expect("Should be valid until year 500,000,000"),
};
keys.verify_keys.insert(
format!("ed25519:{}", services().globals.keypair().version()),
VerifyKey {
key: Base64::new(
services().globals.keypair.public_key().to_vec(),
),
},
);
keys
}
}
impl From<ServerSigningKeys> for SigningKeys {
fn from(value: ServerSigningKeys) -> Self {
let ServerSigningKeys {
verify_keys,
old_verify_keys,
valid_until_ts,
..
} = value;
Self {
verify_keys: verify_keys
.into_iter()
.map(|(id, key)| (id.to_string(), key))
.collect(),
old_verify_keys: old_verify_keys
.into_iter()
.map(|(id, key)| (id.to_string(), key))
.collect(),
valid_until_ts,
}
}
}
#[async_trait]
pub(crate) trait Data: Send + Sync {
@ -20,18 +82,29 @@ pub(crate) trait Data: Send + Sync {
fn clear_caches(&self, amount: u32);
fn load_keypair(&self) -> Result<Ed25519KeyPair>;
fn remove_keypair(&self) -> Result<()>;
fn add_signing_key(
/// Only extends the cached keys, not moving any verify_keys to
/// old_verify_keys, as if we suddenly recieve requests from the origin
/// server, we want to be able to accept requests from them
fn add_signing_key_from_trusted_server(
&self,
origin: &ServerName,
new_keys: ServerSigningKeys,
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>;
) -> Result<SigningKeys>;
/// Extends cached keys, as well as moving verify_keys that are not present
/// in these new keys to old_verify_keys, so that potnetially
/// comprimised keys cannot be used to make requests
fn add_signing_key_from_origin(
&self,
origin: &ServerName,
new_keys: ServerSigningKeys,
) -> Result<SigningKeys>;
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
/// for the server.
fn signing_keys_for(
&self,
origin: &ServerName,
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>;
) -> Result<Option<SigningKeys>>;
fn database_version(&self) -> Result<u64>;
fn bump_database_version(&self, new_version: u64) -> Result<()>;
}