Determine Verifier automatically so verifier types don't need to be public.
This commit is contained in:
parent
8da921cffa
commit
593d0e469b
@ -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,
|
||||
|
67
src/lib.rs
67
src/lib.rs
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user