Split the library into modules.
This commit is contained in:
parent
03fcf7281d
commit
97ee073e11
89
src/keys.rs
Normal file
89
src/keys.rs
Normal file
@ -0,0 +1,89 @@
|
||||
//! Public and private key pairs.
|
||||
|
||||
use std::fmt::{Debug, Formatter, Result as FmtResult};
|
||||
|
||||
use ring::signature::Ed25519KeyPair as RingEd25519KeyPair;
|
||||
use untrusted::Input;
|
||||
|
||||
use crate::{signatures::Signature, Algorithm, Error};
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// * message: An arbitrary series of bytes to sign.
|
||||
fn sign(&self, message: &[u8]) -> Signature;
|
||||
}
|
||||
|
||||
/// An Ed25519 key pair.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Ed25519KeyPair {
|
||||
/// The public key.
|
||||
public_key: Vec<u8>,
|
||||
|
||||
/// The private key.
|
||||
private_key: Vec<u8>,
|
||||
|
||||
/// The version of the key pair.
|
||||
version: String,
|
||||
}
|
||||
|
||||
impl KeyPair for Ed25519KeyPair {
|
||||
fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result<Self, Error> {
|
||||
if let Err(error) = RingEd25519KeyPair::from_seed_and_public_key(
|
||||
Input::from(private_key),
|
||||
Input::from(public_key),
|
||||
) {
|
||||
return Err(Error::new(error.to_string()));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
public_key: public_key.to_owned(),
|
||||
private_key: private_key.to_owned(),
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
fn sign(&self, message: &[u8]) -> Signature {
|
||||
// Okay to unwrap because we verified the input in the `new`.
|
||||
let ring_key_pair = RingEd25519KeyPair::from_seed_and_public_key(
|
||||
Input::from(&self.private_key),
|
||||
Input::from(&self.public_key),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Signature {
|
||||
algorithm: Algorithm::Ed25519,
|
||||
signature: ring_key_pair.sign(message).as_ref().to_vec(),
|
||||
version: self.version.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Ed25519KeyPair {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> FmtResult {
|
||||
formatter
|
||||
.debug_struct("Ed25519KeyPair")
|
||||
.field("public_key", &self.public_key)
|
||||
.field("version", &self.version)
|
||||
.finish()
|
||||
}
|
||||
}
|
499
src/lib.rs
499
src/lib.rs
@ -143,24 +143,22 @@
|
||||
)]
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
error::Error as StdError,
|
||||
fmt::{Debug, Display, Formatter, Result as FmtResult},
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
use base64::{decode_config, encode_config, STANDARD_NO_PAD};
|
||||
use ring::signature::{verify, Ed25519KeyPair as RingEd25519KeyPair, ED25519};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, MapAccess, Unexpected, Visitor},
|
||||
ser::SerializeMap,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use serde_json::{to_string, Value};
|
||||
use untrusted::Input;
|
||||
use url::Url;
|
||||
|
||||
pub use url::Host;
|
||||
|
||||
pub use keys::{Ed25519KeyPair, KeyPair};
|
||||
pub use signatures::{Signature, SignatureSet, Signatures};
|
||||
pub use verification::{Ed25519Verifier, Verifier};
|
||||
|
||||
mod keys;
|
||||
mod signatures;
|
||||
mod verification;
|
||||
|
||||
/// Signs an arbitrary JSON object.
|
||||
///
|
||||
/// # Parameters
|
||||
@ -231,62 +229,6 @@ where
|
||||
verifier.verify_json(public_key, signature, to_canonical_json(value)?.as_bytes())
|
||||
}
|
||||
|
||||
/// 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 has an invalid length.
|
||||
InvalidLength(usize),
|
||||
/// 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 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()))
|
||||
}
|
||||
|
||||
/// The algorithm used for signing data.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Algorithm {
|
||||
/// The Ed25519 digital signature algorithm.
|
||||
Ed25519,
|
||||
}
|
||||
|
||||
/// An Ed25519 key pair.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Ed25519KeyPair {
|
||||
/// The public key.
|
||||
public_key: Vec<u8>,
|
||||
|
||||
/// The private key.
|
||||
private_key: Vec<u8>,
|
||||
|
||||
/// The version of the key pair.
|
||||
version: String,
|
||||
}
|
||||
|
||||
/// A verifier for Ed25519 digital signatures.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Ed25519Verifier;
|
||||
|
||||
/// An error produced when ruma-signatures operations fail.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Error {
|
||||
@ -294,143 +236,6 @@ 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
|
||||
///
|
||||
/// * message: An arbitrary series of bytes to sign.
|
||||
fn sign(&self, message: &[u8]) -> Signature;
|
||||
}
|
||||
|
||||
/// A digital signature.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Signature {
|
||||
/// The cryptographic algorithm to use.
|
||||
algorithm: Algorithm,
|
||||
/// The signature data.
|
||||
signature: Vec<u8>,
|
||||
/// The version of the signature.
|
||||
version: String,
|
||||
}
|
||||
|
||||
/// A map of server names to sets of digital signatures created by that server.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Signatures {
|
||||
/// A map of homeservers to sets of signatures for the homeserver.
|
||||
map: HashMap<Host, SignatureSet>,
|
||||
}
|
||||
|
||||
/// Serde Visitor for deserializing `Signatures`.
|
||||
struct SignaturesVisitor;
|
||||
|
||||
/// A set of digital signatures created by a single homeserver.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct SignatureSet {
|
||||
/// A set of signatures for a homeserver.
|
||||
set: HashSet<Signature>,
|
||||
}
|
||||
|
||||
/// Serde Visitor for deserializing `SignatureSet`.
|
||||
struct SignatureSetVisitor;
|
||||
|
||||
/// A digital signature verifier.
|
||||
pub trait Verifier {
|
||||
/// Use a public key to verify a signature against the JSON object that was signed.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * public_key: The public key of the key pair used to sign the message.
|
||||
/// * signature: The `Signature` to verify.
|
||||
/// * message: The message that was signed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if verification fails.
|
||||
fn verify_json(
|
||||
&self,
|
||||
public_key: &[u8],
|
||||
signature: &Signature,
|
||||
message: &[u8],
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl KeyPair for Ed25519KeyPair {
|
||||
fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result<Self, Error> {
|
||||
if let Err(error) = RingEd25519KeyPair::from_seed_and_public_key(
|
||||
Input::from(private_key),
|
||||
Input::from(public_key),
|
||||
) {
|
||||
return Err(Error::new(error.to_string()));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
public_key: public_key.to_owned(),
|
||||
private_key: private_key.to_owned(),
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
fn sign(&self, message: &[u8]) -> Signature {
|
||||
// Okay to unwrap because we verified the input in the `new`.
|
||||
let ring_key_pair = RingEd25519KeyPair::from_seed_and_public_key(
|
||||
Input::from(&self.private_key),
|
||||
Input::from(&self.public_key),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Signature {
|
||||
algorithm: Algorithm::Ed25519,
|
||||
signature: ring_key_pair.sign(message).as_ref().to_vec(),
|
||||
version: self.version.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Ed25519KeyPair {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> FmtResult {
|
||||
formatter
|
||||
.debug_struct("Ed25519KeyPair")
|
||||
.field("public_key", &self.public_key)
|
||||
.field("version", &self.version)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier for Ed25519Verifier {
|
||||
fn verify_json(
|
||||
&self,
|
||||
public_key: &[u8],
|
||||
signature: &Signature,
|
||||
message: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
verify(
|
||||
&ED25519,
|
||||
Input::from(public_key),
|
||||
Input::from(message),
|
||||
Input::from(signature.as_bytes()),
|
||||
)
|
||||
.map_err(|_| Error::new("signature verification failed"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Creates a new error.
|
||||
///
|
||||
@ -459,287 +264,11 @@ impl Display for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Creates a signature from raw bytes.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * id: A key identifier, e.g. "ed25519:1".
|
||||
/// * bytes: The digital signature, as a series of bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the key identifier is invalid.
|
||||
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(Self {
|
||||
algorithm,
|
||||
signature: bytes.to_vec(),
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
/// The algorithm used to generate the signature.
|
||||
pub fn algorithm(&self) -> Algorithm {
|
||||
self.algorithm
|
||||
}
|
||||
|
||||
/// The raw bytes of the signature.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.signature.as_slice()
|
||||
}
|
||||
|
||||
/// A Base64 encoding of the signature.
|
||||
///
|
||||
/// Uses the standard character set with no padding.
|
||||
pub fn base64(&self) -> String {
|
||||
encode_config(self.signature.as_slice(), STANDARD_NO_PAD)
|
||||
}
|
||||
|
||||
/// The key identifier, a string containing the signature algorithm and the key "version"
|
||||
/// separated by a colon, e.g. "ed25519:1".
|
||||
pub fn id(&self) -> String {
|
||||
format!("{}:{}", self.algorithm, self.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 version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
|
||||
impl Signatures {
|
||||
/// Initializes a new empty Signatures.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes a new empty Signatures with room for a specific number of servers.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * capacity: The number of items to allocate memory for.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
map: HashMap::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a signature set for a server.
|
||||
///
|
||||
/// If no signature set for the given server existed in the collection, `None` is returned.
|
||||
/// Otherwise, the signature set is returned.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * server_name: The hostname or IP of the homeserver, e.g. `example.com`.
|
||||
/// * signature_set: The `SignatureSet` containing the digital signatures made by the server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the given server name cannot be parsed as a valid host.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
server_name: &str,
|
||||
signature_set: SignatureSet,
|
||||
) -> Result<Option<SignatureSet>, Error> {
|
||||
let url_string = format!("https://{}", server_name);
|
||||
let url = Url::parse(&url_string)
|
||||
.map_err(|_| Error::new(format!("invalid server name: {}", server_name)))?;
|
||||
|
||||
let host = match url.host() {
|
||||
Some(host) => host.to_owned(),
|
||||
None => return Err(Error::new(format!("invalid server name: {}", server_name))),
|
||||
};
|
||||
|
||||
Ok(self.map.insert(host, signature_set))
|
||||
}
|
||||
|
||||
/// The number of servers in the collection.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Whether or not the collection of signatures is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Signatures {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(SignaturesVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Signatures {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map_serializer = serializer.serialize_map(Some(self.len()))?;
|
||||
|
||||
for (host, signature_set) in self.map.iter() {
|
||||
map_serializer.serialize_key(&host.to_string())?;
|
||||
map_serializer.serialize_value(signature_set)?;
|
||||
}
|
||||
|
||||
map_serializer.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for SignaturesVisitor {
|
||||
type Value = Signatures;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(formatter, "digital signatures")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut visitor: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut signatures = match visitor.size_hint() {
|
||||
Some(capacity) => Signatures::with_capacity(capacity),
|
||||
None => Signatures::new(),
|
||||
};
|
||||
|
||||
while let Some((server_name, signature_set)) =
|
||||
visitor.next_entry::<String, SignatureSet>()?
|
||||
{
|
||||
if signatures.insert(&server_name, signature_set).is_err() {
|
||||
return Err(M::Error::invalid_value(
|
||||
Unexpected::Str(&server_name),
|
||||
&self,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(signatures)
|
||||
}
|
||||
}
|
||||
|
||||
impl SignatureSet {
|
||||
/// Initializes a new empty SignatureSet.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
set: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes a new empty SignatureSet with room for a specific number of signatures.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * capacity: The number of items to allocate memory for.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
set: HashSet::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a signature to the set.
|
||||
///
|
||||
/// The boolean return value indicates whether or not the value was actually inserted, since
|
||||
/// subsequent inserts of the same signature have no effect.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * signature: A `Signature` to insert into the set.
|
||||
pub fn insert(&mut self, signature: Signature) -> bool {
|
||||
self.set.insert(signature)
|
||||
}
|
||||
|
||||
/// The number of signatures in the set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
}
|
||||
|
||||
/// Whether or not the set of signatures is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SignatureSet {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(SignatureSetVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SignatureSet {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map_serializer = serializer.serialize_map(Some(self.len()))?;
|
||||
|
||||
for signature in self.set.iter() {
|
||||
map_serializer.serialize_key(&signature.id())?;
|
||||
map_serializer.serialize_value(&signature.base64())?;
|
||||
}
|
||||
|
||||
map_serializer.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for SignatureSetVisitor {
|
||||
type Value = SignatureSet;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(formatter, "a set of digital signatures")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut visitor: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut signature_set = match visitor.size_hint() {
|
||||
Some(capacity) => SignatureSet::with_capacity(capacity),
|
||||
None => SignatureSet::new(),
|
||||
};
|
||||
|
||||
while let Some((key, value)) = visitor.next_entry::<String, String>()? {
|
||||
let (algorithm, version) = split_id(&key).map_err(|split_error| match split_error {
|
||||
SplitError::InvalidLength(length) => M::Error::invalid_length(length, &self),
|
||||
SplitError::UnknownAlgorithm(algorithm) => {
|
||||
M::Error::invalid_value(Unexpected::Str(algorithm), &self)
|
||||
}
|
||||
})?;
|
||||
|
||||
let signature_bytes: Vec<u8> = match decode_config(&value, STANDARD_NO_PAD) {
|
||||
Ok(raw) => raw,
|
||||
Err(error) => return Err(M::Error::custom(error.description())),
|
||||
};
|
||||
|
||||
let signature = Signature {
|
||||
algorithm,
|
||||
signature: signature_bytes,
|
||||
version,
|
||||
};
|
||||
|
||||
signature_set.insert(signature);
|
||||
}
|
||||
|
||||
Ok(signature_set)
|
||||
}
|
||||
/// The algorithm used for signing data.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Algorithm {
|
||||
/// The Ed25519 digital signature algorithm.
|
||||
Ed25519,
|
||||
}
|
||||
|
||||
impl Display for Algorithm {
|
||||
|
365
src/signatures.rs
Normal file
365
src/signatures.rs
Normal file
@ -0,0 +1,365 @@
|
||||
//! Digital signatures and collections of signatures.
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
error::Error as _,
|
||||
fmt::{Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
use base64::{decode_config, encode_config, STANDARD_NO_PAD};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, MapAccess, Unexpected, Visitor},
|
||||
ser::SerializeMap,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use url::{Host, Url};
|
||||
|
||||
use crate::{Algorithm, Error};
|
||||
|
||||
/// A digital signature.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Signature {
|
||||
/// The cryptographic algorithm to use.
|
||||
pub(crate) algorithm: Algorithm,
|
||||
|
||||
/// The signature data.
|
||||
pub(crate) signature: Vec<u8>,
|
||||
|
||||
/// The version of the signature.
|
||||
pub(crate) version: String,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Creates a signature from raw bytes.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * id: A key identifier, e.g. "ed25519:1".
|
||||
/// * bytes: The digital signature, as a series of bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the key identifier is invalid.
|
||||
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(Self {
|
||||
algorithm,
|
||||
signature: bytes.to_vec(),
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
/// The algorithm used to generate the signature.
|
||||
pub fn algorithm(&self) -> Algorithm {
|
||||
self.algorithm
|
||||
}
|
||||
|
||||
/// The raw bytes of the signature.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.signature.as_slice()
|
||||
}
|
||||
|
||||
/// A Base64 encoding of the signature.
|
||||
///
|
||||
/// Uses the standard character set with no padding.
|
||||
pub fn base64(&self) -> String {
|
||||
encode_config(self.signature.as_slice(), STANDARD_NO_PAD)
|
||||
}
|
||||
|
||||
/// The key identifier, a string containing the signature algorithm and the key "version"
|
||||
/// separated by a colon, e.g. "ed25519:1".
|
||||
pub fn id(&self) -> String {
|
||||
format!("{}:{}", self.algorithm, self.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 version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
|
||||
/// A map of server names to sets of digital signatures created by that server.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Signatures {
|
||||
/// A map of homeservers to sets of signatures for the homeserver.
|
||||
map: HashMap<Host, SignatureSet>,
|
||||
}
|
||||
|
||||
impl Signatures {
|
||||
/// Initializes a new empty Signatures.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes a new empty Signatures with room for a specific number of servers.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * capacity: The number of items to allocate memory for.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
map: HashMap::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a signature set for a server.
|
||||
///
|
||||
/// If no signature set for the given server existed in the collection, `None` is returned.
|
||||
/// Otherwise, the signature set is returned.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * server_name: The hostname or IP of the homeserver, e.g. `example.com`.
|
||||
/// * signature_set: The `SignatureSet` containing the digital signatures made by the server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the given server name cannot be parsed as a valid host.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
server_name: &str,
|
||||
signature_set: SignatureSet,
|
||||
) -> Result<Option<SignatureSet>, Error> {
|
||||
let url_string = format!("https://{}", server_name);
|
||||
let url = Url::parse(&url_string)
|
||||
.map_err(|_| Error::new(format!("invalid server name: {}", server_name)))?;
|
||||
|
||||
let host = match url.host() {
|
||||
Some(host) => host.to_owned(),
|
||||
None => return Err(Error::new(format!("invalid server name: {}", server_name))),
|
||||
};
|
||||
|
||||
Ok(self.map.insert(host, signature_set))
|
||||
}
|
||||
|
||||
/// The number of servers in the collection.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Whether or not the collection of signatures is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Signatures {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map_serializer = serializer.serialize_map(Some(self.len()))?;
|
||||
|
||||
for (host, signature_set) in self.map.iter() {
|
||||
map_serializer.serialize_key(&host.to_string())?;
|
||||
map_serializer.serialize_value(signature_set)?;
|
||||
}
|
||||
|
||||
map_serializer.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Signatures {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(SignaturesVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serde Visitor for deserializing `Signatures`.
|
||||
struct SignaturesVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SignaturesVisitor {
|
||||
type Value = Signatures;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(formatter, "digital signatures")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut visitor: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut signatures = match visitor.size_hint() {
|
||||
Some(capacity) => Signatures::with_capacity(capacity),
|
||||
None => Signatures::new(),
|
||||
};
|
||||
|
||||
while let Some((server_name, signature_set)) =
|
||||
visitor.next_entry::<String, SignatureSet>()?
|
||||
{
|
||||
if signatures.insert(&server_name, signature_set).is_err() {
|
||||
return Err(M::Error::invalid_value(
|
||||
Unexpected::Str(&server_name),
|
||||
&self,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(signatures)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of digital signatures created by a single homeserver.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct SignatureSet {
|
||||
/// A set of signatures for a homeserver.
|
||||
set: HashSet<Signature>,
|
||||
}
|
||||
|
||||
impl SignatureSet {
|
||||
/// Initializes a new empty SignatureSet.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
set: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes a new empty SignatureSet with room for a specific number of signatures.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * capacity: The number of items to allocate memory for.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
set: HashSet::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a signature to the set.
|
||||
///
|
||||
/// The boolean return value indicates whether or not the value was actually inserted, since
|
||||
/// subsequent inserts of the same signature have no effect.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * signature: A `Signature` to insert into the set.
|
||||
pub fn insert(&mut self, signature: Signature) -> bool {
|
||||
self.set.insert(signature)
|
||||
}
|
||||
|
||||
/// The number of signatures in the set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
}
|
||||
|
||||
/// Whether or not the set of signatures is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SignatureSet {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map_serializer = serializer.serialize_map(Some(self.len()))?;
|
||||
|
||||
for signature in self.set.iter() {
|
||||
map_serializer.serialize_key(&signature.id())?;
|
||||
map_serializer.serialize_value(&signature.base64())?;
|
||||
}
|
||||
|
||||
map_serializer.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SignatureSet {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(SignatureSetVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serde Visitor for deserializing `SignatureSet`.
|
||||
struct SignatureSetVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SignatureSetVisitor {
|
||||
type Value = SignatureSet;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(formatter, "a set of digital signatures")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut visitor: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut signature_set = match visitor.size_hint() {
|
||||
Some(capacity) => SignatureSet::with_capacity(capacity),
|
||||
None => SignatureSet::new(),
|
||||
};
|
||||
|
||||
while let Some((key, value)) = visitor.next_entry::<String, String>()? {
|
||||
let (algorithm, version) = split_id(&key).map_err(|split_error| match split_error {
|
||||
SplitError::InvalidLength(length) => M::Error::invalid_length(length, &self),
|
||||
SplitError::UnknownAlgorithm(algorithm) => {
|
||||
M::Error::invalid_value(Unexpected::Str(algorithm), &self)
|
||||
}
|
||||
})?;
|
||||
|
||||
let signature_bytes: Vec<u8> = match decode_config(&value, STANDARD_NO_PAD) {
|
||||
Ok(raw) => raw,
|
||||
Err(error) => return Err(M::Error::custom(error.description())),
|
||||
};
|
||||
|
||||
let signature = Signature {
|
||||
algorithm,
|
||||
signature: signature_bytes,
|
||||
version,
|
||||
};
|
||||
|
||||
signature_set.insert(signature);
|
||||
}
|
||||
|
||||
Ok(signature_set)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 has an invalid length.
|
||||
InvalidLength(usize),
|
||||
/// 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 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()))
|
||||
}
|
48
src/verification.rs
Normal file
48
src/verification.rs
Normal file
@ -0,0 +1,48 @@
|
||||
//! Verification of digital signatures.
|
||||
|
||||
use ring::signature::{verify, ED25519};
|
||||
use untrusted::Input;
|
||||
|
||||
use crate::{signatures::Signature, Error};
|
||||
|
||||
/// A digital signature verifier.
|
||||
pub trait Verifier {
|
||||
/// Use a public key to verify a signature against the JSON object that was signed.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * public_key: The public key of the key pair used to sign the message.
|
||||
/// * signature: The `Signature` to verify.
|
||||
/// * message: The message that was signed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if verification fails.
|
||||
fn verify_json(
|
||||
&self,
|
||||
public_key: &[u8],
|
||||
signature: &Signature,
|
||||
message: &[u8],
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// A verifier for Ed25519 digital signatures.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Ed25519Verifier;
|
||||
|
||||
impl Verifier for Ed25519Verifier {
|
||||
fn verify_json(
|
||||
&self,
|
||||
public_key: &[u8],
|
||||
signature: &Signature,
|
||||
message: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
verify(
|
||||
&ED25519,
|
||||
Input::from(public_key),
|
||||
Input::from(message),
|
||||
Input::from(signature.as_bytes()),
|
||||
)
|
||||
.map_err(|_| Error::new("signature verification failed"))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user