Move signing to a top-level function.

This commit is contained in:
Jimmy Cuadra 2016-12-10 10:47:12 -08:00
parent b080a934fb
commit 348b6e2941

View File

@ -2,9 +2,10 @@
//! [Matrix](https://matrix.org/) specification. //! [Matrix](https://matrix.org/) specification.
//! //!
//! 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. Each homeserver has one or more signing key pairs which it uses to sign all //! Matrix system, as well as requests between homeservers for federation. Each homeserver has one
//! events. Matrix clients and other Matrix homeservers can ask the homeserver for its public keys //! or more signing key pairs which it uses to sign all events and federation requests. Matrix
//! and use those keys to verify the signed events. //! clients and other Matrix homeservers can ask the 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
@ -28,13 +29,18 @@
//! "1".to_string(), // The "version" of the key. //! "1".to_string(), // The "version" of the key.
//! ).unwrap(); //! ).unwrap();
//! let value = serde_json::from_str("{}").unwrap(); // An empty JSON object. //! let value = serde_json::from_str("{}").unwrap(); // An empty JSON object.
//! let signature = key_pair.sign(&value).unwrap(); // Creates a `Signature`. //! let signature = ruma_signatures::sign_json(&key_pair, &value).unwrap(); // `Signature`
//! # } //! # }
//! ``` //! ```
//! //!
//! # Signing Matrix events
//!
//! Signing an event uses a more involved process than signing arbitrary JSON.
//! Event signing is not yet implemented by ruma_signatures.
//!
//! # Verifying signatures //! # Verifying signatures
//! //!
//! A client application or another homeserver can verify a signature: //! A client application or another homeserver can verify a signature on arbitrary JSON:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! # extern crate ruma_signatures; //! # extern crate ruma_signatures;
@ -43,10 +49,12 @@
//! # let public_key = [0; 32]; //! # let public_key = [0; 32];
//! # let signature = ruma_signatures::Signature::new("ed25519:1", &[0; 32]).unwrap(); //! # let signature = ruma_signatures::Signature::new("ed25519:1", &[0; 32]).unwrap();
//! let value = serde_json::from_str("{}").unwrap(); // The same empty JSON object. //! let value = serde_json::from_str("{}").unwrap(); // The same empty JSON object.
//! assert!(signature.verify(&public_key, &value).is_ok()); //! assert!(signature.verify_json(&public_key, &value).is_ok());
//! # } //! # }
//! ``` //! ```
//! //!
//! Verifying signatures of Matrix events is not yet implemented by ruma_signatures.
//!
//! # Signature sets //! # Signature sets
//! //!
//! Signatures that a homeserver has added to an event are stored in a JSON object under the //! Signatures that a homeserver has added to an event are stored in a JSON object under the
@ -109,7 +117,28 @@ static BASE64_CONFIG: Config = Config {
line_length: None, line_length: None,
}; };
fn signable_json(value: &Value) -> Result<String, Error> { /// Signs an arbitrary JSON object.
///
/// # Parameters
///
/// * key_pair: A cryptographic key pair used to sign the JSON.
/// * value: A JSON object to be signed according to the Matrix specification.
///
/// # Errors
///
/// Returns an error if the JSON value is not a JSON object.
pub fn sign_json<K>(key_pair: &K, value: &Value) -> Result<Signature, Error> where K: KeyPair {
let json = to_canonical_json(value)?;
Ok(key_pair.sign(json.as_bytes()))
}
/// Converts a JSON object into the "canonical" form, suitable for signing.
///
/// # Errors
///
/// Returns an error if the provided JSON value is not a JSON object.
pub fn to_canonical_json(value: &Value) -> Result<String, Error> {
if !value.is_object() { if !value.is_object() {
return Err(Error::new("JSON value must be a JSON object")); return Err(Error::new("JSON value must be a JSON object"));
} }
@ -117,9 +146,9 @@ fn signable_json(value: &Value) -> Result<String, Error> {
let mut owned_value = value.clone(); let mut owned_value = value.clone();
{ {
let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above. let mut object = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
hash.remove("signatures"); object.remove("signatures");
hash.remove("unsigned"); object.remove("unsigned");
} }
to_string(&owned_value).map_err(|error| Error::new(error.description())) to_string(&owned_value).map_err(|error| Error::new(error.description()))
@ -185,18 +214,11 @@ pub trait KeyPair: Sized {
/// ///
/// # Parameters /// # Parameters
/// ///
/// * value: A JSON value to be signed according to the Matrix specification. /// * message: An arbitrary binary value to sign.
/// fn sign(&self, message: &[u8]) -> Signature;
/// # Errors
///
/// Returns an error if the JSON value is not a JSON object.
fn sign(&self, value: &Value) -> Result<Signature, Error>;
} }
/// A single digital signature. /// A single digital signature.
///
/// Signatures are originally generated from a `KeyPair`.
/// For verifying a signature, a `Signature` can be constructed from bytes using `new`.
#[derive(Debug, Eq, Hash, PartialEq)] #[derive(Debug, Eq, Hash, PartialEq)]
pub struct Signature { pub struct Signature {
algorithm: Algorithm, algorithm: Algorithm,
@ -230,14 +252,12 @@ impl KeyPair for Ed25519KeyPair {
}) })
} }
fn sign(&self, value: &Value) -> Result<Signature, Error> { fn sign(&self, message: &[u8]) -> Signature {
let json = signable_json(value)?; Signature {
Ok(Signature {
algorithm: Algorithm::Ed25519, algorithm: Algorithm::Ed25519,
signature: self.ring_key_pair.sign(json.as_bytes()).as_slice().to_vec(), signature: self.ring_key_pair.sign(message).as_slice().to_vec(),
version: self.version.clone(), version: self.version.clone(),
}) }
} }
} }
@ -301,13 +321,13 @@ impl Signature {
} }
/// Use the public key to verify the signature against the JSON object that was signed. /// Use the public key to verify the signature against the JSON object that was signed.
pub fn verify(&self, public_key: &[u8], value: &Value) -> Result<(), Error> { pub fn verify_json(&self, public_key: &[u8], value: &Value) -> Result<(), Error> {
match self.algorithm { match self.algorithm {
Algorithm::Ed25519 => { Algorithm::Ed25519 => {
verify( verify(
&ED25519, &ED25519,
Input::from(public_key), Input::from(public_key),
Input::from(signable_json(value)?.as_bytes()), Input::from(to_canonical_json(value)?.as_bytes()),
Input::from(self.as_bytes()), Input::from(self.as_bytes()),
).map_err(|_| Error::new("signature verification failed")) ).map_err(|_| Error::new("signature verification failed"))
} }
@ -421,7 +441,7 @@ mod test {
use rustc_serialize::base64::FromBase64; use rustc_serialize::base64::FromBase64;
use serde_json::from_str; use serde_json::from_str;
use super::{Ed25519KeyPair, KeyPair, Signature}; use super::{Ed25519KeyPair, KeyPair, Signature, sign_json};
const PUBLIC_KEY: &'static str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI"; const PUBLIC_KEY: &'static str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
const PRIVATE_KEY: &'static str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0"; const PRIVATE_KEY: &'static str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
@ -437,7 +457,7 @@ mod test {
"1".to_string(), "1".to_string(),
).unwrap(); ).unwrap();
let value = from_str("{}").unwrap(); let value = from_str("{}").unwrap();
let signature = key_pair.sign(&value).unwrap(); let signature = sign_json(&key_pair, &value).unwrap();
assert_eq!(signature.base64(), EMPTY_JSON_SIGNATURE); assert_eq!(signature.base64(), EMPTY_JSON_SIGNATURE);
} }
@ -448,6 +468,6 @@ mod test {
&EMPTY_JSON_SIGNATURE.from_base64().unwrap(), &EMPTY_JSON_SIGNATURE.from_base64().unwrap(),
).unwrap(); ).unwrap();
let value = from_str("{}").unwrap(); let value = from_str("{}").unwrap();
assert!(signature.verify(&PUBLIC_KEY.from_base64().unwrap(), &value).is_ok()); assert!(signature.verify_json(&PUBLIC_KEY.from_base64().unwrap(), &value).is_ok());
} }
} }