use std::collections::{BTreeMap, BTreeSet};
use ruma_common::{
AnyKeyName, CanonicalJsonObject, CanonicalJsonValue, IdParseError, OwnedEventId,
OwnedServerName, SigningKeyAlgorithm, SigningKeyId, UserId,
canonical_json::{
CanonicalJsonFieldError, CanonicalJsonObjectExt, CanonicalJsonType, RedactingSerializer,
},
room_version_rules::{RoomVersionRules, SignaturesRules},
serde::{Base64, base64::Standard},
};
use ruma_events::{
StaticEventContent,
room::policy::{POLICY_SERVER_ED25519_SIGNING_KEY_ID, RoomPolicyEventContent},
};
#[cfg(test)]
mod tests;
use crate::{
JsonError, VerificationError, content_hash, ed25519::Ed25519Verifier,
sign::FIELDS_TO_REMOVE_FOR_SIGNING,
};
pub fn verify_event(
public_key_map: &PublicKeyMap,
object: &CanonicalJsonObject,
rules: &RoomVersionRules,
) -> Result<Verified, VerificationError> {
let hashes = object.get_as_required_object("hashes", "hashes")?;
let hash = hashes.get_as_required_string("sha256", "hashes.sha256")?;
let signature_map = object.get_as_required_object("signatures", "signatures")?;
let servers_to_check = required_server_signatures_to_verify_event(object, &rules.signatures)?;
let canonical_json = RedactingSerializer::new()
.rules(&rules.redaction)
.custom_redacted_root_fields(FIELDS_TO_REMOVE_FOR_SIGNING)
.serialize(object)?;
for entity_id in servers_to_check {
verify_canonical_json_for_entity(
entity_id.as_str(),
public_key_map,
signature_map,
canonical_json.as_bytes(),
)?;
}
let calculated_hash = content_hash(object)?;
if let Ok(hash) = Base64::<Standard>::parse(hash)
&& hash.as_bytes() == calculated_hash.as_bytes()
{
return Ok(Verified::All);
}
Ok(Verified::Signatures)
}
pub fn verify_policy_server_signature(
room_policy: &RoomPolicyEventContent,
object: &CanonicalJsonObject,
rules: &RoomVersionRules,
) -> Result<(), VerificationError> {
let event_type = object.get_as_required_string("type", "type")?;
if event_type == RoomPolicyEventContent::TYPE
&& object
.get_as_required_string("state_key", "state_key")
.is_ok_and(|state_key| state_key.is_empty())
{
return Ok(());
}
let signature_map = object.get_as_required_object("signatures", "signatures")?;
let canonical_json = RedactingSerializer::new()
.rules(&rules.redaction)
.custom_redacted_root_fields(FIELDS_TO_REMOVE_FOR_SIGNING)
.serialize(object)?;
verify_canonical_json_for_entity(
room_policy.via.as_str(),
room_policy,
signature_map,
canonical_json.as_bytes(),
)
}
pub fn verify_json(
public_key_map: &PublicKeyMap,
object: &CanonicalJsonObject,
) -> Result<(), VerificationError> {
let signature_map = object.get_as_required_object("signatures", "signatures")?;
let canonical_json = to_canonical_json_string_for_signing(object)?;
for entity_id in signature_map.keys() {
verify_canonical_json_for_entity(
entity_id,
public_key_map,
signature_map,
canonical_json.as_bytes(),
)?;
}
Ok(())
}
pub fn verify_canonical_json_bytes(
algorithm: &SigningKeyAlgorithm,
public_key: &[u8],
signature: &[u8],
canonical_json: &[u8],
) -> Result<(), VerificationError> {
let verifier =
verifier_from_algorithm(algorithm).ok_or(VerificationError::UnsupportedAlgorithm)?;
verify_canonical_json_with(&verifier, public_key, signature, canonical_json)
}
pub fn to_canonical_json_string_for_signing(
object: &CanonicalJsonObject,
) -> Result<String, JsonError> {
Ok(RedactingSerializer::new()
.custom_redacted_root_fields(FIELDS_TO_REMOVE_FOR_SIGNING)
.serialize(object)?)
}
fn verify_canonical_json_for_entity(
entity_id: &str,
fetch_public_keys: &impl FetchEntityPublicSigningKey,
signature_map: &CanonicalJsonObject,
canonical_json: &[u8],
) -> Result<(), VerificationError> {
let signature_set = signature_map
.get_as_object(entity_id, format!("signatures.{entity_id}"))?
.ok_or_else(|| VerificationError::NoSignaturesForEntity(entity_id.to_owned()))?;
let mut checked = false;
for (key_id, signature) in signature_set {
let Some(public_key) = fetch_public_keys.public_signing_key(entity_id, key_id)? else {
continue;
};
let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
continue;
};
let Some(verifier) = verifier_from_algorithm(&parsed_key_id.algorithm()) else {
continue;
};
let CanonicalJsonValue::String(signature) = signature else {
return Err(CanonicalJsonFieldError::InvalidType {
path: format!("signatures.{entity_id}.{key_id}"),
expected: CanonicalJsonType::String,
found: signature.json_type(),
}
.into());
};
let signature = Base64::<Standard>::parse(signature).map_err(|error| {
VerificationError::InvalidBase64Signature {
path: format!("signatures.{entity_id}.{key_id}"),
source: error,
}
})?;
verify_canonical_json_with(&verifier, public_key, signature.as_bytes(), canonical_json)?;
checked = true;
}
if !checked {
return Err(VerificationError::NoSupportedSignatureForEntity(entity_id.to_owned()));
}
Ok(())
}
fn verify_canonical_json_with<V>(
verifier: &V,
public_key: &[u8],
signature: &[u8],
canonical_json: &[u8],
) -> Result<(), VerificationError>
where
V: Verifier,
{
verifier.verify_json(public_key, signature, canonical_json).map_err(Into::into)
}
pub fn required_server_signatures_to_verify_event(
object: &CanonicalJsonObject,
rules: &SignaturesRules,
) -> Result<BTreeSet<OwnedServerName>, VerificationError> {
let mut servers_to_check = BTreeSet::new();
if !is_invite_via_third_party_id(object)? {
let sender = object.get_as_required_string("sender", "sender")?;
let user_id = <&UserId>::try_from(sender).map_err(|source| {
VerificationError::ParseIdentifier { identifier_type: "user ID", source }
})?;
servers_to_check.insert(user_id.server_name().to_owned());
}
if rules.check_event_id_server {
let raw_event_id = object.get_as_required_string("event_id", "event_id")?;
let event_id: OwnedEventId = raw_event_id.parse().map_err(|source| {
VerificationError::ParseIdentifier { identifier_type: "event ID", source }
})?;
let server_name = event_id.server_name().map(ToOwned::to_owned).ok_or_else(|| {
VerificationError::ParseIdentifier {
identifier_type: "event ID",
source: IdParseError::InvalidServerName,
}
})?;
servers_to_check.insert(server_name);
}
if rules.check_join_authorised_via_users_server
&& let Some(authorized_user) = object
.get("content")
.and_then(|c| c.as_object())
.map(|c| {
c.get_as_string(
"join_authorised_via_users_server",
"content.join_authorised_via_users_server",
)
})
.transpose()?
.flatten()
{
let authorized_user = <&UserId>::try_from(authorized_user).map_err(|source| {
VerificationError::ParseIdentifier { identifier_type: "user ID", source }
})?;
servers_to_check.insert(authorized_user.server_name().to_owned());
}
Ok(servers_to_check)
}
fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, JsonError> {
let event_type = object.get_as_required_string("type", "type")?;
if event_type != "m.room.member" {
return Ok(false);
}
let content = object.get_as_required_object("content", "content")?;
let membership = content.get_as_required_string("membership", "content.membership")?;
if membership != "invite" {
return Ok(false);
}
Ok(content.get_as_object("third_party_invite", "content.third_party_invite")?.is_some())
}
pub(crate) trait Verifier {
type Error: std::error::Error + Into<VerificationError>;
fn verify_json(
&self,
public_key: &[u8],
signature: &[u8],
message: &[u8],
) -> Result<(), Self::Error>;
}
fn verifier_from_algorithm(algorithm: &SigningKeyAlgorithm) -> Option<impl Verifier + use<>> {
match algorithm {
SigningKeyAlgorithm::Ed25519 => Some(Ed25519Verifier),
_ => None,
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[allow(clippy::exhaustive_enums)]
pub enum Verified {
All,
Signatures,
}
pub type PublicKeyMap = BTreeMap<String, PublicKeySet>;
pub type PublicKeySet = BTreeMap<String, Base64>;
trait FetchEntityPublicSigningKey {
fn public_signing_key(
&self,
entity: &str,
key_id: &str,
) -> Result<Option<&[u8]>, VerificationError>;
}
impl FetchEntityPublicSigningKey for PublicKeyMap {
fn public_signing_key(
&self,
entity: &str,
key_id: &str,
) -> Result<Option<&[u8]>, VerificationError> {
Ok(self
.get(entity)
.ok_or_else(|| VerificationError::NoPublicKeysForEntity(entity.to_owned()))?
.get(key_id)
.map(Base64::as_bytes))
}
}
impl FetchEntityPublicSigningKey for RoomPolicyEventContent {
fn public_signing_key(
&self,
entity: &str,
key_id: &str,
) -> Result<Option<&[u8]>, VerificationError> {
if entity != self.via {
return Err(VerificationError::NoPublicKeysForEntity(entity.to_owned()));
}
if key_id != POLICY_SERVER_ED25519_SIGNING_KEY_ID {
return Ok(None);
}
Ok(self.public_keys.get(&SigningKeyAlgorithm::Ed25519).map(Base64::as_bytes))
}
}