use crate::NonceError;
pub trait SignatureAlgorithm: Send + Sync {
fn name(&self) -> &'static str;
fn sign(&self, timestamp: u64, nonce: &str, data: &[u8]) -> Result<String, NonceError>;
fn verify(
&self,
timestamp: u64,
nonce: &str,
data: &[u8],
signature: &str,
) -> Result<(), NonceError>;
fn sign_with<F>(&self, timestamp: u64, nonce: &str, builder: F) -> Result<String, NonceError>
where
F: FnOnce(&mut dyn MacLike),
{
let _ = (timestamp, nonce, builder);
Err(NonceError::CryptoError(
"Custom signing not supported by this algorithm".to_string(),
))
}
fn verify_with<F>(
&self,
timestamp: u64,
nonce: &str,
signature: &str,
builder: F,
) -> Result<(), NonceError>
where
F: FnOnce(&mut dyn MacLike),
{
let _ = (timestamp, nonce, signature, builder);
Err(NonceError::CryptoError(
"Custom verification not supported by this algorithm".to_string(),
))
}
}
pub trait MacLike {
fn update(&mut self, data: &[u8]);
}
#[cfg(feature = "algo-hmac-sha256")]
pub mod hmac_sha256 {
use super::{MacLike, SignatureAlgorithm};
use crate::NonceError;
use base64::Engine;
use hmac::{Hmac, Mac};
use sha2::Sha256;
pub struct HmacSha256Algorithm {
key: Vec<u8>,
}
impl HmacSha256Algorithm {
pub fn new(key: &[u8]) -> Self {
Self { key: key.to_vec() }
}
fn create_hmac(&self) -> Result<Hmac<Sha256>, NonceError> {
Hmac::<Sha256>::new_from_slice(&self.key)
.map_err(|e| NonceError::CryptoError(format!("Invalid HMAC key: {e}")))
}
}
impl SignatureAlgorithm for HmacSha256Algorithm {
fn name(&self) -> &'static str {
"hmac-sha256"
}
fn sign(&self, timestamp: u64, nonce: &str, data: &[u8]) -> Result<String, NonceError> {
let mut mac = self.create_hmac()?;
mac.update(timestamp.to_string().as_bytes());
mac.update(nonce.as_bytes());
mac.update(data);
let signature = mac.finalize().into_bytes();
Ok(base64::engine::general_purpose::STANDARD.encode(signature))
}
fn verify(
&self,
timestamp: u64,
nonce: &str,
data: &[u8],
signature: &str,
) -> Result<(), NonceError> {
let expected_signature = base64::engine::general_purpose::STANDARD
.decode(signature)
.map_err(|e| NonceError::CryptoError(format!("Invalid base64 signature: {e}")))?;
let mut mac = self.create_hmac()?;
mac.update(timestamp.to_string().as_bytes());
mac.update(nonce.as_bytes());
mac.update(data);
mac.verify_slice(&expected_signature)
.map_err(|_| NonceError::InvalidSignature)
}
fn sign_with<F>(
&self,
timestamp: u64,
nonce: &str,
builder: F,
) -> Result<String, NonceError>
where
F: FnOnce(&mut dyn MacLike),
{
let mut mac = self.create_hmac()?;
let mut mac_wrapper = HmacWrapper(&mut mac);
mac_wrapper.update(timestamp.to_string().as_bytes());
mac_wrapper.update(nonce.as_bytes());
builder(&mut mac_wrapper);
let signature = mac.finalize().into_bytes();
Ok(base64::engine::general_purpose::STANDARD.encode(signature))
}
fn verify_with<F>(
&self,
timestamp: u64,
nonce: &str,
signature: &str,
builder: F,
) -> Result<(), NonceError>
where
F: FnOnce(&mut dyn MacLike),
{
let expected_signature = base64::engine::general_purpose::STANDARD
.decode(signature)
.map_err(|e| NonceError::CryptoError(format!("Invalid base64 signature: {e}")))?;
let mut mac = self.create_hmac()?;
let mut mac_wrapper = HmacWrapper(&mut mac);
mac_wrapper.update(timestamp.to_string().as_bytes());
mac_wrapper.update(nonce.as_bytes());
builder(&mut mac_wrapper);
mac.verify_slice(&expected_signature)
.map_err(|_| NonceError::InvalidSignature)
}
}
struct HmacWrapper<'a>(&'a mut Hmac<Sha256>);
impl<'a> MacLike for HmacWrapper<'a> {
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hmac_sha256_basic_sign_verify() {
let algorithm = HmacSha256Algorithm::new(b"test_key");
let timestamp = 1234567890;
let nonce = "test_nonce";
let data = b"test_payload";
let signature = algorithm.sign(timestamp, nonce, data).unwrap();
assert!(!signature.is_empty());
algorithm
.verify(timestamp, nonce, data, &signature)
.unwrap();
}
#[test]
fn test_hmac_sha256_invalid_signature() {
let algorithm = HmacSha256Algorithm::new(b"test_key");
let timestamp = 1234567890;
let nonce = "test_nonce";
let data = b"test_payload";
let result = algorithm.verify(timestamp, nonce, data, "invalid_signature");
assert!(matches!(result, Err(NonceError::CryptoError(_))));
let signature = algorithm.sign(timestamp, nonce, data).unwrap();
let result = algorithm.verify(timestamp, nonce, b"wrong_data", &signature);
assert!(matches!(result, Err(NonceError::InvalidSignature)));
}
#[test]
fn test_hmac_sha256_sign_with() {
let algorithm = HmacSha256Algorithm::new(b"test_key");
let timestamp = 1234567890;
let nonce = "test_nonce";
let signature = algorithm
.sign_with(timestamp, nonce, |mac| {
mac.update(b"custom_data");
mac.update(b"more_data");
})
.unwrap();
algorithm
.verify_with(timestamp, nonce, &signature, |mac| {
mac.update(b"custom_data");
mac.update(b"more_data");
})
.unwrap();
let result = algorithm.verify_with(timestamp, nonce, &signature, |mac| {
mac.update(b"different_data");
});
assert!(matches!(result, Err(NonceError::InvalidSignature)));
}
#[test]
fn test_hmac_sha256_different_keys_different_signatures() {
let algorithm1 = HmacSha256Algorithm::new(b"key1");
let algorithm2 = HmacSha256Algorithm::new(b"key2");
let timestamp = 1234567890;
let nonce = "test_nonce";
let data = b"test_payload";
let signature1 = algorithm1.sign(timestamp, nonce, data).unwrap();
let signature2 = algorithm2.sign(timestamp, nonce, data).unwrap();
assert_ne!(signature1, signature2);
let result = algorithm1.verify(timestamp, nonce, data, &signature2);
assert!(matches!(result, Err(NonceError::InvalidSignature)));
}
}
}
#[cfg(feature = "algo-hmac-sha256")]
pub type DefaultSignatureAlgorithm = hmac_sha256::HmacSha256Algorithm;
#[cfg(feature = "algo-hmac-sha256")]
pub fn create_default_algorithm(key: &[u8]) -> DefaultSignatureAlgorithm {
hmac_sha256::HmacSha256Algorithm::new(key)
}
#[cfg(not(feature = "algo-hmac-sha256"))]
pub fn create_default_algorithm(_key: &[u8]) -> ! {
compile_error!("No signature algorithm available. Enable at least one algorithm feature.");
}