diff --git a/Cargo.toml b/Cargo.toml index d7c0c29..27c3639 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["security", "authentication"] categories = ["authentication"] [dependencies] -anyhow = "1" +thiserror = "1" base64 = "0.22" hex = "0.4" lazy-regex = "3" @@ -20,6 +20,7 @@ rsa = "0.9.0" regex = { version = "1", default_features = false, features = ["std"] } sha2 = { version = "0.10", features = ["oid"] } urlencoding = "2" +spki = "0.7" [dev-dependencies] serde = { version = "1", features = ["derive"] } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b9f57b1 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,27 @@ +use thiserror::Error; + +/// All of the possible errors that can happen while performing mauth operations +#[derive(Debug, Error)] +pub enum Error { + /// A UTF8 decode error while attempting to process the URL + #[error("Unable to handle the URL as the format was invalid: {0}")] + UrlEncodingError(#[from] std::string::FromUtf8Error), + /// A MAuth version that is not supported was requested + #[error("Version {0} is not supported")] + UnsupportedVersion(u8), + /// The provided private key could not be parsed + #[error("Unable to parse RSA private key: {0}")] + PrivateKeyDecodeError(#[from] rsa::pkcs1::Error), + /// The provided public key could not be parsed + #[error("Unable to parse RSA public key: {0}")] + PublicKeyDecodeError(#[from] spki::Error), + /// An algorithm failure occurred while trying to sign a request + #[error("RSA algorithm error: {0}")] + RsaSignError(#[from] rsa::Error), + /// An algorithm failure occurred while trying to verify a request + #[error("Unable to verify RSA signature: {0}")] + SignatureVerifyError(#[from] rsa::signature::Error), + /// A base64 error was encountered while attempting to verify a v1 signature + #[error("Unable to decode base64-encoded signature: {0}")] + SignatureDecodeError(#[from] base64::DecodeError), +} diff --git a/src/lib.rs b/src/lib.rs index 1179546..b4b2fc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ -pub mod signable; +/// Error types +pub mod error; +pub(crate) mod signable; +/// Signing for outgoing requests pub mod signer; +/// Signature verification for incoming requests pub mod verifier; diff --git a/src/signable.rs b/src/signable.rs index ebc6e7c..e9f7ca8 100644 --- a/src/signable.rs +++ b/src/signable.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use crate::error::Error; use lazy_regex::*; use regex::{Captures, Regex}; use sha2::{Digest, Sha512}; @@ -38,7 +38,7 @@ impl<'a> Signable<'a> { } } - pub fn signing_string_v1(&self) -> Result> { + pub fn signing_string_v1(&self) -> Result, Error> { let mut hasher = Sha512::default(); hasher.update(&self.verb); @@ -54,7 +54,7 @@ impl<'a> Signable<'a> { Ok(hex::encode(hasher.finalize()).into_bytes()) } - pub fn signing_string_v2(&self) -> Result> { + pub fn signing_string_v2(&self) -> Result, Error> { let encoded_query: String = Self::encode_query(&self.query)?; let body_digest = hex::encode(Sha512::digest(self.body)); @@ -70,14 +70,14 @@ impl<'a> Signable<'a> { .into_bytes()) } - fn encode_query(qstr: &str) -> Result { + fn encode_query(qstr: &str) -> Result { if qstr.is_empty() { return Ok("".to_string()); } let mut temp_param_list = qstr .split('&') .map(Self::split_equal_and_decode) - .collect::>>()?; + .collect::, Error>>()?; temp_param_list.sort(); @@ -109,7 +109,7 @@ impl<'a> Signable<'a> { } } - fn split_equal_and_decode(value: &str) -> Result<[String; 2]> { + fn split_equal_and_decode(value: &str) -> Result<[String; 2], Error> { let (k, v) = value.split_once('=').unwrap_or((value, "")); Ok([ Self::replace_plus_and_decode(k)?, @@ -117,7 +117,7 @@ impl<'a> Signable<'a> { ]) } - fn replace_plus_and_decode(value: &str) -> Result { + fn replace_plus_and_decode(value: &str) -> Result { Ok(decode(&value.replace('+', " "))?.into_owned()) } } diff --git a/src/signer.rs b/src/signer.rs index 2c0c5cb..9473d12 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -1,5 +1,4 @@ -use crate::signable::Signable; -use anyhow::{bail, Result}; +use crate::{error::Error, signable::Signable}; use base64::{engine::general_purpose, Engine as _}; use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::RsaPrivateKey; @@ -13,7 +12,7 @@ pub struct Signer { } impl Signer { - pub fn new(app_uuid: impl Into, private_key_data: String) -> Result { + pub fn new(app_uuid: impl Into, private_key_data: String) -> Result { let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key_data)?; let signing_key = rsa::pkcs1v15::SigningKey::::new(private_key.to_owned()); @@ -32,17 +31,17 @@ impl Signer { query: impl Into, body: &[u8], timestamp: impl Into, - ) -> Result { + ) -> Result { let signable = Signable::new(verb, path, query, body, timestamp, &self.app_uuid); match version { 1 => self.sign_string_v1(&signable), 2 => self.sign_string_v2(&signable), - _ => bail!("Version {version} is not supported."), + v => Err(Error::UnsupportedVersion(v)), } } - fn sign_string_v1(&self, signable: &Signable) -> Result { + fn sign_string_v1(&self, signable: &Signable) -> Result { let signature = self.private_key.sign( rsa::Pkcs1v15Sign::new_unprefixed(), &signable.signing_string_v1()?, @@ -50,7 +49,7 @@ impl Signer { Ok(general_purpose::STANDARD.encode(signature)) } - fn sign_string_v2(&self, signable: &Signable) -> Result { + fn sign_string_v2(&self, signable: &Signable) -> Result { use rsa::signature::{SignatureEncoding, Signer}; let sign = self.signing_key.sign(&signable.signing_string_v2()?); diff --git a/src/verifier.rs b/src/verifier.rs index 41bc1fe..cc87f41 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -1,5 +1,4 @@ -use crate::signable::Signable; -use anyhow::{bail, Result}; +use crate::{error::Error, signable::Signable}; use base64::{engine::general_purpose, Engine as _}; use rsa::pkcs1v15::Signature; use rsa::pkcs8::DecodePublicKey; @@ -14,7 +13,7 @@ pub struct Verifier { } impl Verifier { - pub fn new(app_uuid: impl Into, public_key_data: String) -> Result { + pub fn new(app_uuid: impl Into, public_key_data: String) -> Result { let public_key = RsaPublicKey::from_public_key_pem(&public_key_data)?; let verifying_key = rsa::pkcs1v15::VerifyingKey::::new(public_key.to_owned()); @@ -34,27 +33,35 @@ impl Verifier { body: &[u8], timestamp: impl Into, signature: impl Into, - ) -> Result { + ) -> Result<(), Error> { let signable = Signable::new(verb, path, query, body, timestamp, &self.app_uuid); match version { 1 => self.verify_signature_v1(&signable, signature.into()), 2 => self.verify_signature_v2(&signable, signature.into()), - _ => bail!("Version {version} is not supported."), + v => Err(Error::UnsupportedVersion(v)), } } - fn verify_signature_v1(&self, signable: &Signable, signature: String) -> Result { + fn verify_signature_v1( + &self, + signable: &Signable, + signature: String, + ) -> Result<(), Error> { self.public_key.verify( rsa::Pkcs1v15Sign::new_unprefixed(), &signable.signing_string_v1()?, &general_purpose::STANDARD.decode(signature)?, )?; - Ok(true) + Ok(()) } - fn verify_signature_v2(&self, signable: &Signable, signature: String) -> Result { + fn verify_signature_v2( + &self, + signable: &Signable, + signature: String, + ) -> Result<(), Error> { use rsa::signature::Verifier; let signature = @@ -62,6 +69,6 @@ impl Verifier { self.verifying_key .verify(&signable.signing_string_v2()?, &signature)?; - Ok(true) + Ok(()) } } diff --git a/tests/protocol_test_suite.rs b/tests/protocol_test_suite.rs index 4640b50..a6d392c 100644 --- a/tests/protocol_test_suite.rs +++ b/tests/protocol_test_suite.rs @@ -88,15 +88,9 @@ fn test_verifier( _ => vec![], }; - let result = verifier + verifier .verify_signature(version, req.verb, path, query, &body_data, timestamp, sig) - .unwrap(); - - if result { - Ok(()) - } else { - Err(format!("[{test_case}] failed")) - } + .map_err(|_| format!("[{test_case}] failed")) } #[test]