Move code examples onto relevant functions/types and rewrite the crate
documentation to be a more general overview.
This commit is contained in:
parent
bca31bfbd4
commit
f1d9eba728
189
src/functions.rs
189
src/functions.rs
@ -71,6 +71,45 @@ static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "uns
|
|||||||
/// * `value` is not a JSON object.
|
/// * `value` 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.
|
||||||
/// * `server_name` cannot be parsed as a valid host.
|
/// * `server_name` cannot be parsed as a valid host.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// A homeserver signs JSON with a key pair:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ruma_signatures::KeyPair as _;
|
||||||
|
///
|
||||||
|
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
|
/// const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
||||||
|
///
|
||||||
|
/// let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
/// let private_key = base64::decode_config(&PRIVATE_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create an Ed25519 key pair.
|
||||||
|
/// let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
||||||
|
/// &public_key,
|
||||||
|
/// &private_key,
|
||||||
|
/// "1".to_string(), // The "version" of the key.
|
||||||
|
/// ).unwrap();
|
||||||
|
///
|
||||||
|
/// // Deserialize some JSON.
|
||||||
|
/// let mut value = serde_json::from_str("{}").unwrap();
|
||||||
|
///
|
||||||
|
/// // Sign the JSON with the key pair.
|
||||||
|
/// assert!(ruma_signatures::sign_json("example.com", &key_pair, &mut value).is_ok());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This will modify the JSON from an empty object to a structure like this:
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "signatures": {
|
||||||
|
/// "example.com": {
|
||||||
|
/// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn sign_json<K>(server_name: &str, key_pair: &K, value: &mut Value) -> Result<(), Error>
|
pub fn sign_json<K>(server_name: &str, key_pair: &K, value: &mut Value) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
K: KeyPair,
|
K: KeyPair,
|
||||||
@ -148,6 +187,30 @@ pub fn to_canonical_json(value: &Value) -> Result<String, Error> {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if verification fails.
|
/// Returns an error if verification fails.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
|
/// const SIGNATURE_BYTES: &str =
|
||||||
|
/// "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
||||||
|
///
|
||||||
|
/// // Decode the public key used to generate the signature into raw bytes.
|
||||||
|
/// let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create a `Signature` from the raw bytes of the signature.
|
||||||
|
/// let signature_bytes = base64::decode_config(&SIGNATURE_BYTES, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
/// let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap();
|
||||||
|
///
|
||||||
|
/// // Deserialize the signed JSON.
|
||||||
|
/// let value = serde_json::from_str("{}").unwrap();
|
||||||
|
///
|
||||||
|
/// // Create the verifier for the Ed25519 algorithm.
|
||||||
|
/// let verifier = ruma_signatures::Ed25519Verifier;
|
||||||
|
///
|
||||||
|
/// // Verify the signature.
|
||||||
|
/// assert!(ruma_signatures::verify_json(&verifier, &public_key, &signature, &value).is_ok());
|
||||||
|
/// ```
|
||||||
pub fn verify_json<V>(
|
pub fn verify_json<V>(
|
||||||
verifier: &V,
|
verifier: &V,
|
||||||
public_key: &[u8],
|
public_key: &[u8],
|
||||||
@ -230,6 +293,77 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
|||||||
/// * `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.
|
||||||
/// * `server_name` cannot be parsed as a valid host.
|
/// * `server_name` cannot be parsed as a valid host.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ruma_signatures::KeyPair as _;
|
||||||
|
///
|
||||||
|
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
|
/// const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
||||||
|
///
|
||||||
|
/// let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
/// let private_key = base64::decode_config(&PRIVATE_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create an Ed25519 key pair.
|
||||||
|
/// let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
||||||
|
/// &public_key,
|
||||||
|
/// &private_key,
|
||||||
|
/// "1".to_string(), // The "version" of the key.
|
||||||
|
/// ).unwrap();
|
||||||
|
///
|
||||||
|
/// // Deserialize an event from JSON.
|
||||||
|
/// let mut value = serde_json::from_str(
|
||||||
|
/// r#"{
|
||||||
|
/// "room_id": "!x:domain",
|
||||||
|
/// "sender": "@a:domain",
|
||||||
|
/// "origin": "domain",
|
||||||
|
/// "origin_server_ts": 1000000,
|
||||||
|
/// "signatures": {},
|
||||||
|
/// "hashes": {},
|
||||||
|
/// "type": "X",
|
||||||
|
/// "content": {},
|
||||||
|
/// "prev_events": [],
|
||||||
|
/// "auth_events": [],
|
||||||
|
/// "depth": 3,
|
||||||
|
/// "unsigned": {
|
||||||
|
/// "age_ts": 1000000
|
||||||
|
/// }
|
||||||
|
/// }"#
|
||||||
|
/// ).unwrap();
|
||||||
|
///
|
||||||
|
/// // Hash and sign the JSON with the key pair.
|
||||||
|
/// assert!(ruma_signatures::hash_and_sign_event("example.com", &key_pair, &mut value).is_ok());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This will modify the JSON from the structure shown to a structure like this:
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "auth_events": [],
|
||||||
|
/// "content": {},
|
||||||
|
/// "depth": 3,
|
||||||
|
/// "hashes": {
|
||||||
|
/// "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
|
||||||
|
/// },
|
||||||
|
/// "origin": "domain",
|
||||||
|
/// "origin_server_ts": 1000000,
|
||||||
|
/// "prev_events": [],
|
||||||
|
/// "room_id": "!x:domain",
|
||||||
|
/// "sender": "@a:domain",
|
||||||
|
/// "signatures": {
|
||||||
|
/// "domain": {
|
||||||
|
/// "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// "type": "X",
|
||||||
|
/// "unsigned": {
|
||||||
|
/// "age_ts": 1000000
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Notice the addition of `hashes` and `signatures`.
|
||||||
pub fn hash_and_sign_event<K>(
|
pub fn hash_and_sign_event<K>(
|
||||||
server_name: &str,
|
server_name: &str,
|
||||||
key_pair: &K,
|
key_pair: &K,
|
||||||
@ -272,18 +406,69 @@ where
|
|||||||
/// Uses a set of public keys to verify a signed JSON representation of an event.
|
/// 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
|
/// 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
|
/// map from 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
|
/// 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.
|
/// first one found on the given event will be used.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
///
|
///
|
||||||
/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used.
|
/// * 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
|
/// * verify_key_map: A map from server names to a map from 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
|
/// 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
|
/// verified. Key identifiers for each server (e.g. "ed25519:1") then map to their respective public
|
||||||
/// keys.
|
/// keys.
|
||||||
/// * value: The `serde_json::Value` (JSON value) of the event that was signed.
|
/// * value: The `serde_json::Value` (JSON value) of the event that was signed.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use std::collections::HashMap;
|
||||||
|
///
|
||||||
|
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
|
///
|
||||||
|
/// // Decode the public key used to generate the signature into raw bytes.
|
||||||
|
/// let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create a map from key ID to public key.
|
||||||
|
/// let mut example_server_keys = HashMap::new();
|
||||||
|
/// example_server_keys.insert("ed25519:1", public_key.as_slice());
|
||||||
|
///
|
||||||
|
/// // Insert the public keys into a map keyed by server name.
|
||||||
|
/// let mut verify_key_map = HashMap::new();
|
||||||
|
/// verify_key_map.insert("domain", example_server_keys);
|
||||||
|
///
|
||||||
|
/// // Deserialize an event from JSON.
|
||||||
|
/// let value = serde_json::from_str(
|
||||||
|
/// r#"{
|
||||||
|
/// "auth_events": [],
|
||||||
|
/// "content": {},
|
||||||
|
/// "depth": 3,
|
||||||
|
/// "hashes": {
|
||||||
|
/// "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
|
||||||
|
/// },
|
||||||
|
/// "origin": "domain",
|
||||||
|
/// "origin_server_ts": 1000000,
|
||||||
|
/// "prev_events": [],
|
||||||
|
/// "room_id": "!x:domain",
|
||||||
|
/// "sender": "@a:domain",
|
||||||
|
/// "signatures": {
|
||||||
|
/// "domain": {
|
||||||
|
/// "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// "type": "X",
|
||||||
|
/// "unsigned": {
|
||||||
|
/// "age_ts": 1000000
|
||||||
|
/// }
|
||||||
|
/// }"#
|
||||||
|
/// ).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create the verifier for the Ed25519 algorithm.
|
||||||
|
/// let verifier = ruma_signatures::Ed25519Verifier;
|
||||||
|
///
|
||||||
|
/// // Verify at least one signature for each server in `verify_key_map`.
|
||||||
|
/// assert!(ruma_signatures::verify_event(&verifier, verify_key_map, &value).is_ok());
|
||||||
|
/// ```
|
||||||
pub fn verify_event<V, S>(
|
pub fn verify_event<V, S>(
|
||||||
verifier: &V,
|
verifier: &V,
|
||||||
verify_key_map: HashMap<&str, HashMap<&str, &[u8], S>, S>,
|
verify_key_map: HashMap<&str, HashMap<&str, &[u8], S>, S>,
|
||||||
|
284
src/lib.rs
284
src/lib.rs
@ -3,218 +3,30 @@
|
|||||||
//!
|
//!
|
||||||
//! Digital signatures are used by Matrix homeservers to verify the authenticity of events in the
|
//! Digital signatures are used by Matrix homeservers to verify the authenticity of events in the
|
||||||
//! Matrix system, as well as requests between homeservers for federation. Each homeserver has one
|
//! Matrix system, as well as requests between homeservers for federation. Each homeserver has one
|
||||||
//! or more signing key pairs which it uses to sign all events and federation requests. Matrix
|
//! or more signing key pairs (sometimes referred to as "verify keys") which it uses to sign all
|
||||||
//! clients and other Matrix homeservers can ask the homeserver for its public keys and use those
|
//! events and federation requests. Matrix clients and other Matrix homeservers can ask the
|
||||||
//! keys to verify the signed data.
|
//! homeserver for its public keys and use those keys to verify the signed data.
|
||||||
//!
|
//!
|
||||||
//! Each signing key pair has an identifier, which consists of the name of the digital signature
|
//! Each signing key pair has an identifier, which consists of the name of the digital signature
|
||||||
//! algorithm it uses and a "version" string, separated by a colon. The version is an arbitrary
|
//! algorithm it uses and a "version" string, separated by a colon. The version is an arbitrary
|
||||||
//! identifier used to distinguish key pairs using the same algorithm from the same homeserver.
|
//! identifier used to distinguish key pairs using the same algorithm from the same homeserver.
|
||||||
//!
|
//!
|
||||||
//! In JSON representations, signatures and hashes appear as Base64-encoded strings, using the
|
//! Arbitrary JSON objects can be signed as well as JSON representations of Matrix events. In both
|
||||||
|
//! cases, the signatures are stored within the JSON object itself under a `signatures` key. Events
|
||||||
|
//! are also required to contain hashes of their content, which are similarly stored within the
|
||||||
|
//! hashed JSON object under a `hashes` key.
|
||||||
|
//!
|
||||||
|
//! In JSON representations, both signatures and hashes appear as Base64-encoded strings, using the
|
||||||
//! standard character set, without padding.
|
//! standard character set, without padding.
|
||||||
//!
|
//!
|
||||||
//! # Signing JSON
|
//! # Signatures, maps, and sets
|
||||||
//!
|
//!
|
||||||
//! A homeserver signs JSON with a key pair:
|
//! An individual signature is represented in ruma-signatures by the `Signature` type. This type
|
||||||
|
//! encapsulates the raw bytes of the signature, the identifier for the signing key used, and the
|
||||||
|
//! algorithm used to create the signature.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! As mentioned, signatures that a homeserver has added to an event are stored in a JSON object
|
||||||
//! use ruma_signatures::KeyPair as _;
|
//! under the `signatures` key in the event's JSON representation:
|
||||||
//!
|
|
||||||
//! const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
|
||||||
//! const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
|
||||||
//!
|
|
||||||
//! let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//! let private_key = base64::decode_config(&PRIVATE_KEY, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create an Ed25519 key pair.
|
|
||||||
//! let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
|
||||||
//! &public_key,
|
|
||||||
//! &private_key,
|
|
||||||
//! "1".to_string(), // The "version" of the key.
|
|
||||||
//! ).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Deserialize some JSON.
|
|
||||||
//! let mut value = serde_json::from_str("{}").unwrap();
|
|
||||||
//!
|
|
||||||
//! // Sign the JSON with the key pair.
|
|
||||||
//! assert!(ruma_signatures::sign_json("example.com", &key_pair, &mut value).is_ok());
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! This will modify the JSON from an empty object to a structure like this:
|
|
||||||
//!
|
|
||||||
//! ```json
|
|
||||||
//! {
|
|
||||||
//! "signatures": {
|
|
||||||
//! "example.com": {
|
|
||||||
//! "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Hashing and signing Matrix events
|
|
||||||
//!
|
|
||||||
//! Signing an event uses a more involved process than signing arbitrary JSON because events can be
|
|
||||||
//! redacted and signatures need to remain valid even if data is removed from an event later.
|
|
||||||
//! Homeservers are required to generate hashes of event contents and sign events before exchanging
|
|
||||||
//! them with other homeservers. Although the algorithm for hashing and signing an event is more
|
|
||||||
//! complicated than for signing arbitrary JSON, the interface to a user of ruma-signatures is the
|
|
||||||
//! same:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use ruma_signatures::KeyPair as _;
|
|
||||||
//!
|
|
||||||
//! const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
|
||||||
//! const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
|
||||||
//!
|
|
||||||
//! let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//! let private_key = base64::decode_config(&PRIVATE_KEY, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create an Ed25519 key pair.
|
|
||||||
//! let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
|
||||||
//! &public_key,
|
|
||||||
//! &private_key,
|
|
||||||
//! "1".to_string(), // The "version" of the key.
|
|
||||||
//! ).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Deserialize an event from JSON.
|
|
||||||
//! let mut value = serde_json::from_str(
|
|
||||||
//! r#"{
|
|
||||||
//! "room_id": "!x:domain",
|
|
||||||
//! "sender": "@a:domain",
|
|
||||||
//! "origin": "domain",
|
|
||||||
//! "origin_server_ts": 1000000,
|
|
||||||
//! "signatures": {},
|
|
||||||
//! "hashes": {},
|
|
||||||
//! "type": "X",
|
|
||||||
//! "content": {},
|
|
||||||
//! "prev_events": [],
|
|
||||||
//! "auth_events": [],
|
|
||||||
//! "depth": 3,
|
|
||||||
//! "unsigned": {
|
|
||||||
//! "age_ts": 1000000
|
|
||||||
//! }
|
|
||||||
//! }"#
|
|
||||||
//! ).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Hash and sign the JSON with the key pair.
|
|
||||||
//! assert!(ruma_signatures::hash_and_sign_event("example.com", &key_pair, &mut value).is_ok());
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! This will modify the JSON from the structure shown to a structure like this:
|
|
||||||
//!
|
|
||||||
//! ```json
|
|
||||||
//! {
|
|
||||||
//! "auth_events": [],
|
|
||||||
//! "content": {},
|
|
||||||
//! "depth": 3,
|
|
||||||
//! "hashes": {
|
|
||||||
//! "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
|
|
||||||
//! },
|
|
||||||
//! "origin": "domain",
|
|
||||||
//! "origin_server_ts": 1000000,
|
|
||||||
//! "prev_events": [],
|
|
||||||
//! "room_id": "!x:domain",
|
|
||||||
//! "sender": "@a:domain",
|
|
||||||
//! "signatures": {
|
|
||||||
//! "domain": {
|
|
||||||
//! "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
|
|
||||||
//! }
|
|
||||||
//! },
|
|
||||||
//! "type": "X",
|
|
||||||
//! "unsigned": {
|
|
||||||
//! "age_ts": 1000000
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Notice the addition of `hashes` and `signatures`.
|
|
||||||
//!
|
|
||||||
//! # Verifying signatures
|
|
||||||
//!
|
|
||||||
//! A client application or another homeserver can verify a signature on arbitrary JSON:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
|
||||||
//! const SIGNATURE_BYTES: &str =
|
|
||||||
//! "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
|
||||||
//!
|
|
||||||
//! // Decode the public key used to generate the signature into raw bytes.
|
|
||||||
//! let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create a `Signature` from the raw bytes of the signature.
|
|
||||||
//! let signature_bytes = base64::decode_config(&SIGNATURE_BYTES, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//! let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Deserialize the signed JSON.
|
|
||||||
//! let value = serde_json::from_str("{}").unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create the verifier for the Ed25519 algorithm.
|
|
||||||
//! let verifier = ruma_signatures::Ed25519Verifier;
|
|
||||||
//!
|
|
||||||
//! // Verify the signature.
|
|
||||||
//! assert!(ruma_signatures::verify_json(&verifier, &public_key, &signature, &value).is_ok());
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Verifying the signatures on a Matrix event are slightly different:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use std::collections::HashMap;
|
|
||||||
//!
|
|
||||||
//! const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
|
||||||
//!
|
|
||||||
//! // Decode the public key used to generate the signature into raw bytes.
|
|
||||||
//! let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create a map of key ID to public key.
|
|
||||||
//! let mut example_server_keys = HashMap::new();
|
|
||||||
//! example_server_keys.insert("ed25519:1", public_key.as_slice());
|
|
||||||
//!
|
|
||||||
//! // Insert the public keys into a map keyed by server name.
|
|
||||||
//! let mut verify_key_map = HashMap::new();
|
|
||||||
//! verify_key_map.insert("domain", example_server_keys);
|
|
||||||
//!
|
|
||||||
//! // Deserialize an event from JSON.
|
|
||||||
//! let value = serde_json::from_str(
|
|
||||||
//! r#"{
|
|
||||||
//! "auth_events": [],
|
|
||||||
//! "content": {},
|
|
||||||
//! "depth": 3,
|
|
||||||
//! "hashes": {
|
|
||||||
//! "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
|
|
||||||
//! },
|
|
||||||
//! "origin": "domain",
|
|
||||||
//! "origin_server_ts": 1000000,
|
|
||||||
//! "prev_events": [],
|
|
||||||
//! "room_id": "!x:domain",
|
|
||||||
//! "sender": "@a:domain",
|
|
||||||
//! "signatures": {
|
|
||||||
//! "domain": {
|
|
||||||
//! "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
|
|
||||||
//! }
|
|
||||||
//! },
|
|
||||||
//! "type": "X",
|
|
||||||
//! "unsigned": {
|
|
||||||
//! "age_ts": 1000000
|
|
||||||
//! }
|
|
||||||
//! }"#
|
|
||||||
//! ).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create the verifier for the Ed25519 algorithm.
|
|
||||||
//! let verifier = ruma_signatures::Ed25519Verifier;
|
|
||||||
//!
|
|
||||||
//! // Verify at least one signature for each server in `verify_key_map`.
|
|
||||||
//! 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
|
|
||||||
//!
|
|
||||||
//! Signatures that a homeserver has added to an event are stored in a JSON object under the
|
|
||||||
//! "signatures" key in the event's JSON representation:
|
|
||||||
//!
|
//!
|
||||||
//! ```json
|
//! ```json
|
||||||
//! {
|
//! {
|
||||||
@ -228,56 +40,34 @@
|
|||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The keys inside the "signatures" object are the hostnames of homeservers that have added
|
//! The value of the the `signatures` key is represented in ruma-signatures by the `SignatureMap`
|
||||||
//! signatures. Within each of those objects are a set of signatures, keyed by the signing key
|
//! type. This type maps the name of a homeserver to a set of its signatures for the containing
|
||||||
//! pair's identifier.
|
//! data. The set of signatures for each homeserver (which appears as a map from key ID to signature
|
||||||
|
//! in the JSON representation) is represented in ruma-signatures by the `SignatureSet` type. Both
|
||||||
|
//! `SignatureMap`s and `SignatureSet`s can be serialized and deserialized with
|
||||||
|
//! [https://serde.rs/](Serde).
|
||||||
//!
|
//!
|
||||||
//! This inner object can be created by serializing a `SignatureSet`:
|
//! # Signing and hashing
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! To sign an arbitrary JSON object, use the `sign_json` function. See the documentation of this
|
||||||
//! const SIGNATURE_BYTES: &str =
|
//! function for more details and a full example of use.
|
||||||
//! "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
|
||||||
//!
|
//!
|
||||||
//! // Create a `Signature` from the raw bytes of the signature.
|
//! Signing an event uses a more complicated process than signing arbitrary JSON, because events can
|
||||||
//! let signature_bytes = base64::decode_config(&SIGNATURE_BYTES, base64::STANDARD_NO_PAD).unwrap();
|
//! be redacted, and signatures need to remain valid even if data is removed from an event later.
|
||||||
//! let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap();
|
//! Homeservers are required to generate hashes of event contents as well as signing events before
|
||||||
|
//! exchanging them with other homeservers. Although the algorithm for hashing and signing an event
|
||||||
|
//! is more complicated than for signing arbitrary JSON, the interface to a user of ruma-signatures
|
||||||
|
//! is the same. To hash and sign an event, use the `hash_and_sign_event` function. See the
|
||||||
|
//! documentation of this function for more details and a full example of use.
|
||||||
//!
|
//!
|
||||||
//! // Create a `SignatureSet` and insert the signature into it.
|
//! # Verifying signatures and hashes
|
||||||
//! let mut signature_set = ruma_signatures::SignatureSet::new();
|
|
||||||
//! signature_set.insert(signature);
|
|
||||||
//!
|
//!
|
||||||
//! // Serialize the set to JSON.
|
//! When a homeserver receives data from another homeserver via the federation, it's necessary to
|
||||||
//! assert!(serde_json::to_string(&signature_set).is_ok());
|
//! verify the authenticity and integrity of the data by verifying their signatures.
|
||||||
//! ```
|
|
||||||
//!
|
//!
|
||||||
//! This code produces the object under the "example.com" key in the preceeding JSON. Similarly,
|
//! To verify a signature on arbitrary JSON, use the `verify_json` function. To verify the
|
||||||
//! a `SignatureSet` can be produced by deserializing JSON that follows this form.
|
//! signatures and hashes on an event, use the `verify_event` function. See the documentation for
|
||||||
//!
|
//! these respective functions for more details and full examples of use.
|
||||||
//! The outer object (the map of server names to signature sets) is a `SignatureMap` value and
|
|
||||||
//! created like this:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! const SIGNATURE_BYTES: &str =
|
|
||||||
//! "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
|
||||||
//!
|
|
||||||
//! // Create a `Signature` from the raw bytes of the signature.
|
|
||||||
//! let signature_bytes = base64::decode_config(&SIGNATURE_BYTES, base64::STANDARD_NO_PAD).unwrap();
|
|
||||||
//! let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Create a `SignatureSet` and insert the signature into it.
|
|
||||||
//! let mut signature_set = ruma_signatures::SignatureSet::new();
|
|
||||||
//! signature_set.insert(signature);
|
|
||||||
//!
|
|
||||||
//! // Create a `SignatureMap` and insert the set into it, keyed by the homeserver name.
|
|
||||||
//! let mut signature_map = ruma_signatures::SignatureMap::new();
|
|
||||||
//! signature_map.insert("example.com", signature_set).unwrap();
|
|
||||||
//!
|
|
||||||
//! // Serialize the map to JSON.
|
|
||||||
//! assert!(serde_json::to_string(&signature_map).is_ok());
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Just like the `SignatureSet` itself, the `SignatureMap` value can also be deserialized from
|
|
||||||
//! JSON.
|
|
||||||
|
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_copy_implementations,
|
missing_copy_implementations,
|
||||||
|
@ -88,10 +88,35 @@ impl Signature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map of server names to sets of digital signatures created by that server.
|
/// A map from server names to sets of digital signatures created by that server.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Creating and serializing a `SignatureMap`:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// const SIGNATURE_BYTES: &str =
|
||||||
|
/// "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
||||||
|
///
|
||||||
|
/// // Create a `Signature` from the raw bytes of the signature.
|
||||||
|
/// let signature_bytes = base64::decode_config(&SIGNATURE_BYTES, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
/// let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create a `SignatureSet` and insert the signature into it.
|
||||||
|
/// let mut signature_set = ruma_signatures::SignatureSet::new();
|
||||||
|
/// signature_set.insert(signature);
|
||||||
|
///
|
||||||
|
/// // Create a `SignatureMap` and insert the set into it, keyed by the homeserver name.
|
||||||
|
/// let mut signature_map = ruma_signatures::SignatureMap::new();
|
||||||
|
/// signature_map.insert("example.com", signature_set).unwrap();
|
||||||
|
///
|
||||||
|
/// // Serialize the map to JSON.
|
||||||
|
/// assert!(serde_json::to_string(&signature_map).is_ok());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct SignatureMap {
|
pub struct SignatureMap {
|
||||||
/// A map of homeservers to sets of signatures for the homeserver.
|
/// A map from homeservers to sets of signatures for the homeserver.
|
||||||
map: HashMap<Host, SignatureSet>,
|
map: HashMap<Host, SignatureSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,6 +278,26 @@ impl<'de> Visitor<'de> for SignatureMapVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A set of digital signatures created by a single homeserver.
|
/// A set of digital signatures created by a single homeserver.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Creating and serializing a `SignatureSet`:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// const SIGNATURE_BYTES: &str =
|
||||||
|
/// "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
||||||
|
///
|
||||||
|
/// // Create a `Signature` from the raw bytes of the signature.
|
||||||
|
/// let signature_bytes = base64::decode_config(&SIGNATURE_BYTES, base64::STANDARD_NO_PAD).unwrap();
|
||||||
|
/// let signature = ruma_signatures::Signature::new("ed25519:1", &signature_bytes).unwrap();
|
||||||
|
///
|
||||||
|
/// // Create a `SignatureSet` and insert the signature into it.
|
||||||
|
/// let mut signature_set = ruma_signatures::SignatureSet::new();
|
||||||
|
/// signature_set.insert(signature);
|
||||||
|
///
|
||||||
|
/// // Serialize the set to JSON.
|
||||||
|
/// assert!(serde_json::to_string(&signature_set).is_ok());
|
||||||
|
/// ```
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct SignatureSet {
|
pub struct SignatureSet {
|
||||||
/// A set of signatures for a homeserver.
|
/// A set of signatures for a homeserver.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user