mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-17 15:51:23 +01:00
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:
parent
da99b0706e
commit
9087da91db
8 changed files with 610 additions and 214 deletions
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
pub(crate) use data::Data;
|
||||
pub(crate) use data::{Data, SigningKeys};
|
||||
use futures_util::FutureExt;
|
||||
use hyper::service::Service as _;
|
||||
use hyper_util::{
|
||||
|
|
@ -23,10 +23,9 @@ use hyper_util::{
|
|||
};
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use ruma::{
|
||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
serde::Base64,
|
||||
DeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
OwnedServerSigningKeyId, RoomVersionId, ServerName, UserId,
|
||||
api::federation::discovery::ServerSigningKeys, serde::Base64, DeviceId,
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use tokio::sync::{broadcast, Mutex, RwLock, Semaphore};
|
||||
use tracing::{error, info, Instrument};
|
||||
|
|
@ -379,38 +378,90 @@ impl Service {
|
|||
room_versions
|
||||
}
|
||||
|
||||
/// 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(crate) fn add_signing_key(
|
||||
pub(crate) fn add_signing_key_from_trusted_server(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
new_keys: ServerSigningKeys,
|
||||
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>> {
|
||||
self.db.add_signing_key(origin, new_keys)
|
||||
) -> Result<SigningKeys> {
|
||||
self.db.add_signing_key_from_trusted_server(origin, new_keys)
|
||||
}
|
||||
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
||||
/// for the server.
|
||||
/// Same as `from_trusted_server`, except it will move active keys not
|
||||
/// present in `new_keys` to `old_signing_keys`
|
||||
pub(crate) fn add_signing_key_from_origin(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
new_keys: ServerSigningKeys,
|
||||
) -> Result<SigningKeys> {
|
||||
self.db.add_signing_key_from_origin(origin, new_keys)
|
||||
}
|
||||
|
||||
/// This returns Ok(None) when there are no keys found for the server.
|
||||
pub(crate) fn signing_keys_for(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>> {
|
||||
let mut keys = self.db.signing_keys_for(origin)?;
|
||||
if origin == self.server_name() {
|
||||
keys.insert(
|
||||
format!("ed25519:{}", services().globals.keypair().version())
|
||||
.try_into()
|
||||
.expect("found invalid server signing keys in DB"),
|
||||
VerifyKey {
|
||||
key: Base64::new(self.keypair.public_key().to_vec()),
|
||||
},
|
||||
);
|
||||
}
|
||||
) -> Result<Option<SigningKeys>> {
|
||||
Ok(self.db.signing_keys_for(origin)?.or_else(|| {
|
||||
(origin == self.server_name()).then(SigningKeys::load_own_keys)
|
||||
}))
|
||||
}
|
||||
|
||||
Ok(keys)
|
||||
/// Filters the key map of multiple servers down to keys that should be
|
||||
/// accepted given the expiry time, room version, and timestamp of the
|
||||
/// paramters
|
||||
#[allow(clippy::unused_self)]
|
||||
pub(crate) fn filter_keys_server_map(
|
||||
&self,
|
||||
keys: BTreeMap<String, SigningKeys>,
|
||||
timestamp: MilliSecondsSinceUnixEpoch,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> BTreeMap<String, BTreeMap<String, Base64>> {
|
||||
keys.into_iter()
|
||||
.filter_map(|(server, keys)| {
|
||||
self.filter_keys_single_server(keys, timestamp, room_version_id)
|
||||
.map(|keys| (server, keys))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Filters the keys of a single server down to keys that should be accepted
|
||||
/// given the expiry time, room version, and timestamp of the paramters
|
||||
#[allow(clippy::unused_self)]
|
||||
pub(crate) fn filter_keys_single_server(
|
||||
&self,
|
||||
keys: SigningKeys,
|
||||
timestamp: MilliSecondsSinceUnixEpoch,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> Option<BTreeMap<String, Base64>> {
|
||||
let all_valid = keys.valid_until_ts > timestamp
|
||||
// valid_until_ts MUST be ignored in room versions 1, 2, 3, and 4.
|
||||
// https://spec.matrix.org/v1.10/server-server-api/#get_matrixkeyv2server
|
||||
|| matches!(room_version_id, RoomVersionId::V1
|
||||
| RoomVersionId::V2
|
||||
| RoomVersionId::V4
|
||||
| RoomVersionId::V3);
|
||||
all_valid.then(|| {
|
||||
// Given that either the room version allows stale keys, or the
|
||||
// valid_until_ts is in the future, all verify_keys are
|
||||
// valid
|
||||
let mut map: BTreeMap<_, _> = keys
|
||||
.verify_keys
|
||||
.into_iter()
|
||||
.map(|(id, key)| (id, key.key))
|
||||
.collect();
|
||||
|
||||
map.extend(keys.old_verify_keys.into_iter().filter_map(
|
||||
|(id, key)| {
|
||||
// Even on old room versions, we don't allow old keys if
|
||||
// they are expired
|
||||
(key.expired_ts > timestamp).then_some((id, key.key))
|
||||
},
|
||||
));
|
||||
|
||||
map
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn database_version(&self) -> Result<u64> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue