diff --git a/src/functions.rs b/src/functions.rs index f1b7823a..019bfa20 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -1,8 +1,10 @@ //! Functions for signing and verifying JSON and events. +use std::{collections::HashMap, hash::BuildHasher}; + use base64::{encode_config, STANDARD_NO_PAD}; use ring::digest::{digest, SHA256}; -use serde_json::{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::{ keys::KeyPair, @@ -134,7 +136,7 @@ pub fn to_canonical_json(value: &Value) -> Result { 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. +/// Uses a public key to verify a signature of a JSON object. /// /// # Parameters /// @@ -267,6 +269,94 @@ where Ok(()) } +/// Uses a set of public keys to verify a signed JSON representation of an event. +/// +/// Some room versions may require signatures from multiple homeservers, so this function takes a +/// map of servers to sets of public keys. For each homeserver present in the map, this function +/// 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. +/// +/// # Parameters +/// +/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used. +/// * verify_key_map: A map of server names to a map of key identifiers to public keys. Server +/// names are the hostname or IP of a homeserver (e.g. "example.com") for which a signature must be +/// verified. Key identifiers for each server (e.g. "ed25519:1") then map to their respective public +/// keys. +/// * value: The `serde_json::Value` (JSON value) of the event that was signed. +pub fn verify_event( + verifier: &V, + verify_key_map: HashMap<&str, HashMap<&str, &[u8], S>, S>, + value: &Value, +) -> Result<(), Error> +where + V: Verifier, + S: BuildHasher, +{ + let redacted = redact(value)?; + + let map = match redacted { + Value::Object(ref map) => map, + _ => return Err(Error::new("JSON value must be a JSON object")), + }; + + 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("JSON object must contain a `signatures` field.")), + }; + + for (server_name, verify_keys) in verify_key_map { + let signature_set = match signature_map.get(server_name)? { + Some(set) => set, + None => { + return Err(Error::new(format!( + "no signatures found for server `{}`", + server_name + ))) + } + }; + + let mut maybe_signature = None; + let mut maybe_verify_key = None; + + for (key_id, verify_key) in verify_keys { + if let Some(signature) = signature_set.get(key_id) { + maybe_signature = Some(signature); + maybe_verify_key = Some(verify_key); + + break; + } + } + + let signature = match maybe_signature { + Some(signature) => signature, + None => { + return Err(Error::new( + "event is not signed with any of the given verify keys", + )) + } + }; + + let verify_key = match maybe_verify_key { + Some(verify_key) => verify_key, + None => { + return Err(Error::new( + "event is not signed with any of the given verify keys", + )) + } + }; + + let canonical_json = from_str(&to_canonical_json(&redacted)?)?; + + verify_json(verifier, verify_key, signature, &canonical_json)?; + } + + Ok(()) +} + /// 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( diff --git a/src/lib.rs b/src/lib.rs index 28d506f3..5add97fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,7 @@ //! A homeserver signs JSON with a key pair: //! //! ```rust,no_run -//! # use ruma_signatures::{self, KeyPair}; -//! # use serde_json; +//! # use ruma_signatures::KeyPair; //! # let public_key = [0; 32]; //! # let private_key = [0; 32]; //! // Create an Ed25519 key pair. @@ -55,8 +54,7 @@ //! same: //! //! ```rust,no_run -//! # use ruma_signatures::{self, KeyPair}; -//! # use serde_json; +//! # use ruma_signatures::KeyPair; //! # let public_key = [0; 32]; //! # let private_key = [0; 32]; //! // Create an Ed25519 key pair. @@ -120,8 +118,6 @@ //! A client application or another homeserver can verify a signature on arbitrary JSON: //! //! ```rust,no_run -//! # use ruma_signatures; -//! # use serde_json; //! # let public_key = [0; 32]; //! # let signature_bytes = [0, 32]; //! let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap(); @@ -130,7 +126,24 @@ //! assert!(ruma_signatures::verify_json(&verifier, &public_key, &signature, &value).is_ok()); //! ``` //! -//! Verifying signatures of Matrix events is not yet implemented by ruma-signatures. +//! Verifying the signatures on a Matrix event are slightly different: +//! +//! ```rust,no_run +//! # use std::collections::HashMap; +//! # let public_key_vec = Vec::new(); +//! # let public_key = public_key_vec.as_slice(); +//! # let event_json = ""; +//! let mut verify_key_map = HashMap::new(); +//! let mut example_server_keys = HashMap::new(); +//! example_server_keys.insert("ed25519:1", public_key); // public_key: &[u8] +//! verify_key_map.insert("example.com", example_server_keys); +//! let value = serde_json::from_str(event_json).unwrap(); +//! let verifier = ruma_signatures::Ed25519Verifier; +//! assert!(ruma_signatures::verify_event(&verifier, verify_key_map, &value).is_ok()); +//! ``` +//! +//! See the documentation for `verify_event` for details on what verification entails and the +//! `verify_key_map` parameter. //! //! # Signature sets and maps //! @@ -156,9 +169,6 @@ //! This inner object can be created by serializing a `SignatureSet`: //! //! ```rust,no_run -//! # use ruma_signatures; -//! # use serde; -//! # use serde_json; //! # let signature_bytes = [0, 32]; //! let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap(); //! let mut signature_set = ruma_signatures::SignatureSet::new(); @@ -173,9 +183,6 @@ //! created like this: //! //! ```rust,no_run -//! # use ruma_signatures; -//! # use serde; -//! # use serde_json; //! # let signature_bytes = [0, 32]; //! let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap(); //! let mut signature_set = ruma_signatures::SignatureSet::new(); @@ -222,7 +229,8 @@ use std::{ pub use url::Host; pub use functions::{ - content_hash, hash_and_sign_event, reference_hash, sign_json, to_canonical_json, verify_json, + content_hash, hash_and_sign_event, reference_hash, sign_json, to_canonical_json, verify_event, + verify_json, }; pub use keys::{Ed25519KeyPair, KeyPair}; pub use signatures::{Signature, SignatureMap, SignatureSet};