diff --git a/src/functions.rs b/src/functions.rs index d1e92c3e..df681a48 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -9,7 +9,8 @@ use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value}; use crate::{ keys::KeyPair, signatures::SignatureMap, - verification::{Verified, Verifier}, + split_id, + verification::{Ed25519Verifier, Verified, Verifier}, Error, }; @@ -178,7 +179,6 @@ pub fn canonical_json(value: &Value) -> Result { /// /// # Parameters /// -/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used. /// * public_key_map: A map from entity identifiers to a map from key identifiers to public keys. /// Generally, entity identifiers are server names—the host/IP/port of a homeserver (e.g. /// "example.com") for which a signature must be verified. Key identifiers for each server (e.g. @@ -216,9 +216,6 @@ pub fn canonical_json(value: &Value) -> Result { /// }"# /// ).unwrap(); /// -/// // Create the verifier for the Ed25519 algorithm. -/// let verifier = ruma_signatures::Ed25519Verifier; -/// /// // Create the `SignatureMap` that will inform `verify_json` which signatures to verify. /// let mut signature_set = HashMap::new(); /// signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); @@ -226,16 +223,9 @@ pub fn canonical_json(value: &Value) -> Result { /// public_key_map.insert("example.com".to_string(), signature_set); /// /// // Verify at least one signature for each entity in `public_key_map`. -/// assert!(ruma_signatures::verify_json(&verifier, &public_key_map, &value).is_ok()); +/// assert!(ruma_signatures::verify_json(&public_key_map, &value).is_ok()); /// ``` -pub fn verify_json( - verifier: &V, - public_key_map: &SignatureMap, - value: &Value, -) -> Result<(), Error> -where - V: Verifier, -{ +pub fn verify_json(public_key_map: &SignatureMap, value: &Value) -> Result<(), Error> { let map = match value { Value::Object(ref map) => map, _ => return Err(Error::new("JSON value must be a JSON object")), @@ -264,6 +254,12 @@ where let mut maybe_public_key = None; for (key_id, public_key) in public_keys { + // Since only ed25519 is supported right now, we don't actually need to check what the + // algorithm is. If it split successfully, it's ed25519. + if split_id(key_id).is_err() { + break; + } + if let Some(signature) = signature_set.get(key_id) { maybe_signature = Some(signature); maybe_public_key = Some(public_key); @@ -294,7 +290,7 @@ where let public_key_bytes = decode_config(&public_key, STANDARD_NO_PAD)?; - verify_json_with(verifier, &public_key_bytes, &signature_bytes, value)?; + verify_json_with(&Ed25519Verifier, &public_key_bytes, &signature_bytes, value)?; } Ok(()) @@ -514,7 +510,6 @@ where /// /// # Parameters /// -/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used. /// * public_key_map: A map from entity identifiers to a map from key identifiers to public keys. /// Generally, entity identifiers are server names—the host/IP/port of a homeserver (e.g. /// "example.com") for which a signature must be verified. Key identifiers for each server (e.g. @@ -554,9 +549,6 @@ where /// }"# /// ).unwrap(); /// -/// // Create the verifier for the Ed25519 algorithm. -/// let verifier = ruma_signatures::Ed25519Verifier; -/// /// // Create a map from key ID to public key. /// let mut example_server_keys = HashMap::new(); /// example_server_keys.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); @@ -566,16 +558,9 @@ where /// public_key_map.insert("domain".to_string(), example_server_keys); /// /// // Verify at least one signature for each entity in `public_key_map`. -/// assert!(ruma_signatures::verify_event(&verifier, &public_key_map, &value).is_ok()); +/// assert!(ruma_signatures::verify_event(&public_key_map, &value).is_ok()); /// ``` -pub fn verify_event( - verifier: &V, - public_key_map: &SignatureMap, - value: &Value, -) -> Result -where - V: Verifier, -{ +pub fn verify_event(public_key_map: &SignatureMap, value: &Value) -> Result { let redacted = redact(value)?; let map = match redacted { @@ -620,6 +605,12 @@ where let mut maybe_public_key = None; for (key_id, public_key) in public_keys { + // Since only ed25519 is supported right now, we don't actually need to check what the + // algorithm is. If it split successfully, it's ed25519. + if split_id(key_id).is_err() { + break; + } + if let Some(signature) = signature_set.get(key_id) { maybe_signature = Some(signature); maybe_public_key = Some(public_key); @@ -653,7 +644,7 @@ where let public_key_bytes = decode_config(&public_key, STANDARD_NO_PAD)?; verify_json_with( - verifier, + &Ed25519Verifier, &public_key_bytes, &signature_bytes, &canonical_json, diff --git a/src/lib.rs b/src/lib.rs index 1e6f5963..7b097a80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,6 @@ pub use functions::{ }; pub use keys::{Ed25519KeyPair, KeyPair}; pub use signatures::{Signature, SignatureMap, SignatureSet}; -pub use verification::{Ed25519Verifier, Verified, Verifier}; mod functions; mod keys; @@ -177,6 +176,53 @@ impl Display for Algorithm { } } +/// An error when trying to extract the algorithm and version from a key identifier. +#[derive(Clone, Debug, PartialEq)] +enum SplitError<'a> { + /// The signature's ID does not have exactly two components separated by a colon. + InvalidLength(usize), + /// The signature's ID contains invalid characters in its version. + InvalidVersion(&'a str), + /// The signature uses an unknown algorithm. + UnknownAlgorithm(&'a str), +} + +/// Extract the algorithm and version from a key identifier. +fn split_id(id: &str) -> Result<(Algorithm, String), SplitError<'_>> { + /// The length of a valid signature ID. + const SIGNATURE_ID_LENGTH: usize = 2; + + let signature_id: Vec<&str> = id.split(':').collect(); + + let signature_id_length = signature_id.len(); + + if signature_id_length != SIGNATURE_ID_LENGTH { + return Err(SplitError::InvalidLength(signature_id_length)); + } + + let version = signature_id[1]; + + let invalid_character_index = version.find(|ch| { + !((ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') + || ch == '_') + }); + + if invalid_character_index.is_some() { + return Err(SplitError::InvalidVersion(version)); + } + + let algorithm_input = signature_id[0]; + + let algorithm = match algorithm_input { + "ed25519" => Algorithm::Ed25519, + algorithm => return Err(SplitError::UnknownAlgorithm(algorithm)), + }; + + Ok((algorithm, signature_id[1].to_string())) +} + #[cfg(test)] mod test { use std::collections::HashMap; @@ -187,7 +233,6 @@ mod test { use super::{ canonical_json, hash_and_sign_event, sign_json, verify_event, verify_json, Ed25519KeyPair, - Ed25519Verifier, }; const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI"; @@ -317,15 +362,13 @@ mod test { fn verify_empty_json() { let value = from_str(r#"{"signatures":{"example.com":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#).unwrap(); - let verifier = Ed25519Verifier; - let mut signature_set = HashMap::new(); signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); let mut public_key_map = HashMap::new(); public_key_map.insert("example.com".to_string(), signature_set); - assert!(verify_json(&verifier, &public_key_map, &value).is_ok()); + assert!(verify_json(&public_key_map, &value).is_ok()); } #[test] @@ -387,36 +430,32 @@ mod test { r#"{"one":1,"signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"# ).unwrap(); - let verifier = Ed25519Verifier; - let mut signature_set = HashMap::new(); signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); let mut public_key_map = HashMap::new(); public_key_map.insert("example.com".to_string(), signature_set); - assert!(verify_json(&verifier, &public_key_map, &value).is_ok()); + assert!(verify_json(&public_key_map, &value).is_ok()); let reverse_value = from_str( r#"{"two":"Two","signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"one":1}"# ).unwrap(); - assert!(verify_json(&verifier, &public_key_map, &reverse_value).is_ok()); + assert!(verify_json(&public_key_map, &reverse_value).is_ok()); } #[test] fn fail_verify_json() { let value = from_str(r#"{"not":"empty","signatures":{"example.com":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}"#).unwrap(); - let verifier = Ed25519Verifier; - let mut signature_set = HashMap::new(); signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); let mut public_key_map = HashMap::new(); public_key_map.insert("example.com".to_string(), signature_set); - assert!(verify_json(&verifier, &public_key_map, &value).is_err()); + assert!(verify_json(&public_key_map, &value).is_err()); } #[test] @@ -529,8 +568,6 @@ mod test { }"# ).unwrap(); - let verifier = Ed25519Verifier; - - assert!(verify_event(&verifier, &public_key_map, &value).is_ok()); + assert!(verify_event(&public_key_map, &value).is_ok()); } } diff --git a/src/signatures.rs b/src/signatures.rs index 54698181..f1a03033 100644 --- a/src/signatures.rs +++ b/src/signatures.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use base64::{encode_config, STANDARD_NO_PAD}; -use crate::{Algorithm, Error}; +use crate::{split_id, Algorithm, Error, SplitError}; /// A digital signature. #[derive(Clone, Debug, Eq, Hash, PartialEq)] @@ -38,7 +38,11 @@ impl Signature { /// /// # Errors /// - /// Returns an error if the key identifier is invalid. + /// Returns an error if: + /// + /// * The key ID specifies an unknown algorithm. + /// * The key ID is malformed. + /// * The key ID contains a version with invalid characters. pub fn new(id: &str, bytes: &[u8]) -> Result { let (algorithm, version) = split_id(id).map_err(|split_error| match split_error { SplitError::InvalidLength(length) => Error::new(format!("malformed signature ID: expected exactly 2 segment separated by a colon, found {}", length)), @@ -97,53 +101,6 @@ pub type SignatureMap = HashMap; /// This is represented as a map from signing key ID to Base64-encoded signature. pub type SignatureSet = HashMap; -/// An error when trying to extract the algorithm and version from a key identifier. -#[derive(Clone, Debug, PartialEq)] -enum SplitError<'a> { - /// The signature's ID does not have exactly two components separated by a colon. - InvalidLength(usize), - /// The signature's ID contains invalid characters in its version. - InvalidVersion(&'a str), - /// The signature uses an unknown algorithm. - UnknownAlgorithm(&'a str), -} - -/// Extract the algorithm and version from a key identifier. -fn split_id(id: &str) -> Result<(Algorithm, String), SplitError<'_>> { - /// The length of a valid signature ID. - const SIGNATURE_ID_LENGTH: usize = 2; - - let signature_id: Vec<&str> = id.split(':').collect(); - - let signature_id_length = signature_id.len(); - - if signature_id_length != SIGNATURE_ID_LENGTH { - return Err(SplitError::InvalidLength(signature_id_length)); - } - - let version = signature_id[1]; - - let invalid_character_index = version.find(|ch| { - !((ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') - || ch == '_') - }); - - if invalid_character_index.is_some() { - return Err(SplitError::InvalidVersion(version)); - } - - let algorithm_input = signature_id[0]; - - let algorithm = match algorithm_input { - "ed25519" => Algorithm::Ed25519, - algorithm => return Err(SplitError::UnknownAlgorithm(algorithm)), - }; - - Ok((algorithm, signature_id[1].to_string())) -} - #[cfg(test)] mod tests { use super::Signature;