signatures: Handle room version 5 / 6 differences
This commit is contained in:
parent
fa58f09a23
commit
eeb5df2c95
@ -15,5 +15,6 @@ version = "0.6.0-dev.1"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.12.3"
|
base64 = "0.12.3"
|
||||||
ring = "0.16.15"
|
ring = "0.16.15"
|
||||||
|
ruma-identifiers = { version = "0.17.4", path = "../ruma-identifiers" }
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
untrusted = "0.7.1"
|
untrusted = "0.7.1"
|
||||||
|
@ -4,6 +4,7 @@ use std::{cmp, collections::HashMap, mem};
|
|||||||
|
|
||||||
use base64::{decode_config, encode_config, STANDARD_NO_PAD};
|
use base64::{decode_config, encode_config, STANDARD_NO_PAD};
|
||||||
use ring::digest::{digest, SHA256};
|
use ring::digest::{digest, SHA256};
|
||||||
|
use ruma_identifiers::RoomVersionId;
|
||||||
use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value};
|
use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -33,7 +34,7 @@ static ALLOWED_KEYS: &[&str] = &[
|
|||||||
"membership",
|
"membership",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn allowed_content_keys_for(event_type: &str) -> &'static [&'static str] {
|
fn allowed_content_keys_for(event_type: &str, version: &RoomVersionId) -> &'static [&'static str] {
|
||||||
match event_type {
|
match event_type {
|
||||||
"m.room.member" => &["membership"],
|
"m.room.member" => &["membership"],
|
||||||
"m.room.create" => &["creator"],
|
"m.room.create" => &["creator"],
|
||||||
@ -48,6 +49,16 @@ fn allowed_content_keys_for(event_type: &str) -> &'static [&'static str] {
|
|||||||
"users",
|
"users",
|
||||||
"users_default",
|
"users_default",
|
||||||
],
|
],
|
||||||
|
"m.room.aliases" => match version {
|
||||||
|
RoomVersionId::Version1
|
||||||
|
| RoomVersionId::Version2
|
||||||
|
| RoomVersionId::Version3
|
||||||
|
| RoomVersionId::Version4
|
||||||
|
| RoomVersionId::Version5 => &["join_rule"],
|
||||||
|
// All other room versions, including custom ones, are treated by version 6 rules.
|
||||||
|
// TODO: Should we return an error for unknown versions instead?
|
||||||
|
_ => &[],
|
||||||
|
},
|
||||||
"m.room.history_visibility" => &["history_visibility"],
|
"m.room.history_visibility" => &["history_visibility"],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
@ -366,8 +377,8 @@ pub fn content_hash(value: &Value) -> Result<String, Error> {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if the provided JSON value is not a JSON object.
|
/// Returns an error if the provided JSON value is not a JSON object.
|
||||||
pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
pub fn reference_hash(value: &Value, version: &RoomVersionId) -> Result<String, Error> {
|
||||||
let redacted_value = redact(value)?;
|
let redacted_value = redact(value, version)?;
|
||||||
|
|
||||||
let json =
|
let json =
|
||||||
canonical_json_with_fields_to_remove(&redacted_value, REFERENCE_HASH_FIELDS_TO_REMOVE)?;
|
canonical_json_with_fields_to_remove(&redacted_value, REFERENCE_HASH_FIELDS_TO_REMOVE)?;
|
||||||
@ -403,6 +414,9 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # use ruma_identifiers::RoomVersionId;
|
||||||
|
/// # use ruma_signatures::{hash_and_sign_event, Ed25519KeyPair};
|
||||||
|
/// #
|
||||||
/// const PKCS8: &str = "\
|
/// const PKCS8: &str = "\
|
||||||
/// MFMCAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\
|
/// MFMCAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\
|
||||||
/// tA0T+6toSMDIQDdM+tpNzNWQM9NFpfgr4B9S7LHszOrVRp9NfKmeXS3aQ\
|
/// tA0T+6toSMDIQDdM+tpNzNWQM9NFpfgr4B9S7LHszOrVRp9NfKmeXS3aQ\
|
||||||
@ -411,7 +425,7 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
|||||||
/// let document = base64::decode_config(&PKCS8, base64::STANDARD_NO_PAD).unwrap();
|
/// let document = base64::decode_config(&PKCS8, base64::STANDARD_NO_PAD).unwrap();
|
||||||
///
|
///
|
||||||
/// // Create an Ed25519 key pair.
|
/// // Create an Ed25519 key pair.
|
||||||
/// let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
/// let key_pair = Ed25519KeyPair::new(
|
||||||
/// &document,
|
/// &document,
|
||||||
/// "1".into(), // The "version" of the key.
|
/// "1".into(), // The "version" of the key.
|
||||||
/// ).unwrap();
|
/// ).unwrap();
|
||||||
@ -437,7 +451,7 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
|||||||
/// ).unwrap();
|
/// ).unwrap();
|
||||||
///
|
///
|
||||||
/// // Hash and sign the JSON with the key pair.
|
/// // Hash and sign the JSON with the key pair.
|
||||||
/// assert!(ruma_signatures::hash_and_sign_event("domain", &key_pair, &mut value).is_ok());
|
/// assert!(hash_and_sign_event("domain", &key_pair, &mut value, &RoomVersionId::Version1).is_ok());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This will modify the JSON from the structure shown to a structure like this:
|
/// This will modify the JSON from the structure shown to a structure like this:
|
||||||
@ -468,7 +482,12 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Notice the addition of `hashes` and `signatures`.
|
/// Notice the addition of `hashes` and `signatures`.
|
||||||
pub fn hash_and_sign_event<K>(entity_id: &str, key_pair: &K, value: &mut Value) -> Result<(), Error>
|
pub fn hash_and_sign_event<K>(
|
||||||
|
entity_id: &str,
|
||||||
|
key_pair: &K,
|
||||||
|
value: &mut Value,
|
||||||
|
version: &RoomVersionId,
|
||||||
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
K: KeyPair,
|
K: KeyPair,
|
||||||
{
|
{
|
||||||
@ -490,7 +509,7 @@ where
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut redacted = redact(value)?;
|
let mut redacted = redact(value, version)?;
|
||||||
|
|
||||||
sign_json(entity_id, key_pair, &mut redacted)?;
|
sign_json(entity_id, key_pair, &mut redacted)?;
|
||||||
|
|
||||||
@ -524,8 +543,10 @@ where
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use std::collections::HashMap;
|
/// # use std::collections::HashMap;
|
||||||
///
|
/// # use ruma_identifiers::RoomVersionId;
|
||||||
|
/// # use ruma_signatures::verify_event;
|
||||||
|
/// #
|
||||||
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
///
|
///
|
||||||
/// // Deserialize an event from JSON.
|
/// // Deserialize an event from JSON.
|
||||||
@ -561,10 +582,14 @@ where
|
|||||||
/// public_key_map.insert("domain".into(), public_key_set);
|
/// public_key_map.insert("domain".into(), public_key_set);
|
||||||
///
|
///
|
||||||
/// // Verify at least one signature for each entity in `public_key_map`.
|
/// // Verify at least one signature for each entity in `public_key_map`.
|
||||||
/// assert!(ruma_signatures::verify_event(&public_key_map, &value).is_ok());
|
/// assert!(verify_event(&public_key_map, &value, &RoomVersionId::Version6).is_ok());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn verify_event(public_key_map: &PublicKeyMap, value: &Value) -> Result<Verified, Error> {
|
pub fn verify_event(
|
||||||
let redacted = redact(value)?;
|
public_key_map: &PublicKeyMap,
|
||||||
|
value: &Value,
|
||||||
|
version: &RoomVersionId,
|
||||||
|
) -> Result<Verified, Error> {
|
||||||
|
let redacted = redact(value, version)?;
|
||||||
|
|
||||||
let map = match redacted {
|
let map = match redacted {
|
||||||
Value::Object(ref map) => map,
|
Value::Object(ref map) => map,
|
||||||
@ -694,7 +719,7 @@ fn canonical_json_with_fields_to_remove(value: &Value, fields: &[&str]) -> Resul
|
|||||||
/// * `value` contains a field called `hashes` that is not a JSON object.
|
/// * `value` contains a field called `hashes` that is not a JSON object.
|
||||||
/// * `value` contains a field called `signatures` that is not a JSON object.
|
/// * `value` contains a field called `signatures` that is not a JSON object.
|
||||||
/// * `value` is missing the `type` field or the field is not a JSON string.
|
/// * `value` is missing the `type` field or the field is not a JSON string.
|
||||||
pub fn redact(value: &Value) -> Result<Value, Error> {
|
pub fn redact(value: &Value, version: &RoomVersionId) -> Result<Value, Error> {
|
||||||
if !value.is_object() {
|
if !value.is_object() {
|
||||||
return Err(Error::new("JSON value must be a JSON object"));
|
return Err(Error::new("JSON value must be a JSON object"));
|
||||||
}
|
}
|
||||||
@ -709,7 +734,7 @@ pub fn redact(value: &Value) -> Result<Value, Error> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let allowed_content_keys = match event_type_value.as_str() {
|
let allowed_content_keys = match event_type_value.as_str() {
|
||||||
Some(event_type) => allowed_content_keys_for(event_type),
|
Some(event_type) => allowed_content_keys_for(event_type, version),
|
||||||
None => return Err(Error::new("field `type` in JSON value must be a JSON string")),
|
None => return Err(Error::new("field `type` in JSON value must be a JSON string")),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -176,6 +176,7 @@ mod test {
|
|||||||
|
|
||||||
use base64::{decode_config, STANDARD_NO_PAD};
|
use base64::{decode_config, STANDARD_NO_PAD};
|
||||||
use ring::signature::{Ed25519KeyPair as RingEd25519KeyPair, KeyPair as _};
|
use ring::signature::{Ed25519KeyPair as RingEd25519KeyPair, KeyPair as _};
|
||||||
|
use ruma_identifiers::RoomVersionId;
|
||||||
use serde_json::{from_str, json, to_string, to_value, Value};
|
use serde_json::{from_str, json, to_string, to_value, Value};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -422,7 +423,7 @@ mod test {
|
|||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
let mut value = from_str::<Value>(json).unwrap();
|
let mut value = from_str::<Value>(json).unwrap();
|
||||||
hash_and_sign_event("domain", &key_pair, &mut value).unwrap();
|
hash_and_sign_event("domain", &key_pair, &mut value, &RoomVersionId::Version5).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
to_string(&value).unwrap(),
|
to_string(&value).unwrap(),
|
||||||
@ -455,7 +456,7 @@ mod test {
|
|||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
let mut value = from_str::<Value>(json).unwrap();
|
let mut value = from_str::<Value>(json).unwrap();
|
||||||
hash_and_sign_event("domain", &key_pair, &mut value).unwrap();
|
hash_and_sign_event("domain", &key_pair, &mut value, &RoomVersionId::Version5).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
to_string(&value).unwrap(),
|
to_string(&value).unwrap(),
|
||||||
@ -496,6 +497,6 @@ mod test {
|
|||||||
}"#
|
}"#
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert!(verify_event(&public_key_map, &value).is_ok());
|
assert!(verify_event(&public_key_map, &value, &RoomVersionId::Version5).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user