Move signing to a top-level function.
This commit is contained in:
parent
b080a934fb
commit
348b6e2941
80
src/lib.rs
80
src/lib.rs
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user