Make SignatureMap and SignatureSet type aliases for HashMap.
This commit is contained in:
		
							parent
							
								
									efbea86fe5
								
							
						
					
					
						commit
						e64df47d23
					
				| @ -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(()) | ||||
|  | ||||
| @ -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<u8>, | ||||
|     pub signature: Vec<u8>, | ||||
| 
 | ||||
|     /// 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<Host, SignatureSet>, | ||||
| } | ||||
| 
 | ||||
| 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<Option<SignatureSet>, 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<Entry<Host, SignatureSet>, 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<Option<&SignatureSet>, 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<Option<&mut SignatureSet>, 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||
|     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<D>(deserializer: D) -> Result<Self, D::Error> | ||||
|     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<M>(self, mut visitor: M) -> Result<Self::Value, M::Error> | ||||
|     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::<String, SignatureSet>()? | ||||
|         { | ||||
|             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<String, SignatureSet>; | ||||
| 
 | ||||
| /// 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<String, Signature>, | ||||
| } | ||||
| 
 | ||||
| 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<Signature> { | ||||
|         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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||
|     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<D>(deserializer: D) -> Result<Self, D::Error> | ||||
|     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<M>(self, mut visitor: M) -> Result<Self::Value, M::Error> | ||||
|     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::<String, String>()? { | ||||
|             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<u8> = 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<String, String>; | ||||
| 
 | ||||
| /// 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<Host, Error> { | ||||
|     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; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user