Add content_hash and reference_hash functions and move all functions into a module.
This commit is contained in:
parent
9a56e2b0a6
commit
202b3ed402
223
src/functions.rs
Normal file
223
src/functions.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
//! Functions for signing and verifying JSON and events.
|
||||||
|
|
||||||
|
use std::error::Error as _;
|
||||||
|
|
||||||
|
use base64::{encode_config, STANDARD_NO_PAD};
|
||||||
|
use ring::digest::{digest, SHA256};
|
||||||
|
use serde_json::{to_string, Value};
|
||||||
|
|
||||||
|
use crate::{keys::KeyPair, signatures::Signature, verification::Verifier, Error};
|
||||||
|
|
||||||
|
/// The fields that are allowed to remain in an event during redaction.
|
||||||
|
static ALLOWED_KEYS: &[&str] = &[
|
||||||
|
"event_id",
|
||||||
|
"type",
|
||||||
|
"room_id",
|
||||||
|
"sender",
|
||||||
|
"state_key",
|
||||||
|
"content",
|
||||||
|
"hashes",
|
||||||
|
"signatures",
|
||||||
|
"depth",
|
||||||
|
"prev_events",
|
||||||
|
"prev_state",
|
||||||
|
"auth_events",
|
||||||
|
"origin",
|
||||||
|
"origin_server_ts",
|
||||||
|
"membership",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The fields of an *m.room.power_levels* event's `content` key that are allowed to remain in an
|
||||||
|
/// event during redaction.
|
||||||
|
static ALLOWED_POWER_LEVELS_KEYS: &[&str] = &[
|
||||||
|
"ban",
|
||||||
|
"events",
|
||||||
|
"events_default",
|
||||||
|
"kick",
|
||||||
|
"redact",
|
||||||
|
"state_default",
|
||||||
|
"users",
|
||||||
|
"users_default",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The fields to remove from a JSON object when converting JSON into the "canonical" form.
|
||||||
|
static CANONICAL_JSON_FIELDS_TO_REMOVE: &[&str] = &["signatures", "unsigned"];
|
||||||
|
|
||||||
|
/// The fields to remove from a JSON object when creating a content hash of an event.
|
||||||
|
static CONTENT_HASH_FIELDS_TO_REMOVE: &[&str] = &["hashes", "signatures", "unsigned"];
|
||||||
|
|
||||||
|
/// The fields to remove from a JSON object when creating a reference hash of an event.
|
||||||
|
static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "unsigned"];
|
||||||
|
|
||||||
|
/// Signs an arbitrary JSON object.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * key_pair: A cryptographic key pair used to sign the JSON.
|
||||||
|
/// * value: A JSON object to be signed according to the Matrix specification.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the JSON value is not a JSON object.
|
||||||
|
pub fn sign_json<K>(key_pair: &K, value: &Value) -> Result<Signature, Error>
|
||||||
|
where
|
||||||
|
K: KeyPair,
|
||||||
|
{
|
||||||
|
let json = to_canonical_json(value)?;
|
||||||
|
|
||||||
|
Ok(key_pair.sign(json.as_bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a JSON object into the "canonical" string form, suitable for signing.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * value: The `serde_json::Value` (JSON value) to convert.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the provided JSON value is not a JSON object.
|
||||||
|
pub fn to_canonical_json(value: &Value) -> Result<String, Error> {
|
||||||
|
to_canonical_json_with_fields_to_remove(value, CANONICAL_JSON_FIELDS_TO_REMOVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use a public key to verify a signature of a JSON object.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used.
|
||||||
|
/// * public_key: The public key of the key pair used to sign the JSON, as a series of bytes.
|
||||||
|
/// * signature: The `Signature` to verify.
|
||||||
|
/// * value: The `serde_json::Value` (JSON value) that was signed.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if verification fails.
|
||||||
|
pub fn verify_json<V>(
|
||||||
|
verifier: &V,
|
||||||
|
public_key: &[u8],
|
||||||
|
signature: &Signature,
|
||||||
|
value: &Value,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
V: Verifier,
|
||||||
|
{
|
||||||
|
verifier.verify_json(public_key, signature, to_canonical_json(value)?.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a *content hash* for the JSON representation of an event.
|
||||||
|
///
|
||||||
|
/// The content hash of an event covers the complete event including the unredacted contents. It is
|
||||||
|
/// used during federation and is described in the Matrix server-server specification.
|
||||||
|
pub fn content_hash(value: &Value) -> Result<String, Error> {
|
||||||
|
let json = to_canonical_json_with_fields_to_remove(value, CONTENT_HASH_FIELDS_TO_REMOVE)?;
|
||||||
|
|
||||||
|
let hash = digest(&SHA256, json.as_bytes());
|
||||||
|
|
||||||
|
Ok(encode_config(&hash, STANDARD_NO_PAD))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a *reference hash* for the JSON representation of an event.
|
||||||
|
///
|
||||||
|
/// The reference hash of an event covers the essential fields of an event, including content
|
||||||
|
/// hashes. It is used during federation and is described in the Matrix server-server
|
||||||
|
/// specification.
|
||||||
|
pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
||||||
|
let redacted_value = redact(value)?;
|
||||||
|
|
||||||
|
let json =
|
||||||
|
to_canonical_json_with_fields_to_remove(&redacted_value, REFERENCE_HASH_FIELDS_TO_REMOVE)?;
|
||||||
|
|
||||||
|
let hash = digest(&SHA256, json.as_bytes());
|
||||||
|
|
||||||
|
Ok(encode_config(&hash, STANDARD_NO_PAD))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal implementation detail of the canonical JSON algorithm. Allows customization of the
|
||||||
|
/// fields that will be removed before serializing.
|
||||||
|
fn to_canonical_json_with_fields_to_remove(
|
||||||
|
value: &Value,
|
||||||
|
fields: &[&str],
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
if !value.is_object() {
|
||||||
|
return Err(Error::new("JSON value must be a JSON object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut owned_value = value.clone();
|
||||||
|
|
||||||
|
{
|
||||||
|
let object = owned_value
|
||||||
|
.as_object_mut()
|
||||||
|
.expect("safe since we checked above");
|
||||||
|
|
||||||
|
for field in fields {
|
||||||
|
object.remove(*field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string(&owned_value).map_err(|error| Error::new(error.description()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redact 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> {
|
||||||
|
if !value.is_object() {
|
||||||
|
return Err(Error::new("JSON value must be a JSON object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut owned_value = value.clone();
|
||||||
|
|
||||||
|
let event = owned_value
|
||||||
|
.as_object_mut()
|
||||||
|
.expect("safe since we checked above");
|
||||||
|
|
||||||
|
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")),
|
||||||
|
};
|
||||||
|
|
||||||
|
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",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(content_value) = event.get_mut("content") {
|
||||||
|
if !content_value.is_object() {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Field `content` in JSON value must be a JSON object",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = content_value
|
||||||
|
.as_object_mut()
|
||||||
|
.expect("safe since we checked above");
|
||||||
|
|
||||||
|
for key in content.clone().keys() {
|
||||||
|
match event_type.as_ref() {
|
||||||
|
"m.room.member" if key != "membership" => content.remove(key),
|
||||||
|
"m.room.create" if key != "creator" => content.remove(key),
|
||||||
|
"m.room.join_rules" if key != "join_rules" => content.remove(key),
|
||||||
|
"m.room.power_levels" if !ALLOWED_POWER_LEVELS_KEYS.contains(&key.as_ref()) => {
|
||||||
|
content.remove(key)
|
||||||
|
}
|
||||||
|
"m.room.aliases" if key != "aliases" => content.remove(key),
|
||||||
|
"m.room.history_visibility" if key != "history_visibility" => content.remove(key),
|
||||||
|
_ => content.remove(key),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in event.clone().keys() {
|
||||||
|
if !ALLOWED_KEYS.contains(&key.as_ref()) {
|
||||||
|
event.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(owned_value)
|
||||||
|
}
|
74
src/lib.rs
74
src/lib.rs
@ -148,88 +148,18 @@ use std::{
|
|||||||
fmt::{Display, Formatter, Result as FmtResult},
|
fmt::{Display, Formatter, Result as FmtResult},
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde_json::{to_string, Value};
|
|
||||||
|
|
||||||
pub use url::Host;
|
pub use url::Host;
|
||||||
|
|
||||||
|
pub use functions::{content_hash, reference_hash, sign_json, to_canonical_json, verify_json};
|
||||||
pub use keys::{Ed25519KeyPair, KeyPair};
|
pub use keys::{Ed25519KeyPair, KeyPair};
|
||||||
pub use signatures::{Signature, SignatureMap, SignatureSet};
|
pub use signatures::{Signature, SignatureMap, SignatureSet};
|
||||||
pub use verification::{Ed25519Verifier, Verifier};
|
pub use verification::{Ed25519Verifier, Verifier};
|
||||||
|
|
||||||
|
mod functions;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod signatures;
|
mod signatures;
|
||||||
mod verification;
|
mod verification;
|
||||||
|
|
||||||
/// Signs an arbitrary JSON object.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
///
|
|
||||||
/// * key_pair: A cryptographic key pair used to sign the JSON.
|
|
||||||
/// * value: A JSON object to be signed according to the Matrix specification.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error if the JSON value is not a JSON object.
|
|
||||||
pub fn sign_json<K>(key_pair: &K, value: &Value) -> Result<Signature, Error>
|
|
||||||
where
|
|
||||||
K: KeyPair,
|
|
||||||
{
|
|
||||||
let json = to_canonical_json(value)?;
|
|
||||||
|
|
||||||
Ok(key_pair.sign(json.as_bytes()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a JSON object into the "canonical" string form, suitable for signing.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
///
|
|
||||||
/// * value: The `serde_json::Value` (JSON value) to convert.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error if the provided JSON value is not a JSON object.
|
|
||||||
pub fn to_canonical_json(value: &Value) -> Result<String, Error> {
|
|
||||||
if !value.is_object() {
|
|
||||||
return Err(Error::new("JSON value must be a JSON object"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut owned_value = value.clone();
|
|
||||||
|
|
||||||
{
|
|
||||||
let object = owned_value
|
|
||||||
.as_object_mut()
|
|
||||||
.expect("safe since we checked above");
|
|
||||||
object.remove("signatures");
|
|
||||||
object.remove("unsigned");
|
|
||||||
}
|
|
||||||
|
|
||||||
to_string(&owned_value).map_err(|error| Error::new(error.description()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use a public key to verify a signature of a JSON object.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
///
|
|
||||||
/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used.
|
|
||||||
/// * public_key: The public key of the key pair used to sign the JSON, as a series of bytes.
|
|
||||||
/// * signature: The `Signature` to verify.
|
|
||||||
/// * value: The `serde_json::Value` (JSON value) that was signed.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error if verification fails.
|
|
||||||
pub fn verify_json<V>(
|
|
||||||
verifier: &V,
|
|
||||||
public_key: &[u8],
|
|
||||||
signature: &Signature,
|
|
||||||
value: &Value,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
V: Verifier,
|
|
||||||
{
|
|
||||||
verifier.verify_json(public_key, signature, to_canonical_json(value)?.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error produced when ruma-signatures operations fail.
|
/// An error produced when ruma-signatures operations fail.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user