mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
fix: Signature verification
This commit is contained in:
Generated
+1
@@ -1090,6 +1090,7 @@ dependencies = [
|
|||||||
"core_affinity",
|
"core_affinity",
|
||||||
"ctor",
|
"ctor",
|
||||||
"cyborgtime",
|
"cyborgtime",
|
||||||
|
"ed25519-dalek",
|
||||||
"either",
|
"either",
|
||||||
"figment",
|
"figment",
|
||||||
"futures",
|
"futures",
|
||||||
|
|||||||
+14
-13
@@ -10,31 +10,31 @@ version.workspace = true
|
|||||||
[lib]
|
[lib]
|
||||||
path = "mod.rs"
|
path = "mod.rs"
|
||||||
crate-type = [
|
crate-type = [
|
||||||
"rlib",
|
"rlib",
|
||||||
# "dylib",
|
# "dylib",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
brotli_compression = [
|
brotli_compression = [
|
||||||
"reqwest/brotli",
|
"reqwest/brotli",
|
||||||
]
|
]
|
||||||
conduwuit_mods = [
|
conduwuit_mods = [
|
||||||
"dep:libloading"
|
"dep:libloading"
|
||||||
]
|
]
|
||||||
gzip_compression = [
|
gzip_compression = [
|
||||||
"reqwest/gzip",
|
"reqwest/gzip",
|
||||||
]
|
]
|
||||||
hardened_malloc = [
|
hardened_malloc = [
|
||||||
"dep:hardened_malloc-rs"
|
"dep:hardened_malloc-rs"
|
||||||
]
|
]
|
||||||
jemalloc = [
|
jemalloc = [
|
||||||
"dep:tikv-jemalloc-sys",
|
"dep:tikv-jemalloc-sys",
|
||||||
"dep:tikv-jemalloc-ctl",
|
"dep:tikv-jemalloc-ctl",
|
||||||
"dep:tikv-jemallocator",
|
"dep:tikv-jemallocator",
|
||||||
]
|
]
|
||||||
jemalloc_conf = []
|
jemalloc_conf = []
|
||||||
jemalloc_prof = [
|
jemalloc_prof = [
|
||||||
"tikv-jemalloc-sys/profiling",
|
"tikv-jemalloc-sys/profiling",
|
||||||
]
|
]
|
||||||
jemalloc_stats = [
|
jemalloc_stats = [
|
||||||
"tikv-jemalloc-sys/stats",
|
"tikv-jemalloc-sys/stats",
|
||||||
@@ -43,10 +43,10 @@ jemalloc_stats = [
|
|||||||
]
|
]
|
||||||
perf_measurements = []
|
perf_measurements = []
|
||||||
release_max_log_level = [
|
release_max_log_level = [
|
||||||
"tracing/max_level_trace",
|
"tracing/max_level_trace",
|
||||||
"tracing/release_max_level_info",
|
"tracing/release_max_level_info",
|
||||||
"log/max_level_trace",
|
"log/max_level_trace",
|
||||||
"log/release_max_level_info",
|
"log/release_max_level_info",
|
||||||
]
|
]
|
||||||
sentry_telemetry = []
|
sentry_telemetry = []
|
||||||
zstd_compression = [
|
zstd_compression = [
|
||||||
@@ -110,6 +110,7 @@ tracing.workspace = true
|
|||||||
url.workspace = true
|
url.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
lock_api.workspace = true
|
lock_api.workspace = true
|
||||||
|
ed25519-dalek = "~2"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
nix.workspace = true
|
nix.workspace = true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::{borrow::Borrow, collections::BTreeSet};
|
use std::{borrow::Borrow, collections::BTreeSet};
|
||||||
|
|
||||||
|
use ed25519_dalek::{Verifier, VerifyingKey};
|
||||||
use futures::{
|
use futures::{
|
||||||
Future,
|
Future,
|
||||||
future::{OptionFuture, join, join3},
|
future::{OptionFuture, join, join3},
|
||||||
@@ -11,17 +12,20 @@ use ruma::{
|
|||||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||||
member::{MembershipState, ThirdPartyInvite},
|
member::{MembershipState, ThirdPartyInvite},
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
third_party_invite::RoomThirdPartyInviteEventContent,
|
third_party_invite::{PublicKey, RoomThirdPartyInviteEventContent},
|
||||||
},
|
},
|
||||||
int,
|
int,
|
||||||
serde::{Base64, Raw},
|
serde::{Base64, Raw, base64::Standard},
|
||||||
signatures::{PublicKeyMap, PublicKeySet, verify_json},
|
signatures::{ParseError, VerificationError},
|
||||||
};
|
};
|
||||||
use serde::{
|
use serde::{
|
||||||
Deserialize,
|
Deserialize,
|
||||||
de::{Error as _, IgnoredAny},
|
de::{Error as _, IgnoredAny},
|
||||||
};
|
};
|
||||||
use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue};
|
use serde_json::{
|
||||||
|
from_str as from_json_str,
|
||||||
|
value::{RawValue as RawJsonValue, to_raw_value},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Error, Event, Result, StateEventType, StateKey, TimelineEventType,
|
Error, Event, Result, StateEventType, StateKey, TimelineEventType,
|
||||||
@@ -31,7 +35,7 @@ use super::{
|
|||||||
},
|
},
|
||||||
room_version::RoomVersion,
|
room_version::RoomVersion,
|
||||||
};
|
};
|
||||||
use crate::{debug, error, trace, utils::to_canonical_object, warn};
|
use crate::{debug, error, trace, warn};
|
||||||
|
|
||||||
// FIXME: field extracting could be bundled for `content`
|
// FIXME: field extracting could be bundled for `content`
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -1497,6 +1501,17 @@ fn get_send_level(
|
|||||||
.unwrap_or_else(|| if state_key.is_some() { int!(50) } else { int!(0) })
|
.unwrap_or_else(|| if state_key.is_some() { int!(50) } else { int!(0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_payload(pk: &[u8], sig: &[u8], c: &[u8]) -> Result<(), ruma::signatures::Error> {
|
||||||
|
VerifyingKey::from_bytes(
|
||||||
|
pk.try_into()
|
||||||
|
.map_err(|_| ParseError::PublicKey(ed25519_dalek::SignatureError::new()))?,
|
||||||
|
)
|
||||||
|
.map_err(ParseError::PublicKey)?
|
||||||
|
.verify(c, &sig.try_into().map_err(ParseError::Signature)?)
|
||||||
|
.map_err(VerificationError::Signature)
|
||||||
|
.map_err(ruma::signatures::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks a third-party invite is valid.
|
/// Checks a third-party invite is valid.
|
||||||
async fn verify_third_party_invite<F, Fut, E>(
|
async fn verify_third_party_invite<F, Fut, E>(
|
||||||
target_current_membership: MembershipState,
|
target_current_membership: MembershipState,
|
||||||
@@ -1554,45 +1569,61 @@ where
|
|||||||
// content of m.room.third_party_invite as:
|
// content of m.room.third_party_invite as:
|
||||||
// 1. A single public key in the public_key property.
|
// 1. A single public key in the public_key property.
|
||||||
// 2. A list of public keys in the public_keys property.
|
// 2. A list of public keys in the public_keys property.
|
||||||
if third_party_invite_event
|
let Ok(tpi_content) =
|
||||||
.get_content::<RoomThirdPartyInviteEventContent>()
|
third_party_invite_event.get_content::<RoomThirdPartyInviteEventContent>()
|
||||||
.is_err()
|
else {
|
||||||
{
|
|
||||||
warn!("m.room.third_party_invite event has invalid content");
|
warn!("m.room.third_party_invite event has invalid content");
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
let Some(signatures) = signed.get("signatures").and_then(|v| v.as_object()) else {
|
let Some(signatures) = signed.get("signatures").and_then(|v| v.as_object()) else {
|
||||||
warn!("invite event third_party_invite signed missing/invalid signatures");
|
warn!("invite event third_party_invite signed missing/invalid signatures");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let mut public_key_map = PublicKeyMap::new();
|
let mut public_keys = tpi_content.public_keys.unwrap_or_default();
|
||||||
for (server_name, sig_map) in signatures {
|
public_keys.push(PublicKey {
|
||||||
let mut pk_set = PublicKeySet::new();
|
public_key: tpi_content.public_key,
|
||||||
if let Some(sig_map) = sig_map.as_object() {
|
key_validity_url: Some(tpi_content.key_validity_url),
|
||||||
for (key_id, sig) in sig_map {
|
});
|
||||||
let Some(sig_str) = sig.as_str() else {
|
|
||||||
warn!(
|
for pk in public_keys {
|
||||||
"invite event third_party_invite signature is not a string or is missing"
|
// signatures -> { server_name: { ed25519:N: signature } }
|
||||||
);
|
for (server_name, server_sigs) in signatures {
|
||||||
return false;
|
if let Some(server_sigs) = server_sigs.as_object() {
|
||||||
};
|
for (key_id, signature_value) in server_sigs {
|
||||||
let Ok(sig_b64) = Base64::parse(sig_str) else {
|
if let Some(signature_str) = signature_value.as_str() {
|
||||||
warn!("invite event third_party_invite signature is not valid Base64");
|
if let Ok(signature) = Base64::<Standard>::parse(signature_str) {
|
||||||
return false;
|
debug!(
|
||||||
};
|
?pk,
|
||||||
pk_set.insert(key_id.clone(), sig_b64);
|
%server_name,
|
||||||
|
%key_id,
|
||||||
|
"verifying third-party invite signature",
|
||||||
|
);
|
||||||
|
match verify_payload(
|
||||||
|
pk.public_key.as_bytes(),
|
||||||
|
signature.as_bytes(),
|
||||||
|
to_raw_value(signed).unwrap().get().as_bytes(),
|
||||||
|
) {
|
||||||
|
| Ok(()) => {
|
||||||
|
debug!("valid third-party invite signature found");
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
| Err(e) => {
|
||||||
|
warn!(
|
||||||
|
?pk,
|
||||||
|
%server_name,
|
||||||
|
%key_id,
|
||||||
|
"invalid third-party invite signature: {e}",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public_key_map.insert(server_name.clone(), pk_set);
|
|
||||||
}
|
}
|
||||||
if let Err(e) = verify_json(
|
|
||||||
&public_key_map,
|
warn!("no valid signature found for third-party invite");
|
||||||
to_canonical_object(signed).expect("signed was already validated"),
|
false
|
||||||
) {
|
|
||||||
warn!("invite event third_party_invite signature verification failed: {e}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If there was no error, there was a valid signature, so allow.
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user