Add PublicKey{Map,Set} and make the Signature versions private.

This commit is contained in:
Jimmy Cuadra 2019-07-12 03:50:49 -07:00
parent 593d0e469b
commit daf160324c
3 changed files with 44 additions and 33 deletions

View File

@ -7,7 +7,7 @@ use ring::digest::{digest, SHA256};
use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value}; use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value};
use crate::{ use crate::{
keys::KeyPair, keys::{KeyPair, PublicKeyMap},
signatures::SignatureMap, signatures::SignatureMap,
split_id, split_id,
verification::{Ed25519Verifier, Verified, Verifier}, verification::{Ed25519Verifier, Verified, Verifier},
@ -95,7 +95,7 @@ static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "uns
/// let mut value = serde_json::from_str("{}").unwrap(); /// let mut value = serde_json::from_str("{}").unwrap();
/// ///
/// // Sign the JSON with the key pair. /// // Sign the JSON with the key pair.
/// assert!(ruma_signatures::sign_json("example.com", &key_pair, &mut value).is_ok()); /// assert!(ruma_signatures::sign_json("domain", &key_pair, &mut value).is_ok());
/// ``` /// ```
/// ///
/// This will modify the JSON from an empty object to a structure like this: /// This will modify the JSON from an empty object to a structure like this:
@ -103,7 +103,7 @@ static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "uns
/// ```json /// ```json
/// { /// {
/// "signatures": { /// "signatures": {
/// "example.com": { /// "domain": {
/// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" /// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
/// } /// }
/// } /// }
@ -209,23 +209,23 @@ pub fn canonical_json(value: &Value) -> Result<String, Error> {
/// let value = serde_json::from_str( /// let value = serde_json::from_str(
/// r#"{ /// r#"{
/// "signatures": { /// "signatures": {
/// "example.com": { /// "domain": {
/// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" /// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
/// } /// }
/// } /// }
/// }"# /// }"#
/// ).unwrap(); /// ).unwrap();
/// ///
/// // Create the `SignatureMap` that will inform `verify_json` which signatures to verify. /// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
/// let mut signature_set = HashMap::new(); /// let mut public_key_set = HashMap::new();
/// signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); /// public_key_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
/// let mut public_key_map = HashMap::new(); /// let mut public_key_map = HashMap::new();
/// public_key_map.insert("example.com".to_string(), signature_set); /// public_key_map.insert("domain".to_string(), public_key_set);
/// ///
/// // Verify at least one signature for each entity in `public_key_map`. /// // Verify at least one signature for each entity in `public_key_map`.
/// assert!(ruma_signatures::verify_json(&public_key_map, &value).is_ok()); /// assert!(ruma_signatures::verify_json(&public_key_map, &value).is_ok());
/// ``` /// ```
pub fn verify_json(public_key_map: &SignatureMap, value: &Value) -> Result<(), Error> { pub fn verify_json(public_key_map: &PublicKeyMap, value: &Value) -> Result<(), Error> {
let map = match value { let map = match value {
Value::Object(ref map) => map, Value::Object(ref map) => map,
_ => return Err(Error::new("JSON value must be a JSON object")), _ => return Err(Error::new("JSON value must be a JSON object")),
@ -431,7 +431,7 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
/// ).unwrap(); /// ).unwrap();
/// ///
/// // Hash and sign the JSON with the key pair. /// // Hash and sign the JSON with the key pair.
/// assert!(ruma_signatures::hash_and_sign_event("example.com", &key_pair, &mut value).is_ok()); /// assert!(ruma_signatures::hash_and_sign_event("domain", &key_pair, &mut value).is_ok());
/// ``` /// ```
/// ///
/// This will modify the JSON from the structure shown to a structure like this: /// This will modify the JSON from the structure shown to a structure like this:
@ -549,18 +549,16 @@ where
/// }"# /// }"#
/// ).unwrap(); /// ).unwrap();
/// ///
/// // Create a map from key ID to public key. /// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
/// let mut example_server_keys = HashMap::new(); /// let mut public_key_set = HashMap::new();
/// example_server_keys.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); /// public_key_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
///
/// // Insert the public keys into a map keyed by entity ID.
/// let mut public_key_map = HashMap::new(); /// let mut public_key_map = HashMap::new();
/// public_key_map.insert("domain".to_string(), example_server_keys); /// public_key_map.insert("domain".to_string(), public_key_set);
/// ///
/// // Verify at least one signature for each entity in `public_key_map`. /// // Verify at least one signature for each entity in `public_key_map`.
/// assert!(ruma_signatures::verify_event(&public_key_map, &value).is_ok()); /// assert!(ruma_signatures::verify_event(&public_key_map, &value).is_ok());
/// ``` /// ```
pub fn verify_event(public_key_map: &SignatureMap, value: &Value) -> Result<Verified, Error> { pub fn verify_event(public_key_map: &PublicKeyMap, value: &Value) -> Result<Verified, Error> {
let redacted = redact(value)?; let redacted = redact(value)?;
let map = match redacted { let map = match redacted {

View File

@ -1,6 +1,9 @@
//! Public and private key pairs. //! Public and private key pairs.
use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::{
collections::HashMap,
fmt::{Debug, Formatter, Result as FmtResult},
};
use ring::signature::Ed25519KeyPair as RingEd25519KeyPair; use ring::signature::Ed25519KeyPair as RingEd25519KeyPair;
use untrusted::Input; use untrusted::Input;
@ -87,3 +90,13 @@ impl Debug for Ed25519KeyPair {
.finish() .finish()
} }
} }
/// A map from entity names to sets of public keys for that entity.
///
/// "Entity" is generally a homeserver, e.g. "example.com".
pub type PublicKeyMap = HashMap<String, PublicKeySet>;
/// A set of public keys for a single homeserver.
///
/// This is represented as a map from key ID to Base64-encoded signature.
pub type PublicKeySet = HashMap<String, String>;

View File

@ -104,8 +104,8 @@ pub use functions::{
canonical_json, content_hash, hash_and_sign_event, redact, reference_hash, sign_json, canonical_json, content_hash, hash_and_sign_event, redact, reference_hash, sign_json,
verify_event, verify_json, verify_event, verify_json,
}; };
pub use keys::{Ed25519KeyPair, KeyPair}; pub use keys::{Ed25519KeyPair, KeyPair, PublicKeyMap, PublicKeySet};
pub use signatures::{Signature, SignatureMap, SignatureSet}; pub use signatures::Signature;
mod functions; mod functions;
mod keys; mod keys;
@ -350,23 +350,23 @@ mod test {
let mut value = from_str("{}").unwrap(); let mut value = from_str("{}").unwrap();
sign_json("example.com", &key_pair, &mut value).unwrap(); sign_json("domain", &key_pair, &mut value).unwrap();
assert_eq!( assert_eq!(
to_string(&value).unwrap(), to_string(&value).unwrap(),
r#"{"signatures":{"example.com":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"# r#"{"signatures":{"domain":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#
); );
} }
#[test] #[test]
fn verify_empty_json() { fn verify_empty_json() {
let value = from_str(r#"{"signatures":{"example.com":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#).unwrap(); let value = from_str(r#"{"signatures":{"domain":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#).unwrap();
let mut signature_set = HashMap::new(); let mut signature_set = HashMap::new();
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
let mut public_key_map = HashMap::new(); let mut public_key_map = HashMap::new();
public_key_map.insert("example.com".to_string(), signature_set); public_key_map.insert("domain".to_string(), signature_set);
assert!(verify_json(&public_key_map, &value).is_ok()); assert!(verify_json(&public_key_map, &value).is_ok());
} }
@ -407,39 +407,39 @@ mod test {
}; };
let mut alpha_value = to_value(alpha).expect("alpha should serialize"); let mut alpha_value = to_value(alpha).expect("alpha should serialize");
sign_json("example.com", &key_pair, &mut alpha_value).unwrap(); sign_json("domain", &key_pair, &mut alpha_value).unwrap();
assert_eq!( assert_eq!(
to_string(&alpha_value).unwrap(), to_string(&alpha_value).unwrap(),
r#"{"one":1,"signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"# r#"{"one":1,"signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
); );
let mut reverse_alpha_value = let mut reverse_alpha_value =
to_value(reverse_alpha).expect("reverse_alpha should serialize"); to_value(reverse_alpha).expect("reverse_alpha should serialize");
sign_json("example.com", &key_pair, &mut reverse_alpha_value).unwrap(); sign_json("domain", &key_pair, &mut reverse_alpha_value).unwrap();
assert_eq!( assert_eq!(
to_string(&reverse_alpha_value).unwrap(), to_string(&reverse_alpha_value).unwrap(),
r#"{"one":1,"signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"# r#"{"one":1,"signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
); );
} }
#[test] #[test]
fn verify_minimal_json() { fn verify_minimal_json() {
let value = from_str( let value = from_str(
r#"{"one":1,"signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"# r#"{"one":1,"signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
).unwrap(); ).unwrap();
let mut signature_set = HashMap::new(); let mut signature_set = HashMap::new();
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
let mut public_key_map = HashMap::new(); let mut public_key_map = HashMap::new();
public_key_map.insert("example.com".to_string(), signature_set); public_key_map.insert("domain".to_string(), signature_set);
assert!(verify_json(&public_key_map, &value).is_ok()); assert!(verify_json(&public_key_map, &value).is_ok());
let reverse_value = from_str( let reverse_value = from_str(
r#"{"two":"Two","signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"one":1}"# r#"{"two":"Two","signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"one":1}"#
).unwrap(); ).unwrap();
assert!(verify_json(&public_key_map, &reverse_value).is_ok()); assert!(verify_json(&public_key_map, &reverse_value).is_ok());
@ -447,13 +447,13 @@ mod test {
#[test] #[test]
fn fail_verify_json() { fn fail_verify_json() {
let value = from_str(r#"{"not":"empty","signatures":{"example.com":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}"#).unwrap(); let value = from_str(r#"{"not":"empty","signatures":{"domain":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}"#).unwrap();
let mut signature_set = HashMap::new(); let mut signature_set = HashMap::new();
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string()); signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
let mut public_key_map = HashMap::new(); let mut public_key_map = HashMap::new();
public_key_map.insert("example.com".to_string(), signature_set); public_key_map.insert("domain".to_string(), signature_set);
assert!(verify_json(&public_key_map, &value).is_err()); assert!(verify_json(&public_key_map, &value).is_err());
} }