Check content hash during event verification.

This commit is contained in:
Jimmy Cuadra 2019-07-12 02:16:26 -07:00
parent fe90cdabf9
commit a8b8e70c2d
3 changed files with 71 additions and 15 deletions

View File

@ -6,7 +6,12 @@ use base64::{decode_config, encode_config, STANDARD_NO_PAD};
use ring::digest::{digest, SHA256};
use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value};
use crate::{keys::KeyPair, signatures::SignatureMap, verification::Verifier, Error};
use crate::{
keys::KeyPair,
signatures::SignatureMap,
verification::{Verified, Verifier},
Error,
};
/// The fields that are allowed to remain in an event during redaction.
static ALLOWED_KEYS: &[&str] = &[
@ -121,7 +126,7 @@ where
signature_map = match map.remove("signatures") {
Some(signatures_value) => match signatures_value.as_object() {
Some(signatures) => from_value(Value::Object(signatures.clone()))?,
None => return Err(Error::new("Field `signatures` must be a JSON object")),
None => return Err(Error::new("field `signatures` must be a JSON object")),
},
None => HashMap::with_capacity(1),
};
@ -236,7 +241,7 @@ where
let signature_map: SignatureMap = match map.get("signatures") {
Some(signatures_value) => match signatures_value.as_object() {
Some(signatures) => from_value(Value::Object(signatures.clone()))?,
None => return Err(Error::new("Field `signatures` must be a JSON object")),
None => return Err(Error::new("field `signatures` must be a JSON object")),
},
None => return Err(Error::new("JSON object must contain a `signatures` field.")),
};
@ -481,7 +486,7 @@ where
match hashes_value.as_object_mut() {
Some(hashes) => hashes.insert("sha256".to_string(), Value::String(hash)),
None => return Err(Error::new("Field `hashes` must be a JSON object")),
None => return Err(Error::new("field `hashes` must be a JSON object")),
};
}
@ -504,6 +509,10 @@ where
/// will require a valid signature. All known public keys for a homeserver should be provided. The
/// first one found on the given event will be used.
///
/// If the `Ok` variant is returned by this function, it will contain a `Verified` value which
/// distinguishes an event with valid signatures and a matching content hash with an event with
/// only valid signatures. See the documetation for `Verified` for details.
///
/// # Parameters
///
/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used.
@ -564,7 +573,7 @@ pub fn verify_event<V>(
verifier: &V,
verify_key_map: &SignatureMap,
value: &Value,
) -> Result<(), Error>
) -> Result<Verified, Error>
where
V: Verifier,
{
@ -575,10 +584,24 @@ where
_ => return Err(Error::new("JSON value must be a JSON object")),
};
let hash = match map.get("hashes") {
Some(hashes_value) => match hashes_value.as_object() {
Some(hashes) => match hashes.get("sha256") {
Some(hash_value) => match hash_value.as_str() {
Some(hash) => hash,
None => return Err(Error::new("sha256 hash must be a JSON string")),
},
None => return Err(Error::new("field `hashes` must be a JSON object")),
},
None => return Err(Error::new("event missing sha256 hash")),
},
None => return Err(Error::new("field `hashes` must be present")),
};
let signature_map: SignatureMap = match map.get("signatures") {
Some(signatures_value) => match signatures_value.as_object() {
Some(signatures) => from_value(Value::Object(signatures.clone()))?,
None => return Err(Error::new("Field `signatures` must be a JSON object")),
None => return Err(Error::new("field `signatures` must be a JSON object")),
},
None => return Err(Error::new("JSON object must contain a `signatures` field.")),
};
@ -638,7 +661,13 @@ where
)?;
}
Ok(())
let calculated_hash = content_hash(value)?;
if hash == calculated_hash {
Ok(Verified::All)
} else {
Ok(Verified::Signatures)
}
}
/// Internal implementation detail of the canonical JSON algorithm. Allows customization of the
@ -666,11 +695,20 @@ fn to_canonical_json_with_fields_to_remove(
to_string(&owned_value).map_err(Error::from)
}
/// Redact the JSON representation of an event using the rules specified in the Matrix
/// Redacts the JSON representation of an event using the rules specified in the Matrix
/// client-server specification.
///
/// This is part of the process of signing an event.
fn redact(value: &Value) -> Result<Value, Error> {
///
/// Redaction is also suggested when a verifying an event with `verify_event` returns
/// `Verified::Signatures`. See the documentation for `Verified` for details.
///
/// Returns a new `serde_json::Value` with all applicable fields redacted.
///
/// # Parameters
///
/// * value: A JSON object to redact.
pub fn redact(value: &Value) -> Result<Value, Error> {
if !value.is_object() {
return Err(Error::new("JSON value must be a JSON object"));
}
@ -683,14 +721,14 @@ fn redact(value: &Value) -> Result<Value, Error> {
let event_type_value = match event.get("type") {
Some(event_type_value) => event_type_value,
None => return Err(Error::new("Field `type` in JSON value must be present")),
None => return Err(Error::new("field `type` in JSON value must be present")),
};
let event_type = match event_type_value.as_str() {
Some(event_type) => event_type.to_string(),
None => {
return Err(Error::new(
"Field `type` in JSON value must be a JSON string",
"field `type` in JSON value must be a JSON string",
))
}
};
@ -700,7 +738,7 @@ fn redact(value: &Value) -> Result<Value, Error> {
Value::Object(ref mut map) => map,
_ => {
return Err(Error::new(
"Field `content` in JSON value must be a JSON object",
"field `content` in JSON value must be a JSON object",
))
}
};

View File

@ -101,12 +101,12 @@ use std::{
};
pub use functions::{
content_hash, hash_and_sign_event, reference_hash, sign_json, to_canonical_json, verify_event,
verify_json,
content_hash, hash_and_sign_event, redact, reference_hash, sign_json, to_canonical_json,
verify_event, verify_json,
};
pub use keys::{Ed25519KeyPair, KeyPair};
pub use signatures::{Signature, SignatureMap, SignatureSet};
pub use verification::{Ed25519Verifier, Verifier};
pub use verification::{Ed25519Verifier, Verified, Verifier};
mod functions;
mod keys;

View File

@ -42,3 +42,21 @@ impl Verifier for Ed25519Verifier {
.map_err(|_| Error::new("signature verification failed"))
}
}
/// A value returned when an event is successfully verified.
///
/// Event verification involves verifying both signatures and a content hash. It is possible for
/// the signatures on an event to be valid, but for the hash to be different than the one
/// calculated during verification. This is not necessarily an error condition, as it may indicate
/// that the event has been redacted. In this case, receiving homeservers should store a redacted
/// version of the event.
#[derive(Debug, Clone, Copy, Hash, PartialEq)]
pub enum Verified {
/// All signatures are valid and the content hashes match.
All,
/// All signatures are valid but the content hashes don't match.
///
/// This may indicate a redacted event.
Signatures,
}