From 97ee073e116a2886b963340e71f159180b1cd03f Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 8 Jul 2019 21:10:50 -0700 Subject: [PATCH] Split the library into modules. --- src/keys.rs | 89 ++++++++ src/lib.rs | 499 ++------------------------------------------ src/signatures.rs | 365 ++++++++++++++++++++++++++++++++ src/verification.rs | 48 +++++ 4 files changed, 516 insertions(+), 485 deletions(-) create mode 100644 src/keys.rs create mode 100644 src/signatures.rs create mode 100644 src/verification.rs diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 00000000..a8e1c833 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,89 @@ +//! Public and private key pairs. + +use std::fmt::{Debug, Formatter, Result as FmtResult}; + +use ring::signature::Ed25519KeyPair as RingEd25519KeyPair; +use untrusted::Input; + +use crate::{signatures::Signature, Algorithm, Error}; + +/// A cryptographic key pair for digitally signing data. +pub trait KeyPair: Sized { + /// Initializes a new key pair. + /// + /// # Parameters + /// + /// * public_key: The public key of the key pair. + /// * private_key: The private key of the key pair. + /// * version: The "version" of the key used for this signature. + /// Versions are used as an identifier to distinguish signatures generated from different keys + /// but using the same algorithm on the same homeserver. + /// + /// # Errors + /// + /// Returns an error if the public and private keys provided are invalid for the implementing + /// algorithm. + fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result; + + /// Signs a JSON object. + /// + /// # Parameters + /// + /// * message: An arbitrary series of bytes to sign. + fn sign(&self, message: &[u8]) -> Signature; +} + +/// An Ed25519 key pair. +#[derive(Clone, PartialEq)] +pub struct Ed25519KeyPair { + /// The public key. + public_key: Vec, + + /// The private key. + private_key: Vec, + + /// The version of the key pair. + version: String, +} + +impl KeyPair for Ed25519KeyPair { + fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result { + if let Err(error) = RingEd25519KeyPair::from_seed_and_public_key( + Input::from(private_key), + Input::from(public_key), + ) { + return Err(Error::new(error.to_string())); + } + + Ok(Self { + public_key: public_key.to_owned(), + private_key: private_key.to_owned(), + version, + }) + } + + fn sign(&self, message: &[u8]) -> Signature { + // Okay to unwrap because we verified the input in the `new`. + let ring_key_pair = RingEd25519KeyPair::from_seed_and_public_key( + Input::from(&self.private_key), + Input::from(&self.public_key), + ) + .unwrap(); + + Signature { + algorithm: Algorithm::Ed25519, + signature: ring_key_pair.sign(message).as_ref().to_vec(), + version: self.version.clone(), + } + } +} + +impl Debug for Ed25519KeyPair { + fn fmt(&self, formatter: &mut Formatter) -> FmtResult { + formatter + .debug_struct("Ed25519KeyPair") + .field("public_key", &self.public_key) + .field("version", &self.version) + .finish() + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f030932..c051e682 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,24 +143,22 @@ )] use std::{ - collections::{HashMap, HashSet}, error::Error as StdError, - fmt::{Debug, Display, Formatter, Result as FmtResult}, + fmt::{Display, Formatter, Result as FmtResult}, }; -use base64::{decode_config, encode_config, STANDARD_NO_PAD}; -use ring::signature::{verify, Ed25519KeyPair as RingEd25519KeyPair, ED25519}; -use serde::{ - de::{Error as SerdeError, MapAccess, Unexpected, Visitor}, - ser::SerializeMap, - Deserialize, Deserializer, Serialize, Serializer, -}; use serde_json::{to_string, Value}; -use untrusted::Input; -use url::Url; pub use url::Host; +pub use keys::{Ed25519KeyPair, KeyPair}; +pub use signatures::{Signature, SignatureSet, Signatures}; +pub use verification::{Ed25519Verifier, Verifier}; + +mod keys; +mod signatures; +mod verification; + /// Signs an arbitrary JSON object. /// /// # Parameters @@ -231,62 +229,6 @@ where verifier.verify_json(public_key, signature, to_canonical_json(value)?.as_bytes()) } -/// 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 has an invalid length. - InvalidLength(usize), - /// 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 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())) -} - -/// The algorithm used for signing data. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Algorithm { - /// The Ed25519 digital signature algorithm. - Ed25519, -} - -/// An Ed25519 key pair. -#[derive(Clone, PartialEq)] -pub struct Ed25519KeyPair { - /// The public key. - public_key: Vec, - - /// The private key. - private_key: Vec, - - /// The version of the key pair. - version: String, -} - -/// A verifier for Ed25519 digital signatures. -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct Ed25519Verifier; - /// An error produced when ruma-signatures operations fail. #[derive(Clone, Debug, PartialEq)] pub struct Error { @@ -294,143 +236,6 @@ pub struct Error { message: String, } -/// A cryptographic key pair for digitally signing data. -pub trait KeyPair: Sized { - /// Initializes a new key pair. - /// - /// # Parameters - /// - /// * public_key: The public key of the key pair. - /// * private_key: The private key of the key pair. - /// * version: The "version" of the key used for this signature. - /// Versions are used as an identifier to distinguish signatures generated from different keys - /// but using the same algorithm on the same homeserver. - /// - /// # Errors - /// - /// Returns an error if the public and private keys provided are invalid for the implementing - /// algorithm. - fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result; - - /// Signs a JSON object. - /// - /// # Parameters - /// - /// * message: An arbitrary series of bytes to sign. - fn sign(&self, message: &[u8]) -> Signature; -} - -/// A digital signature. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct Signature { - /// The cryptographic algorithm to use. - algorithm: Algorithm, - /// The signature data. - signature: Vec, - /// The version of the signature. - version: String, -} - -/// A map of server names to sets of digital signatures created by that server. -#[derive(Clone, Debug, Default, PartialEq)] -pub struct Signatures { - /// A map of homeservers to sets of signatures for the homeserver. - map: HashMap, -} - -/// Serde Visitor for deserializing `Signatures`. -struct SignaturesVisitor; - -/// A set of digital signatures created by a single homeserver. -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SignatureSet { - /// A set of signatures for a homeserver. - set: HashSet, -} - -/// Serde Visitor for deserializing `SignatureSet`. -struct SignatureSetVisitor; - -/// A digital signature verifier. -pub trait Verifier { - /// Use a public key to verify a signature against the JSON object that was signed. - /// - /// # Parameters - /// - /// * public_key: The public key of the key pair used to sign the message. - /// * signature: The `Signature` to verify. - /// * message: The message that was signed. - /// - /// # Errors - /// - /// Returns an error if verification fails. - fn verify_json( - &self, - public_key: &[u8], - signature: &Signature, - message: &[u8], - ) -> Result<(), Error>; -} - -impl KeyPair for Ed25519KeyPair { - fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result { - if let Err(error) = RingEd25519KeyPair::from_seed_and_public_key( - Input::from(private_key), - Input::from(public_key), - ) { - return Err(Error::new(error.to_string())); - } - - Ok(Self { - public_key: public_key.to_owned(), - private_key: private_key.to_owned(), - version, - }) - } - - fn sign(&self, message: &[u8]) -> Signature { - // Okay to unwrap because we verified the input in the `new`. - let ring_key_pair = RingEd25519KeyPair::from_seed_and_public_key( - Input::from(&self.private_key), - Input::from(&self.public_key), - ) - .unwrap(); - - Signature { - algorithm: Algorithm::Ed25519, - signature: ring_key_pair.sign(message).as_ref().to_vec(), - version: self.version.clone(), - } - } -} - -impl Debug for Ed25519KeyPair { - fn fmt(&self, formatter: &mut Formatter) -> FmtResult { - formatter - .debug_struct("Ed25519KeyPair") - .field("public_key", &self.public_key) - .field("version", &self.version) - .finish() - } -} - -impl Verifier for Ed25519Verifier { - fn verify_json( - &self, - public_key: &[u8], - signature: &Signature, - message: &[u8], - ) -> Result<(), Error> { - verify( - &ED25519, - Input::from(public_key), - Input::from(message), - Input::from(signature.as_bytes()), - ) - .map_err(|_| Error::new("signature verification failed")) - } -} - impl Error { /// Creates a new error. /// @@ -459,287 +264,11 @@ impl Display for Error { } } -impl Signature { - /// Creates a signature from raw bytes. - /// - /// # Parameters - /// - /// * id: A key identifier, e.g. "ed25519:1". - /// * bytes: The digital signature, as a series of bytes. - /// - /// # Errors - /// - /// Returns an error if the key identifier is invalid. - pub fn new(id: &str, bytes: &[u8]) -> Result { - let (algorithm, version) = split_id(id).map_err(|split_error| match split_error { - SplitError::InvalidLength(_) => Error::new("malformed signature ID"), - SplitError::UnknownAlgorithm(algorithm) => { - Error::new(format!("unknown algorithm: {}", algorithm)) - } - })?; - - Ok(Self { - algorithm, - signature: bytes.to_vec(), - version, - }) - } - - /// The algorithm used to generate the signature. - pub fn algorithm(&self) -> Algorithm { - self.algorithm - } - - /// The raw bytes of the signature. - pub fn as_bytes(&self) -> &[u8] { - self.signature.as_slice() - } - - /// A Base64 encoding of the signature. - /// - /// Uses the standard character set with no padding. - pub fn base64(&self) -> String { - encode_config(self.signature.as_slice(), STANDARD_NO_PAD) - } - - /// The key identifier, a string containing the signature algorithm and the key "version" - /// separated by a colon, e.g. "ed25519:1". - pub fn id(&self) -> String { - format!("{}:{}", self.algorithm, self.version) - } - - /// The "version" of the key used for this signature. - /// - /// Versions are used as an identifier to distinguish signatures generated from different keys - /// but using the same algorithm on the same homeserver. - pub fn version(&self) -> &str { - &self.version - } -} - -impl Signatures { - /// Initializes a new empty Signatures. - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - /// Initializes a new empty Signatures 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 url_string = format!("https://{}", server_name); - let url = Url::parse(&url_string) - .map_err(|_| Error::new(format!("invalid server name: {}", server_name)))?; - - let host = match url.host() { - Some(host) => host.to_owned(), - None => return Err(Error::new(format!("invalid server name: {}", server_name))), - }; - - Ok(self.map.insert(host, signature_set)) - } - - /// 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<'de> Deserialize<'de> for Signatures { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(SignaturesVisitor) - } -} - -impl Serialize for Signatures { - 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> Visitor<'de> for SignaturesVisitor { - type Value = Signatures; - - 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) => Signatures::with_capacity(capacity), - None => Signatures::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) - } -} - -impl SignatureSet { - /// Initializes a new empty SignatureSet. - pub fn new() -> Self { - Self { - set: HashSet::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 { - set: HashSet::with_capacity(capacity), - } - } - - /// Adds a signature to the set. - /// - /// The boolean return value indicates whether or not the value was actually inserted, since - /// subsequent inserts of the same signature have no effect. - /// - /// # Parameters - /// - /// * signature: A `Signature` to insert into the set. - pub fn insert(&mut self, signature: Signature) -> bool { - self.set.insert(signature) - } - - /// The number of signatures in the set. - pub fn len(&self) -> usize { - self.set.len() - } - - /// Whether or not the set of signatures is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl<'de> Deserialize<'de> for SignatureSet { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(SignatureSetVisitor) - } -} - -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.set.iter() { - map_serializer.serialize_key(&signature.id())?; - map_serializer.serialize_value(&signature.base64())?; - } - - map_serializer.end() - } -} - -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::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) - } +/// The algorithm used for signing data. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Algorithm { + /// The Ed25519 digital signature algorithm. + Ed25519, } impl Display for Algorithm { diff --git a/src/signatures.rs b/src/signatures.rs new file mode 100644 index 00000000..16ea3ad0 --- /dev/null +++ b/src/signatures.rs @@ -0,0 +1,365 @@ +//! Digital signatures and collections of signatures. + +use std::{ + collections::{HashMap, HashSet}, + error::Error as _, + fmt::{Formatter, Result as FmtResult}, +}; + +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 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 signature data. + pub(crate) signature: Vec, + + /// The version of the signature. + pub(crate) version: String, +} + +impl Signature { + /// Creates a signature from raw bytes. + /// + /// # Parameters + /// + /// * id: A key identifier, e.g. "ed25519:1". + /// * bytes: The digital signature, as a series of bytes. + /// + /// # Errors + /// + /// Returns an error if the key identifier is invalid. + pub fn new(id: &str, bytes: &[u8]) -> Result { + let (algorithm, version) = split_id(id).map_err(|split_error| match split_error { + SplitError::InvalidLength(_) => Error::new("malformed signature ID"), + SplitError::UnknownAlgorithm(algorithm) => { + Error::new(format!("unknown algorithm: {}", algorithm)) + } + })?; + + Ok(Self { + algorithm, + signature: bytes.to_vec(), + version, + }) + } + + /// The algorithm used to generate the signature. + pub fn algorithm(&self) -> Algorithm { + self.algorithm + } + + /// The raw bytes of the signature. + pub fn as_bytes(&self) -> &[u8] { + self.signature.as_slice() + } + + /// A Base64 encoding of the signature. + /// + /// Uses the standard character set with no padding. + pub fn base64(&self) -> String { + encode_config(self.signature.as_slice(), STANDARD_NO_PAD) + } + + /// The key identifier, a string containing the signature algorithm and the key "version" + /// separated by a colon, e.g. "ed25519:1". + pub fn id(&self) -> String { + format!("{}:{}", self.algorithm, self.version) + } + + /// The "version" of the key used for this signature. + /// + /// Versions are used as an identifier to distinguish signatures generated from different keys + /// but using the same algorithm on the same homeserver. + pub fn version(&self) -> &str { + &self.version + } +} + +/// A map of server names to sets of digital signatures created by that server. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Signatures { + /// A map of homeservers to sets of signatures for the homeserver. + map: HashMap, +} + +impl Signatures { + /// Initializes a new empty Signatures. + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + /// Initializes a new empty Signatures 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 url_string = format!("https://{}", server_name); + let url = Url::parse(&url_string) + .map_err(|_| Error::new(format!("invalid server name: {}", server_name)))?; + + let host = match url.host() { + Some(host) => host.to_owned(), + None => return Err(Error::new(format!("invalid server name: {}", server_name))), + }; + + Ok(self.map.insert(host, signature_set)) + } + + /// 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 Signatures { + 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 Signatures { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(SignaturesVisitor) + } +} + +/// Serde Visitor for deserializing `Signatures`. +struct SignaturesVisitor; + +impl<'de> Visitor<'de> for SignaturesVisitor { + type Value = Signatures; + + 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) => Signatures::with_capacity(capacity), + None => Signatures::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) + } +} + +/// A set of digital signatures created by a single homeserver. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct SignatureSet { + /// A set of signatures for a homeserver. + set: HashSet, +} + +impl SignatureSet { + /// Initializes a new empty SignatureSet. + pub fn new() -> Self { + Self { + set: HashSet::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 { + set: HashSet::with_capacity(capacity), + } + } + + /// Adds a signature to the set. + /// + /// The boolean return value indicates whether or not the value was actually inserted, since + /// subsequent inserts of the same signature have no effect. + /// + /// # Parameters + /// + /// * signature: A `Signature` to insert into the set. + pub fn insert(&mut self, signature: Signature) -> bool { + self.set.insert(signature) + } + + /// The number of signatures in the set. + pub fn len(&self) -> usize { + self.set.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.set.iter() { + 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::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) + } +} + +/// 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 has an invalid length. + InvalidLength(usize), + /// 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 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())) +} diff --git a/src/verification.rs b/src/verification.rs new file mode 100644 index 00000000..664be611 --- /dev/null +++ b/src/verification.rs @@ -0,0 +1,48 @@ +//! Verification of digital signatures. + +use ring::signature::{verify, ED25519}; +use untrusted::Input; + +use crate::{signatures::Signature, Error}; + +/// A digital signature verifier. +pub trait Verifier { + /// Use a public key to verify a signature against the JSON object that was signed. + /// + /// # Parameters + /// + /// * public_key: The public key of the key pair used to sign the message. + /// * signature: The `Signature` to verify. + /// * message: The message that was signed. + /// + /// # Errors + /// + /// Returns an error if verification fails. + fn verify_json( + &self, + public_key: &[u8], + signature: &Signature, + message: &[u8], + ) -> Result<(), Error>; +} + +/// A verifier for Ed25519 digital signatures. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Ed25519Verifier; + +impl Verifier for Ed25519Verifier { + fn verify_json( + &self, + public_key: &[u8], + signature: &Signature, + message: &[u8], + ) -> Result<(), Error> { + verify( + &ED25519, + Input::from(public_key), + Input::from(message), + Input::from(signature.as_bytes()), + ) + .map_err(|_| Error::new("signature verification failed")) + } +}