Skip to main content

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).expect("signing failed");
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, or `Err(SignatureError::InvalidKeyLength)`
87    /// if the key is invalid.
88    pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, SignatureError> {
89        let mut mac =
90            HmacSha256::new_from_slice(&self.key).map_err(|_| SignatureError::InvalidKeyLength)?;
91        mac.update(message);
92        Ok(mac.finalize().into_bytes().to_vec())
93    }
94
95    /// Verify a message signature
96    ///
97    /// # Arguments
98    ///
99    /// * `message` - The message bytes that were signed
100    /// * `signature` - The signature to verify
101    ///
102    /// # Returns
103    ///
104    /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise
105    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), SignatureError> {
106        let mut mac =
107            HmacSha256::new_from_slice(&self.key).map_err(|_| SignatureError::InvalidKeyLength)?;
108        mac.update(message);
109
110        mac.verify_slice(signature)
111            .map_err(|_| SignatureError::VerificationFailed)
112    }
113
114    /// Sign a message and return the signature as a hex string
115    ///
116    /// # Arguments
117    ///
118    /// * `message` - The message bytes to sign
119    ///
120    /// # Returns
121    ///
122    /// The signature encoded as a lowercase hex string
123    pub fn sign_hex(&self, message: &[u8]) -> Result<String, SignatureError> {
124        let sig = self.sign(message)?;
125        Ok(hex::encode(sig))
126    }
127
128    /// Verify a hex-encoded signature
129    ///
130    /// # Arguments
131    ///
132    /// * `message` - The message bytes that were signed
133    /// * `signature_hex` - The hex-encoded signature to verify
134    ///
135    /// # Returns
136    ///
137    /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise
138    pub fn verify_hex(&self, message: &[u8], signature_hex: &str) -> Result<(), SignatureError> {
139        let signature_bytes =
140            hex::decode(signature_hex).map_err(|_| SignatureError::InvalidSignature)?;
141        self.verify(message, &signature_bytes)
142    }
143}
144
145// Placeholder implementation when signing feature is disabled
146#[cfg(not(feature = "signing"))]
147pub struct MessageSigner;
148
149#[cfg(not(feature = "signing"))]
150impl MessageSigner {
151    pub fn new(_key: &[u8]) -> Self {
152        Self
153    }
154}
155
156#[cfg(all(test, feature = "signing"))]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_sign_and_verify() {
162        let secret = b"my-secret-key";
163        let signer = MessageSigner::new(secret);
164
165        let message = b"test message";
166        let signature = signer.sign(message).expect("signing should not fail");
167
168        assert!(signer.verify(message, &signature).is_ok());
169    }
170
171    #[test]
172    fn test_verify_invalid_signature() {
173        let secret = b"my-secret-key";
174        let signer = MessageSigner::new(secret);
175
176        let message = b"test message";
177        let wrong_signature = vec![0u8; 32];
178
179        assert!(signer.verify(message, &wrong_signature).is_err());
180    }
181
182    #[test]
183    fn test_verify_wrong_message() {
184        let secret = b"my-secret-key";
185        let signer = MessageSigner::new(secret);
186
187        let message = b"test message";
188        let signature = signer.sign(message).expect("signing should not fail");
189
190        let wrong_message = b"different message";
191        assert!(signer.verify(wrong_message, &signature).is_err());
192    }
193
194    #[test]
195    fn test_sign_hex() {
196        let secret = b"my-secret-key";
197        let signer = MessageSigner::new(secret);
198
199        let message = b"test message";
200        let signature_hex = signer.sign_hex(message).expect("signing should not fail");
201
202        // Verify it's valid hex
203        assert!(hex::decode(&signature_hex).is_ok());
204
205        // Verify using hex verification
206        assert!(signer.verify_hex(message, &signature_hex).is_ok());
207    }
208
209    #[test]
210    fn test_verify_hex_invalid() {
211        let secret = b"my-secret-key";
212        let signer = MessageSigner::new(secret);
213
214        let message = b"test message";
215        let invalid_hex = "not-valid-hex!!";
216
217        assert_eq!(
218            signer.verify_hex(message, invalid_hex),
219            Err(SignatureError::InvalidSignature)
220        );
221    }
222
223    #[test]
224    fn test_signature_deterministic() {
225        let secret = b"my-secret-key";
226        let signer = MessageSigner::new(secret);
227
228        let message = b"test message";
229        let sig1 = signer.sign(message).expect("signing should not fail");
230        let sig2 = signer.sign(message).expect("signing should not fail");
231
232        assert_eq!(sig1, sig2);
233    }
234
235    #[test]
236    fn test_different_keys_different_signatures() {
237        let message = b"test message";
238
239        let signer1 = MessageSigner::new(b"key1");
240        let signer2 = MessageSigner::new(b"key2");
241
242        let sig1 = signer1.sign(message).expect("signing should not fail");
243        let sig2 = signer2.sign(message).expect("signing should not fail");
244
245        assert_ne!(sig1, sig2);
246    }
247
248    #[test]
249    fn test_signature_error_display() {
250        assert_eq!(
251            SignatureError::InvalidSignature.to_string(),
252            "Invalid signature format"
253        );
254        assert_eq!(
255            SignatureError::VerificationFailed.to_string(),
256            "Signature verification failed"
257        );
258        assert_eq!(
259            SignatureError::InvalidKeyLength.to_string(),
260            "Invalid key length"
261        );
262    }
263}