Switch to *ring* for crypto and flesh out the API.

This commit is contained in:
Jimmy Cuadra 2016-12-10 04:02:22 -08:00
parent 6460312c79
commit b080a934fb
3 changed files with 296 additions and 142 deletions

View File

@ -1,10 +1,3 @@
before_install:
- "curl -LO https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz"
- "tar -zxvf libsodium-1.0.11.tar.gz"
- "cd libsodium-1.0.11"
- "./configure"
- "make"
- "sudo make install"
language: "rust"
notifications:
email: false
@ -12,5 +5,3 @@ notifications:
channels:
- "chat.freenode.net#ruma"
use_notice: true
rust:
- "nightly"

View File

@ -11,11 +11,8 @@ repository = "https://github.com/ruma/ruma-signatures"
version = "0.1.0"
[dependencies]
lazy_static = "0.2.1"
rustc-serialize = "0.3.19"
serde = "0.8.12"
serde_json = "0.8.2"
sodiumoxide = "0.0.12"
[lib]
doctest = false
ring = "0.6.0-alpha1"
rustc-serialize = "0.3.22"
serde = "0.8.19"
serde_json = "0.8.4"
untrusted = "0.3.2"

View File

@ -1,27 +1,106 @@
//! ruma-signatures provides functionality for creating digital signatures according to the
//! Crate **ruma_signatures** implements digital signatures according to the
//! [Matrix](https://matrix.org/) specification.
//!
//! 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
//! events. Matrix clients and other Matrix homeservers can ask the homeserver for its public keys
//! and use those keys to verify the signed events.
//!
//! 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
//! identifier used to distinguish key pairs using the same algorithm from the same homeserver.
//!
//! # Signing JSON
//!
//! A homeserver signs JSON with a key pair:
//!
//! ```rust,no_run
//! # extern crate ruma_signatures;
//! # extern crate serde_json;
//! # fn main() {
//! # use ruma_signatures::KeyPair;
//! # let public_key = [0; 32];
//! # let private_key = [0; 32];
//! // Create an Ed25519 key pair.
//! let key_pair = ruma_signatures::Ed25519KeyPair::new(
//! &public_key, // &[u8]
//! &private_key, // &[u8]
//! "1".to_string(), // The "version" of the key.
//! ).unwrap();
//! let value = serde_json::from_str("{}").unwrap(); // An empty JSON object.
//! let signature = key_pair.sign(&value).unwrap(); // Creates a `Signature`.
//! # }
//! ```
//!
//! # Verifying signatures
//!
//! A client application or another homeserver can verify a signature:
//!
//! ```rust,no_run
//! # extern crate ruma_signatures;
//! # extern crate serde_json;
//! # fn main() {
//! # let public_key = [0; 32];
//! # let signature = ruma_signatures::Signature::new("ed25519:1", &[0; 32]).unwrap();
//! let value = serde_json::from_str("{}").unwrap(); // The same empty JSON object.
//! assert!(signature.verify(&public_key, &value).is_ok());
//! # }
//! ```
//!
//! # Signature sets
//!
//! Signatures that a homeserver has added to an event are stored in a JSON object under the
//! "signatures" key in the event's JSON representation:
//!
//! ```json
//! {
//! "content": {},
//! "event_type": "not.a.real.event",
//! "signatures": {
//! "example.com": {
//! "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
//! }
//! }
//! }
//! ```
//!
//! The keys inside the "signatures" object are the hostnames of homeservers that have added
//! signatures. Within each of those objects are a set of signatures, keyed by the signing key
//! pair's identifier.
//!
//! This inner object can be created by serializing a `SignatureSet`:
//!
//! ```rust,no_run
//! # extern crate ruma_signatures;
//! # extern crate serde;
//! # extern crate serde_json;
//! # fn main() {
//! # let signature = ruma_signatures::Signature::new("ed25519:1", &[0; 32]).unwrap();
//! let mut signature_set = ruma_signatures::SignatureSet::new();
//! signature_set.insert(signature);
//! let json = serde_json::to_string(&signature_set).unwrap();
//! # }
//! ```
//!
//! This code produces the object under the "example.com" key in the preceeding JSON. Similarly,
//! a `SignatureSet` can be produced by deserializing JSON that follows this form.
#[macro_use]
extern crate lazy_static;
extern crate ring;
extern crate rustc_serialize;
extern crate serde;
extern crate serde_json;
extern crate sodiumoxide;
extern crate untrusted;
use std::collections::HashSet;
use std::error::Error as StdError;
use std::fmt::{Display, Formatter, Result as FmtResult};
use ring::signature::{ED25519, Ed25519KeyPair as RingEd25519KeyPair, verify};
use rustc_serialize::base64::{CharacterSet, Config, FromBase64, Newline, ToBase64};
use serde::{Deserialize, Deserializer, Error as SerdeError, Serialize, Serializer};
use serde::de::{MapVisitor, Visitor};
use serde_json::{Value, to_string};
use sodiumoxide::init;
use sodiumoxide::crypto::sign::{SecretKey, Signature as SodiumSignature, sign_detached};
lazy_static! {
static ref _LIBSODIUM_INIT: bool = init();
}
use untrusted::Input;
static BASE64_CONFIG: Config = Config {
char_set: CharacterSet::Standard,
@ -30,12 +109,138 @@ static BASE64_CONFIG: Config = Config {
line_length: None,
};
/// An error produced when signing data fails.
fn signable_json(value: &Value) -> Result<String, Error> {
if !value.is_object() {
return Err(Error::new("JSON value must be a JSON object"));
}
let mut owned_value = value.clone();
{
let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
hash.remove("signatures");
hash.remove("unsigned");
}
to_string(&owned_value).map_err(|error| Error::new(error.description()))
}
enum SplitError<'a> {
InvalidLength(usize),
UnknownAlgorithm(&'a str),
}
fn split_id(id: &str) -> Result<(Algorithm, String), SplitError> {
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()))
}
/// An Ed25519 key pair.
pub struct Ed25519KeyPair {
ring_key_pair: RingEd25519KeyPair,
version: String,
}
/// An error produced during signing or verification.
#[derive(Debug)]
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<Self, Error>;
/// Signs a JSON object.
///
/// # Parameters
///
/// * value: A JSON value to be signed according to the Matrix specification.
///
/// # 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.
///
/// 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)]
pub struct Signature {
algorithm: Algorithm,
signature: Vec<u8>,
version: String,
}
/// A set of signatures created by a single homeserver.
pub struct SignatureSet {
set: HashSet<Signature>,
}
/// Serde Visitor for deserializing `SignatureSet`.
struct SignatureSetVisitor;
/// The algorithm used for signing data.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Algorithm {
/// The Ed25519 digital signature algorithm.
Ed25519,
}
impl KeyPair for Ed25519KeyPair {
fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result<Self, Error> {
Ok(Ed25519KeyPair {
ring_key_pair: RingEd25519KeyPair::from_bytes(
private_key,
public_key,
).map_err(|_| Error::new("invalid key pair"))?,
version: version,
})
}
fn sign(&self, value: &Value) -> Result<Signature, Error> {
let json = signable_json(value)?;
Ok(Signature {
algorithm: Algorithm::Ed25519,
signature: self.ring_key_pair.sign(json.as_bytes()).as_slice().to_vec(),
version: self.version.clone(),
})
}
}
impl Error {
pub fn new<T>(message: T) -> Self where T: Into<String> {
Error {
@ -56,58 +261,57 @@ impl Display for Error {
}
}
/// A single digital signature.
///
/// Generated from `SigningKey`.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct Signature {
algorithm: SigningAlgorithm,
signature: SodiumSignature,
version: String,
}
/// A set of signatures created by a single homeserver.
pub struct SignatureSet {
set: HashSet<Signature>,
}
/// Serde Visitor for deserializing `SignatureSet`.
struct SignatureSetVisitor;
/// The algorithm used for signing.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SigningAlgorithm {
/// The Ed25519 digital signature algorithm.
Ed25519,
}
/// A signing key, consisting of an algorithm, a secret key, and a key version.
#[derive(Debug)]
pub struct SigningKey {
algorithm: SigningAlgorithm,
key: SecretKey,
version: String,
}
impl Signature {
/// Creates a signature from raw bytes.
pub fn new(id: &str, bytes: &[u8]) -> Result<Self, Error> {
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(Signature {
algorithm: algorithm,
signature: bytes.to_vec(),
version: version,
})
}
/// The algorithm used to generate the signature.
pub fn algorithm(&self) -> SigningAlgorithm {
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
/// The raw bytes of the signature.
pub fn as_bytes(&self) -> &[u8] {
self.signature.as_ref()
self.signature.as_slice()
}
/// A Base64 encoding of the signature.
pub fn base64(&self) -> String {
self.signature.as_ref().to_base64(BASE64_CONFIG)
self.signature.as_slice().to_base64(BASE64_CONFIG)
}
/// A string containing the signature algorithm and the key "version" separated by a colon.
pub fn id(&self) -> String {
format!("ed25519:{}", self.version())
format!("{}:{}", self.algorithm, self.version)
}
/// 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> {
match self.algorithm {
Algorithm::Ed25519 => {
verify(
&ED25519,
Input::from(public_key),
Input::from(signable_json(value)?.as_bytes()),
Input::from(self.as_bytes()),
).map_err(|_| Error::new("signature verification failed"))
}
}
}
/// The "version" of the key used for this signature.
@ -172,40 +376,25 @@ impl Visitor for SignatureSetVisitor {
fn visit_map<M>(&mut self, mut visitor: M) -> Result<Self::Value, M::Error>
where M: MapVisitor {
const SIGNATURE_ID_LENGTH: usize = 2;
let mut signature_set = SignatureSet::with_capacity(visitor.size_hint().0);
while let Some((key, value)) = try!(visitor.visit::<String, String>()) {
let signature_id: Vec<&str> = key.split(':').collect();
let (algorithm, version) = split_id(&key).map_err(|split_error| {
match split_error {
SplitError::InvalidLength(length) => M::Error::invalid_length(length),
SplitError::UnknownAlgorithm(algorithm) => M::Error::invalid_value(algorithm),
}
})?;
let signature_id_length = signature_id.len();
if signature_id_length != SIGNATURE_ID_LENGTH {
return Err(M::Error::invalid_length(signature_id_length));
}
let algorithm_input = signature_id[0];
let algorithm = match algorithm_input {
"ed25519" => SigningAlgorithm::Ed25519,
_ => return Err(M::Error::invalid_value(algorithm_input)),
};
let raw_signature: Vec<u8> = match value.from_base64() {
let signature_bytes: Vec<u8> = match value.from_base64() {
Ok(raw) => raw,
Err(error) => return Err(M::Error::custom(error.description())),
};
let sodium_signature = match SodiumSignature::from_slice(&raw_signature) {
Some(s) => s,
None => return Err(M::Error::invalid_value("invalid Ed25519 signature")),
};
let signature = Signature {
algorithm: algorithm,
signature: sodium_signature,
version: signature_id[1].to_string(),
signature: signature_bytes,
version: version,
};
signature_set.insert(signature);
@ -217,48 +406,13 @@ impl Visitor for SignatureSetVisitor {
}
}
impl SigningKey {
/// Initialize a new signing key.
///
/// # Parameters
///
/// * algorithm: The digital signature algorithm to use.
/// * key: A 64-byte secret key.
/// * 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 new(algorithm: SigningAlgorithm, key: [u8; 64], version: String) -> Self {
SigningKey {
algorithm: algorithm,
key: SecretKey(key),
version: version,
}
}
/// Sign a JSON object.
pub fn sign(&self, value: &Value) -> Result<Signature, Error> {
if !value.is_object() {
return Err(Error::new("JSON value must be a JSON object"));
}
let mut owned_value = value.clone();
{
let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
hash.remove("signatures");
hash.remove("unsigned");
}
let json = match to_string(&owned_value) {
Ok(json) => json,
Err(error) => return Err(Error::new(error.description())),
impl Display for Algorithm {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let name = match *self {
Algorithm::Ed25519 => "ed25519",
};
Ok(Signature {
algorithm: self.algorithm,
signature: sign_detached(json.as_bytes(), &self.key),
version: self.version.clone(),
})
write!(f, "{}", name)
}
}
@ -266,22 +420,34 @@ impl SigningKey {
mod test {
use rustc_serialize::base64::FromBase64;
use serde_json::from_str;
use sodiumoxide::crypto::sign::{SecretKey, Seed, keypair_from_seed};
use super::{SigningAlgorithm, SigningKey};
use super::{Ed25519KeyPair, KeyPair, Signature};
const PUBLIC_KEY: &'static str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
const PRIVATE_KEY: &'static str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
const EMPTY_JSON_SIGNATURE: &'static str =
"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
#[test]
fn empty_json() {
let seed_vec = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1".from_base64().unwrap();
let seed = Seed::from_slice(&seed_vec[..]).unwrap();
let (_pubkey, seckey) = keypair_from_seed(&seed);
let SecretKey(raw_seckey) = seckey;
let signing_key = SigningKey::new(SigningAlgorithm::Ed25519, raw_seckey, "1".to_owned());
fn sign_empty_json() {
let key_pair = Ed25519KeyPair::new(
&PUBLIC_KEY.from_base64().unwrap(),
&PRIVATE_KEY.from_base64().unwrap(),
"1".to_string(),
).unwrap();
let value = from_str("{}").unwrap();
let actual = signing_key.sign(&value).unwrap().base64();
assert_eq!(
actual,
"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
);
let signature = key_pair.sign(&value).unwrap();
assert_eq!(signature.base64(), EMPTY_JSON_SIGNATURE);
}
#[test]
fn verify_empty_json() {
let signature = Signature::new(
"ed25519:1",
&EMPTY_JSON_SIGNATURE.from_base64().unwrap(),
).unwrap();
let value = from_str("{}").unwrap();
assert!(signature.verify(&PUBLIC_KEY.from_base64().unwrap(), &value).is_ok());
}
}