From e64df47d23545d76c78141d3bfc79b7143db240f Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 10 Jul 2019 19:55:30 -0700 Subject: [PATCH] Make SignatureMap and SignatureSet type aliases for HashMap. --- src/functions.rs | 24 ++- src/signatures.rs | 394 +++------------------------------------------- 2 files changed, 37 insertions(+), 381 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index 699b2711..b2570504 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -2,13 +2,13 @@ use std::{collections::HashMap, hash::BuildHasher}; -use base64::{encode_config, STANDARD_NO_PAD}; +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::{Signature, SignatureMap, SignatureSet}, + signatures::{Signature, SignatureMap}, verification::Verifier, Error, }; @@ -128,7 +128,7 @@ where Some(signatures) => from_value(Value::Object(signatures.clone()))?, None => return Err(Error::new("Field `signatures` must be a JSON object")), }, - None => SignatureMap::with_capacity(1), + None => HashMap::with_capacity(1), }; maybe_unsigned = map.remove("unsigned"); @@ -142,10 +142,10 @@ where // Insert the new signature in the map we pulled out (or created) previously. let signature_set = signature_map - .entry(server_name)? - .or_insert_with(|| SignatureSet::with_capacity(1)); + .entry(server_name.to_string()) + .or_insert_with(|| HashMap::with_capacity(1)); - signature_set.insert(signature); + signature_set.insert(signature.id(), signature.base64()); // Safe to unwrap because we did this exact check at the beginning of the function. let map = value.as_object_mut().unwrap(); @@ -490,7 +490,7 @@ where }; for (server_name, verify_keys) in verify_key_map { - let signature_set = match signature_map.get(server_name)? { + let signature_set = match signature_map.get(server_name) { Some(set) => set, None => { return Err(Error::new(format!( @@ -532,7 +532,15 @@ where let canonical_json = from_str(&to_canonical_json(&redacted)?)?; - verify_json(verifier, verify_key, signature, &canonical_json)?; + verify_json( + verifier, + verify_key, + &Signature::new( + "ed25519:fixme", + &decode_config(signature, STANDARD_NO_PAD).expect("FIXME"), + )?, + &canonical_json, + )?; } Ok(()) diff --git a/src/signatures.rs b/src/signatures.rs index 64fe2925..ac4d9c3c 100644 --- a/src/signatures.rs +++ b/src/signatures.rs @@ -1,37 +1,36 @@ //! Digital signatures and collections of signatures. -use std::{ - collections::{hash_map::Entry, HashMap}, - error::Error as _, - fmt::{Formatter, Result as FmtResult}, -}; +use std::collections::HashMap; -use base64::{decode_config, encode_config, STANDARD_NO_PAD}; -use serde::{ - de::{Error as SerdeError, MapAccess, Unexpected, Visitor}, - ser::SerializeMap, - Deserialize, Deserializer, Serialize, Serializer, -}; -use url::{Host, Url}; +use base64::{encode_config, STANDARD_NO_PAD}; use crate::{Algorithm, Error}; /// A digital signature. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Signature { - /// The cryptographic algorithm to use. - pub(crate) algorithm: Algorithm, + /// The cryptographic algorithm that generated this signature. + pub algorithm: Algorithm, /// The signature data. - pub(crate) signature: Vec, + pub signature: Vec, - /// The version of the signature. - pub(crate) version: String, + /// The "version" of the key identifier for the public key used to generate this signature. + pub version: String, } impl Signature { /// Creates a signature from raw bytes. /// + /// While a signature can be created directly using struct literal syntax, this constructor can + /// be used to automatically determine the algorithm and version from a key identifier in the + /// form *algorithm:version*, e.g. "ed25519:1". + /// + /// This constructor will ensure that the version does not contain characters that violate the + /// guidelines in the specification. Because it may be necessary to represent signatures with + /// versions that don't adhere to these guidelines, it's possible to simply use the struct + /// literal syntax to construct a `Signature` with an arbitrary key. + /// /// # Parameters /// /// * id: A key identifier, e.g. "ed25519:1". @@ -88,354 +87,15 @@ impl Signature { } } -/// A map from server names to sets of digital signatures created by that server. +/// A map from entity names to sets of digital signatures created by that entity. /// -/// # 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)] -pub struct SignatureMap { - /// A map from homeservers to sets of signatures for the homeserver. - map: HashMap, -} - -impl SignatureMap { - /// Initializes a new empty `SignatureMap`. - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - /// Initializes a new empty `SignatureMap` with room for a specific number of servers. - /// - /// # Parameters - /// - /// * capacity: The number of items to allocate memory for. - pub fn with_capacity(capacity: usize) -> Self { - Self { - map: HashMap::with_capacity(capacity), - } - } - - /// Adds a signature set for a server. - /// - /// If no signature set for the given server existed in the collection, `None` is returned. - /// Otherwise, the signature set is returned. - /// - /// # Parameters - /// - /// * server_name: The hostname or IP of the homeserver, e.g. `example.com`. - /// * signature_set: The `SignatureSet` containing the digital signatures made by the server. - /// - /// # Errors - /// - /// Returns an error if the given server name cannot be parsed as a valid host. - pub fn insert( - &mut self, - server_name: &str, - signature_set: SignatureSet, - ) -> Result, Error> { - let host = server_name_to_host(server_name)?; - - Ok(self.map.insert(host, signature_set)) - } - - /// Gets the given server's corresponding signature set for in-place manipulation. - /// - /// # Parameters - /// - /// * server_name: The hostname or IP of the homeserver, e.g. `example.com`. - /// - /// # Errors - /// - /// Returns an error if the given server name cannot be parsed as a valid host. - pub fn entry(&mut self, server_name: &str) -> Result, Error> { - let host = server_name_to_host(server_name)?; - - Ok(self.map.entry(host)) - } - - /// Gets a reference to the signature set for the given server, if any. - /// - /// # Parameters - /// - /// * server_name: The hostname or IP of the homeserver, e.g. `example.com`. - /// - /// # Errors - /// - /// Returns an error if the given server name cannot be parsed as a valid host. - pub fn get(&self, server_name: &str) -> Result, Error> { - let host = server_name_to_host(server_name)?; - - Ok(self.map.get(&host)) - } - - /// Gets a mutable reference to the signature set for the given server, if any. - /// - /// # Parameters - /// - /// * server_name: The hostname or IP of the homeserver, e.g. `example.com`. - /// - /// # Errors - /// - /// Returns an error if the given server name cannot be parsed as a valid host. - pub fn get_mut(&mut self, server_name: &str) -> Result, Error> { - let host = server_name_to_host(server_name)?; - - Ok(self.map.get_mut(&host)) - } - - /// The number of servers in the collection. - pub fn len(&self) -> usize { - self.map.len() - } - - /// Whether or not the collection of signatures is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl Serialize for SignatureMap { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map_serializer = serializer.serialize_map(Some(self.len()))?; - - for (host, signature_set) in self.map.iter() { - map_serializer.serialize_key(&host.to_string())?; - map_serializer.serialize_value(signature_set)?; - } - - map_serializer.end() - } -} - -impl<'de> Deserialize<'de> for SignatureMap { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(SignatureMapVisitor) - } -} - -/// Serde Visitor for deserializing `SignatureMap`. -struct SignatureMapVisitor; - -impl<'de> Visitor<'de> for SignatureMapVisitor { - type Value = SignatureMap; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult { - write!(formatter, "digital signatures") - } - - fn visit_map(self, mut visitor: M) -> Result - where - M: MapAccess<'de>, - { - let mut signatures = match visitor.size_hint() { - Some(capacity) => SignatureMap::with_capacity(capacity), - None => SignatureMap::new(), - }; - - while let Some((server_name, signature_set)) = - visitor.next_entry::()? - { - if signatures.insert(&server_name, signature_set).is_err() { - return Err(M::Error::invalid_value( - Unexpected::Str(&server_name), - &self, - )); - } - } - - Ok(signatures) - } -} +/// "Entity" is currently always a homeserver, e.g. "example.com". +pub type SignatureMap = HashMap; /// 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)] -pub struct SignatureSet { - /// A set of signatures for a homeserver. - map: HashMap, -} - -impl SignatureSet { - /// Initializes a new empty SignatureSet. - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - /// Initializes a new empty SignatureSet with room for a specific number of signatures. - /// - /// # Parameters - /// - /// * capacity: The number of items to allocate memory for. - pub fn with_capacity(capacity: usize) -> Self { - Self { - map: HashMap::with_capacity(capacity), - } - } - - /// Adds a signature to the set. - /// - /// If no signature with the given key ID existed in the collection, `None` is returned. - /// Otherwise, the signature is returned. - /// - /// # Parameters - /// - /// * signature: A `Signature` to insert into the set. - pub fn insert(&mut self, signature: Signature) -> Option { - self.map.insert(signature.id(), signature) - } - - /// Gets a reference to the signature with the given key ID, if any. - /// - /// # Parameters - /// - /// * key_id: The identifier of the public key (e.g. "ed25519:1") for the desired signature. - pub fn get(&self, key_id: &str) -> Option<&Signature> { - self.map.get(key_id) - } - - /// Gets a mutable reference to the signature with the given ID, if any. - /// - /// # Parameters - /// - /// * key_id: The identifier of the public key (e.g. "ed25519:1") for the desired signature. - pub fn get_mut(&mut self, key_id: &str) -> Option<&mut Signature> { - self.map.get_mut(key_id) - } - - /// The number of signatures in the set. - pub fn len(&self) -> usize { - self.map.len() - } - - /// Whether or not the set of signatures is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl Serialize for SignatureSet { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map_serializer = serializer.serialize_map(Some(self.len()))?; - - for signature in self.map.values() { - map_serializer.serialize_key(&signature.id())?; - map_serializer.serialize_value(&signature.base64())?; - } - - map_serializer.end() - } -} - -impl<'de> Deserialize<'de> for SignatureSet { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(SignatureSetVisitor) - } -} - -/// Serde Visitor for deserializing `SignatureSet`. -struct SignatureSetVisitor; - -impl<'de> Visitor<'de> for SignatureSetVisitor { - type Value = SignatureSet; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult { - write!(formatter, "a set of digital signatures") - } - - fn visit_map(self, mut visitor: M) -> Result - where - M: MapAccess<'de>, - { - let mut signature_set = match visitor.size_hint() { - Some(capacity) => SignatureSet::with_capacity(capacity), - None => SignatureSet::new(), - }; - - while let Some((key, value)) = visitor.next_entry::()? { - let (algorithm, version) = split_id(&key).map_err(|split_error| match split_error { - SplitError::InvalidLength(length) => M::Error::invalid_length(length, &self), - SplitError::InvalidVersion(version) => { - M::Error::invalid_value(Unexpected::Str(version), &self) - } - SplitError::UnknownAlgorithm(algorithm) => { - M::Error::invalid_value(Unexpected::Str(algorithm), &self) - } - })?; - - let signature_bytes: Vec = match decode_config(&value, STANDARD_NO_PAD) { - Ok(raw) => raw, - Err(error) => return Err(M::Error::custom(error.description())), - }; - - let signature = Signature { - algorithm, - signature: signature_bytes, - version, - }; - - signature_set.insert(signature); - } - - Ok(signature_set) - } -} +/// 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)] @@ -484,18 +144,6 @@ fn split_id(id: &str) -> Result<(Algorithm, String), SplitError<'_>> { Ok((algorithm, signature_id[1].to_string())) } -/// Attempts to convert a server name as a string into a `url::Host`. -fn server_name_to_host(server_name: &str) -> Result { - let url_string = format!("https://{}", server_name); - let url = Url::parse(&url_string) - .map_err(|_| Error::new(format!("invalid server name: {}", server_name)))?; - - match url.host() { - Some(host) => Ok(host.to_owned()), - None => Err(Error::new(format!("invalid server name: {}", server_name))), - } -} - #[cfg(test)] mod tests { use super::Signature;