Determine Verifier automatically so verifier types don't need to be public.

This commit is contained in:
Jimmy Cuadra 2019-07-12 03:23:49 -07:00
parent 8da921cffa
commit 593d0e469b
3 changed files with 78 additions and 93 deletions

View File

@ -9,7 +9,8 @@ use serde_json::{from_str, from_value, map::Map, to_string, to_value, Value};
use crate::{
keys::KeyPair,
signatures::SignatureMap,
verification::{Verified, Verifier},
split_id,
verification::{Ed25519Verifier, Verified, Verifier},
Error,
};
@ -178,7 +179,6 @@ pub fn canonical_json(value: &Value) -> Result<String, Error> {
///
/// # Parameters
///
/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used.
/// * public_key_map: A map from entity identifiers to a map from key identifiers to public keys.
/// Generally, entity identifiers are server names—the host/IP/port of a homeserver (e.g.
/// "example.com") for which a signature must be verified. Key identifiers for each server (e.g.
@ -216,9 +216,6 @@ pub fn canonical_json(value: &Value) -> Result<String, Error> {
/// }"#
/// ).unwrap();
///
/// // Create the verifier for the Ed25519 algorithm.
/// let verifier = ruma_signatures::Ed25519Verifier;
///
/// // Create the `SignatureMap` that will inform `verify_json` which signatures to verify.
/// let mut signature_set = HashMap::new();
/// signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
@ -226,16 +223,9 @@ pub fn canonical_json(value: &Value) -> Result<String, Error> {
/// public_key_map.insert("example.com".to_string(), signature_set);
///
/// // Verify at least one signature for each entity in `public_key_map`.
/// assert!(ruma_signatures::verify_json(&verifier, &public_key_map, &value).is_ok());
/// assert!(ruma_signatures::verify_json(&public_key_map, &value).is_ok());
/// ```
pub fn verify_json<V>(
verifier: &V,
public_key_map: &SignatureMap,
value: &Value,
) -> Result<(), Error>
where
V: Verifier,
{
pub fn verify_json(public_key_map: &SignatureMap, value: &Value) -> Result<(), Error> {
let map = match value {
Value::Object(ref map) => map,
_ => return Err(Error::new("JSON value must be a JSON object")),
@ -264,6 +254,12 @@ where
let mut maybe_public_key = None;
for (key_id, public_key) in public_keys {
// Since only ed25519 is supported right now, we don't actually need to check what the
// algorithm is. If it split successfully, it's ed25519.
if split_id(key_id).is_err() {
break;
}
if let Some(signature) = signature_set.get(key_id) {
maybe_signature = Some(signature);
maybe_public_key = Some(public_key);
@ -294,7 +290,7 @@ where
let public_key_bytes = decode_config(&public_key, STANDARD_NO_PAD)?;
verify_json_with(verifier, &public_key_bytes, &signature_bytes, value)?;
verify_json_with(&Ed25519Verifier, &public_key_bytes, &signature_bytes, value)?;
}
Ok(())
@ -514,7 +510,6 @@ where
///
/// # Parameters
///
/// * verifier: A `Verifier` appropriate for the digital signature algorithm that was used.
/// * public_key_map: A map from entity identifiers to a map from key identifiers to public keys.
/// Generally, entity identifiers are server names—the host/IP/port of a homeserver (e.g.
/// "example.com") for which a signature must be verified. Key identifiers for each server (e.g.
@ -554,9 +549,6 @@ where
/// }"#
/// ).unwrap();
///
/// // Create the verifier for the Ed25519 algorithm.
/// let verifier = ruma_signatures::Ed25519Verifier;
///
/// // Create a map from key ID to public key.
/// let mut example_server_keys = HashMap::new();
/// example_server_keys.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
@ -566,16 +558,9 @@ where
/// public_key_map.insert("domain".to_string(), example_server_keys);
///
/// // Verify at least one signature for each entity in `public_key_map`.
/// assert!(ruma_signatures::verify_event(&verifier, &public_key_map, &value).is_ok());
/// assert!(ruma_signatures::verify_event(&public_key_map, &value).is_ok());
/// ```
pub fn verify_event<V>(
verifier: &V,
public_key_map: &SignatureMap,
value: &Value,
) -> Result<Verified, Error>
where
V: Verifier,
{
pub fn verify_event(public_key_map: &SignatureMap, value: &Value) -> Result<Verified, Error> {
let redacted = redact(value)?;
let map = match redacted {
@ -620,6 +605,12 @@ where
let mut maybe_public_key = None;
for (key_id, public_key) in public_keys {
// Since only ed25519 is supported right now, we don't actually need to check what the
// algorithm is. If it split successfully, it's ed25519.
if split_id(key_id).is_err() {
break;
}
if let Some(signature) = signature_set.get(key_id) {
maybe_signature = Some(signature);
maybe_public_key = Some(public_key);
@ -653,7 +644,7 @@ where
let public_key_bytes = decode_config(&public_key, STANDARD_NO_PAD)?;
verify_json_with(
verifier,
&Ed25519Verifier,
&public_key_bytes,
&signature_bytes,
&canonical_json,

View File

@ -106,7 +106,6 @@ pub use functions::{
};
pub use keys::{Ed25519KeyPair, KeyPair};
pub use signatures::{Signature, SignatureMap, SignatureSet};
pub use verification::{Ed25519Verifier, Verified, Verifier};
mod functions;
mod keys;
@ -177,6 +176,53 @@ impl Display for Algorithm {
}
}
/// 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 does not have exactly two components separated by a colon.
InvalidLength(usize),
/// The signature's ID contains invalid characters in its version.
InvalidVersion(&'a str),
/// 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 version = signature_id[1];
let invalid_character_index = version.find(|ch| {
!((ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| ch == '_')
});
if invalid_character_index.is_some() {
return Err(SplitError::InvalidVersion(version));
}
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()))
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
@ -187,7 +233,6 @@ mod test {
use super::{
canonical_json, hash_and_sign_event, sign_json, verify_event, verify_json, Ed25519KeyPair,
Ed25519Verifier,
};
const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
@ -317,15 +362,13 @@ mod test {
fn verify_empty_json() {
let value = from_str(r#"{"signatures":{"example.com":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#).unwrap();
let verifier = Ed25519Verifier;
let mut signature_set = HashMap::new();
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
let mut public_key_map = HashMap::new();
public_key_map.insert("example.com".to_string(), signature_set);
assert!(verify_json(&verifier, &public_key_map, &value).is_ok());
assert!(verify_json(&public_key_map, &value).is_ok());
}
#[test]
@ -387,36 +430,32 @@ mod test {
r#"{"one":1,"signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
).unwrap();
let verifier = Ed25519Verifier;
let mut signature_set = HashMap::new();
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
let mut public_key_map = HashMap::new();
public_key_map.insert("example.com".to_string(), signature_set);
assert!(verify_json(&verifier, &public_key_map, &value).is_ok());
assert!(verify_json(&public_key_map, &value).is_ok());
let reverse_value = from_str(
r#"{"two":"Two","signatures":{"example.com":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"one":1}"#
).unwrap();
assert!(verify_json(&verifier, &public_key_map, &reverse_value).is_ok());
assert!(verify_json(&public_key_map, &reverse_value).is_ok());
}
#[test]
fn fail_verify_json() {
let value = from_str(r#"{"not":"empty","signatures":{"example.com":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}"#).unwrap();
let verifier = Ed25519Verifier;
let mut signature_set = HashMap::new();
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
let mut public_key_map = HashMap::new();
public_key_map.insert("example.com".to_string(), signature_set);
assert!(verify_json(&verifier, &public_key_map, &value).is_err());
assert!(verify_json(&public_key_map, &value).is_err());
}
#[test]
@ -529,8 +568,6 @@ mod test {
}"#
).unwrap();
let verifier = Ed25519Verifier;
assert!(verify_event(&verifier, &public_key_map, &value).is_ok());
assert!(verify_event(&public_key_map, &value).is_ok());
}
}

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use base64::{encode_config, STANDARD_NO_PAD};
use crate::{Algorithm, Error};
use crate::{split_id, Algorithm, Error, SplitError};
/// A digital signature.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
@ -38,7 +38,11 @@ impl Signature {
///
/// # Errors
///
/// Returns an error if the key identifier is invalid.
/// Returns an error if:
///
/// * The key ID specifies an unknown algorithm.
/// * The key ID is malformed.
/// * The key ID contains a version with invalid characters.
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(length) => Error::new(format!("malformed signature ID: expected exactly 2 segment separated by a colon, found {}", length)),
@ -97,53 +101,6 @@ pub type SignatureMap = HashMap<String, SignatureSet>;
/// This is represented as a map from signing key ID to Base64-encoded signature.
pub type SignatureSet = HashMap<String, String>;
/// 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 does not have exactly two components separated by a colon.
InvalidLength(usize),
/// The signature's ID contains invalid characters in its version.
InvalidVersion(&'a str),
/// 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 version = signature_id[1];
let invalid_character_index = version.find(|ch| {
!((ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| ch == '_')
});
if invalid_character_index.is_some() {
return Err(SplitError::InvalidVersion(version));
}
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()))
}
#[cfg(test)]
mod tests {
use super::Signature;