signatures: Fix verify_json and sign_json enforcing PDU size limits

These functions are used for request signatures too.
This commit is contained in:
Jonas Platte 2021-07-29 12:12:45 +02:00
parent b7d0970335
commit 3c76fa1492
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
4 changed files with 33 additions and 38 deletions

View File

@ -7,21 +7,6 @@ pub mod value;
use value::Object as CanonicalJsonObject;
/// Returns a canonical JSON string according to Matrix specification.
///
/// This function should be preferred over `serde_json::to_string` since it checks the size of the
/// canonical string. Matrix canonical JSON enforces a size limit of less than 65,535 when sending
/// PDU's for the server-server protocol.
pub fn to_string<T: Serialize>(val: &T) -> Result<String, Error> {
let s = serde_json::to_string(val).map_err(Error::SerDe)?;
if s.as_bytes().len() > 65_535 {
Err(Error::JsonSize)
} else {
Ok(s)
}
}
/// The set of possible errors when serializing to canonical JSON.
#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
@ -29,9 +14,6 @@ pub enum Error {
/// The numeric value failed conversion to js_int::Int.
IntConvert,
/// The `CanonicalJsonValue` being serialized was larger than 65,535 bytes.
JsonSize,
/// An error occurred while serializing/deserializing.
SerDe(JsonError),
}
@ -40,7 +22,6 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::IntConvert => f.write_str("number found is not a valid `js_int::Int`"),
Error::JsonSize => f.write_str("JSON is larger than 65,535 byte max"),
Error::SerDe(err) => write!(f, "serde Error: {}", err),
}
}
@ -67,10 +48,7 @@ mod tests {
use js_int::int;
use serde_json::{from_str as from_json_str, json, to_string as to_json_string};
use super::{
to_canonical_value, to_string as to_canonical_json_string, try_from_json_map,
value::CanonicalJsonValue,
};
use super::{to_canonical_value, try_from_json_map, value::CanonicalJsonValue};
#[test]
fn serialize_canon() {
@ -82,7 +60,7 @@ mod tests {
.try_into()
.unwrap();
let ser = to_canonical_json_string(&json).unwrap();
let ser = to_json_string(&json).unwrap();
let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();
assert_eq!(json, back);

View File

@ -20,7 +20,7 @@ pub mod urlencoded;
pub use buf::{json_to_buf, slice_to_buf};
pub use can_be_empty::{is_empty, CanBeEmpty};
pub use canonical_json::{
to_canonical_value, to_string as to_canonical_json_string, try_from_json_map,
to_canonical_value, try_from_json_map,
value::{CanonicalJsonValue, Object as CanonicalJsonObject},
Error as CanonicalJsonError,
};

View File

@ -24,6 +24,10 @@ pub enum Error {
/// [`SplitError`] wrapper.
#[error("Split error: {0}")]
SplitError(#[from] SplitError),
/// PDU was too large
#[error("PDU is larger than maximum of 65535 bytes")]
PduSize,
}
/// All errors related to JSON validation/parsing.
@ -69,11 +73,6 @@ pub enum JsonError {
with_key: String,
},
/// A derivative error from [`ruma_serde::CanonicalJsonError`],
/// captured here.
#[error("Canonical JSON error: {0}")]
CanonicalJson(#[from] ruma_serde::CanonicalJsonError),
/// A more generic JSON error from [`serde_json`].
#[error(transparent)]
Serde(#[from] serde_json::Error),

View File

@ -10,8 +10,8 @@ use std::{
use base64::{decode_config, encode_config, Config, STANDARD_NO_PAD, URL_SAFE_NO_PAD};
use ed25519_dalek::Digest;
use ruma_identifiers::{EventId, RoomVersionId, ServerNameBox, UserId};
use ruma_serde::{to_canonical_json_string, CanonicalJsonObject, CanonicalJsonValue};
use serde_json::from_str as from_json_str;
use ruma_serde::{CanonicalJsonObject, CanonicalJsonValue};
use serde_json::{from_str as from_json_str, to_string as to_json_string};
use sha2::Sha256;
use crate::{
@ -21,6 +21,8 @@ use crate::{
Error, JsonError, JsonType, ParseError, VerificationError,
};
const MAX_PDU_BYTES: usize = 65_535;
/// The fields that are allowed to remain in an event during redaction.
static ALLOWED_KEYS: &[&str] = &[
"event_id",
@ -149,7 +151,7 @@ where
let maybe_unsigned_entry = object.remove_entry("unsigned");
// Get the canonical JSON string.
let json = to_canonical_json_string(object).map_err(JsonError::CanonicalJson)?;
let json = to_json_string(object).map_err(JsonError::Serde)?;
// Sign the canonical JSON string.
let signature = key_pair.sign(json.as_bytes());
@ -204,6 +206,10 @@ pub fn canonical_json(object: &CanonicalJsonObject) -> Result<String, Error> {
/// Uses a set of public keys to verify a signed JSON object.
///
/// Unlike `content_hash` and `reference_hash`, this function does not report an error if the
/// canonical JSON is larger than 65535 bytes; this function may be used for requests that are
/// larger than just one PDU's maximum size.
///
/// # Parameters
///
/// * public_key_map: A map from entity identifiers to a map from key identifiers to public keys.
@ -334,8 +340,16 @@ where
/// # Parameters
///
/// object: A JSON object to generate a content hash for.
///
/// # Errors
///
/// Returns an error if the event is too large.
pub fn content_hash(object: &CanonicalJsonObject) -> Result<String, Error> {
let json = canonical_json_with_fields_to_remove(object, CONTENT_HASH_FIELDS_TO_REMOVE)?;
if json.len() > MAX_PDU_BYTES {
return Err(Error::PduSize);
}
let hash = Sha256::digest(json.as_bytes());
Ok(encode_config(&hash, STANDARD_NO_PAD))
@ -355,7 +369,7 @@ pub fn content_hash(object: &CanonicalJsonObject) -> Result<String, Error> {
///
/// # Errors
///
/// Returns an error if redaction fails.
/// Returns an error if the event is too large or redaction fails.
pub fn reference_hash(
value: &CanonicalJsonObject,
version: &RoomVersionId,
@ -364,6 +378,9 @@ pub fn reference_hash(
let json =
canonical_json_with_fields_to_remove(&redacted_value, REFERENCE_HASH_FIELDS_TO_REMOVE)?;
if json.len() > MAX_PDU_BYTES {
return Err(Error::PduSize);
}
let hash = Sha256::digest(json.as_bytes());
@ -671,8 +688,9 @@ struct SignatureAndPubkey<'a> {
public_key: &'a String,
}
/// Internal implementation detail of the canonical JSON algorithm. Allows customization of the
/// fields that will be removed before serializing.
/// Internal implementation detail of the canonical JSON algorithm.
///
/// Allows customization of the fields that will be removed before serializing.
fn canonical_json_with_fields_to_remove(
object: &CanonicalJsonObject,
fields: &[&str],
@ -683,7 +701,7 @@ fn canonical_json_with_fields_to_remove(
owned_object.remove(*field);
}
to_canonical_json_string(&owned_object).map_err(|e| Error::Json(e.into()))
to_json_string(&owned_object).map_err(|e| Error::Json(e.into()))
}
/// Redacts an event using the rules specified in the Matrix client-server specification.