celers_protocol/
auth.rs

1//! Message authentication and signing
2//!
3//! This module provides HMAC-based message signing for Celery protocol messages.
4//! It ensures message integrity and authenticity by generating and verifying
5//! cryptographic signatures.
6//!
7//! # Example
8//!
9//! ```
10//! use celers_protocol::auth::{MessageSigner, SignatureError};
11//!
12//! # #[cfg(feature = "signing")]
13//! # {
14//! let secret = b"my-secret-key";
15//! let signer = MessageSigner::new(secret);
16//!
17//! let message = b"task message body";
18//! let signature = signer.sign(message);
19//!
20//! // Verify signature
21//! assert!(signer.verify(message, &signature).is_ok());
22//! # }
23//! ```
24
25#[cfg(feature = "signing")]
26use hmac::{Hmac, Mac};
27#[cfg(feature = "signing")]
28use sha2::Sha256;
29
30use std::fmt;
31
32/// Error type for signature operations
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum SignatureError {
35    /// Invalid signature
36    InvalidSignature,
37    /// Signature verification failed
38    VerificationFailed,
39    /// Invalid key length
40    InvalidKeyLength,
41}
42
43impl fmt::Display for SignatureError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            SignatureError::InvalidSignature => write!(f, "Invalid signature format"),
47            SignatureError::VerificationFailed => write!(f, "Signature verification failed"),
48            SignatureError::InvalidKeyLength => write!(f, "Invalid key length"),
49        }
50    }
51}
52
53impl std::error::Error for SignatureError {}
54
55#[cfg(feature = "signing")]
56type HmacSha256 = Hmac<Sha256>;
57
58/// Message signer using HMAC-SHA256
59///
60/// Provides cryptographic signing and verification of messages using HMAC-SHA256.
61/// This is compatible with Python Celery's message signing when using SHA256.
62#[cfg(feature = "signing")]
63pub struct MessageSigner {
64    key: Vec<u8>,
65}
66
67#[cfg(feature = "signing")]
68impl MessageSigner {
69    /// Create a new message signer with the given secret key
70    ///
71    /// # Arguments
72    ///
73    /// * `key` - Secret key for HMAC signing (recommended: at least 32 bytes)
74    pub fn new(key: &[u8]) -> Self {
75        Self { key: key.to_vec() }
76    }
77
78    /// Sign a message and return the signature
79    ///
80    /// # Arguments
81    ///
82    /// * `message` - The message bytes to sign
83    ///
84    /// # Returns
85    ///
86    /// The HMAC-SHA256 signature as a byte vector
87    pub fn sign(&self, message: &[u8]) -> Vec<u8> {
88        let mut mac = HmacSha256::new_from_slice(&self.key).expect("HMAC can take key of any size");
89        mac.update(message);
90        mac.finalize().into_bytes().to_vec()
91    }
92
93    /// Verify a message signature
94    ///
95    /// # Arguments
96    ///
97    /// * `message` - The message bytes that were signed
98    /// * `signature` - The signature to verify
99    ///
100    /// # Returns
101    ///
102    /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise
103    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), SignatureError> {
104        let mut mac = HmacSha256::new_from_slice(&self.key).expect("HMAC can take key of any size");
105        mac.update(message);
106
107        mac.verify_slice(signature)
108            .map_err(|_| SignatureError::VerificationFailed)
109    }
110
111    /// Sign a message and return the signature as a hex string
112    ///
113    /// # Arguments
114    ///
115    /// * `message` - The message bytes to sign
116    ///
117    /// # Returns
118    ///
119    /// The signature encoded as a lowercase hex string
120    pub fn sign_hex(&self, message: &[u8]) -> String {
121        let sig = self.sign(message);
122        hex::encode(sig)
123    }
124
125    /// Verify a hex-encoded signature
126    ///
127    /// # Arguments
128    ///
129    /// * `message` - The message bytes that were signed
130    /// * `signature_hex` - The hex-encoded signature to verify
131    ///
132    /// # Returns
133    ///
134    /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise
135    pub fn verify_hex(&self, message: &[u8], signature_hex: &str) -> Result<(), SignatureError> {
136        let signature = hex::decode(signature_hex).map_err(|_| SignatureError::InvalidSignature)?;
137        self.verify(message, &signature)
138    }
139}
140
141// Placeholder implementation when signing feature is disabled
142#[cfg(not(feature = "signing"))]
143pub struct MessageSigner;
144
145#[cfg(not(feature = "signing"))]
146impl MessageSigner {
147    pub fn new(_key: &[u8]) -> Self {
148        Self
149    }
150}
151
152#[cfg(all(test, feature = "signing"))]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_sign_and_verify() {
158        let secret = b"my-secret-key";
159        let signer = MessageSigner::new(secret);
160
161        let message = b"test message";
162        let signature = signer.sign(message);
163
164        assert!(signer.verify(message, &signature).is_ok());
165    }
166
167    #[test]
168    fn test_verify_invalid_signature() {
169        let secret = b"my-secret-key";
170        let signer = MessageSigner::new(secret);
171
172        let message = b"test message";
173        let wrong_signature = vec![0u8; 32];
174
175        assert!(signer.verify(message, &wrong_signature).is_err());
176    }
177
178    #[test]
179    fn test_verify_wrong_message() {
180        let secret = b"my-secret-key";
181        let signer = MessageSigner::new(secret);
182
183        let message = b"test message";
184        let signature = signer.sign(message);
185
186        let wrong_message = b"different message";
187        assert!(signer.verify(wrong_message, &signature).is_err());
188    }
189
190    #[test]
191    fn test_sign_hex() {
192        let secret = b"my-secret-key";
193        let signer = MessageSigner::new(secret);
194
195        let message = b"test message";
196        let signature_hex = signer.sign_hex(message);
197
198        // Verify it's valid hex
199        assert!(hex::decode(&signature_hex).is_ok());
200
201        // Verify using hex verification
202        assert!(signer.verify_hex(message, &signature_hex).is_ok());
203    }
204
205    #[test]
206    fn test_verify_hex_invalid() {
207        let secret = b"my-secret-key";
208        let signer = MessageSigner::new(secret);
209
210        let message = b"test message";
211        let invalid_hex = "not-valid-hex!!";
212
213        assert_eq!(
214            signer.verify_hex(message, invalid_hex),
215            Err(SignatureError::InvalidSignature)
216        );
217    }
218
219    #[test]
220    fn test_signature_deterministic() {
221        let secret = b"my-secret-key";
222        let signer = MessageSigner::new(secret);
223
224        let message = b"test message";
225        let sig1 = signer.sign(message);
226        let sig2 = signer.sign(message);
227
228        assert_eq!(sig1, sig2);
229    }
230
231    #[test]
232    fn test_different_keys_different_signatures() {
233        let message = b"test message";
234
235        let signer1 = MessageSigner::new(b"key1");
236        let signer2 = MessageSigner::new(b"key2");
237
238        let sig1 = signer1.sign(message);
239        let sig2 = signer2.sign(message);
240
241        assert_ne!(sig1, sig2);
242    }
243
244    #[test]
245    fn test_signature_error_display() {
246        assert_eq!(
247            SignatureError::InvalidSignature.to_string(),
248            "Invalid signature format"
249        );
250        assert_eq!(
251            SignatureError::VerificationFailed.to_string(),
252            "Signature verification failed"
253        );
254        assert_eq!(
255            SignatureError::InvalidKeyLength.to_string(),
256            "Invalid key length"
257        );
258    }
259}