hmac_serialiser_rs/
lib.rs

1//! # HMAC Signer
2//!
3//! `hmac_serialiser_rs` is a Rust library for generating and verifying HMAC signatures for secure data transmission.
4//! It uses the `ring` crate for HMAC operations and `serde` for serialising and deserialising data.
5//! Moreover, it uses the `base64` crate for encoding and decoding data.
6//!
7//! ## License
8//!
9//! This library is licensed under the MIT license.
10//!
11//! ## Features
12//!
13//! - Supports various encoding schemes for signatures.
14//! - Flexible HMAC signer logic for custom data types.
15//! - Provides a convenient interface for signing and verifying data.
16//!
17//! ## Example
18//!
19//! ```rust
20//! use hmac_serialiser_rs::{Encoder, HmacSigner, KeyInfo, SignerLogic, Algorithm};
21//! use serde::{Serialize, Deserialize};
22//! use std::error::Error;
23//!
24//! #[derive(Serialize, Deserialize, Debug)]
25//! struct UserData {
26//!     // Add your data fields here
27//!     username: String,
28//!     email: String,
29//! }
30//!
31//! impl hmac_serialiser_rs::Data for UserData {
32//!     fn get_exp(&self) -> Option<chrono::DateTime<chrono::Utc>> {
33//!         // Add logic to retrieve expiration time if needed
34//!         None
35//!     }
36//! }
37//!
38//! fn main() -> Result<(), Box<dyn Error>> {
39//!     // Define your secret key, salt, and optional info
40//!     let key_info = KeyInfo {
41//!         key: b"your_secret_key".to_vec(),
42//!         salt: b"your_salt".to_vec(),
43//!         info: vec![], // empty info
44//!     };
45//!
46//!     // Initialize the HMAC signer
47//!     let signer = HmacSigner::new(key_info, Algorithm::SHA256, Encoder::UrlSafe);
48//!
49//!     // Serialize your data
50//!     let user_data = UserData {
51//!         username: "user123".to_string(),
52//!         email: "user123@example.com".to_string(),
53//!     };
54//!     let token = signer.sign(&user_data);
55//!     println!("Token: {}", token);
56//!
57//!     // Verify the token
58//!     let verified_data: UserData = signer.unsign(&token)?;
59//!     println!("Verified data: {:?}", verified_data);
60//!
61//!     Ok(())
62//! }
63//! ```
64//!
65//! ## Supported Encoders
66//!
67//! - `Standard`: Standard base64 encoding.
68//! - `UrlSafe`: URL-safe base64 encoding.
69//! - `StandardNoPadding`: Standard base64 encoding without padding.
70//! - `UrlSafeNoPadding`: URL-safe base64 encoding without padding.
71//!
72//! ## Supported HMAC Algorithms
73//!
74//! - `SHA1`
75//! - `SHA256`
76//! - `SHA384`
77//! - `SHA512`
78//!
79//! ## Traits
80//!
81//! - `Data`: A trait for data structures that can be signed and verified.
82//! - `SignerLogic`: A trait for defining signer logic.
83//!
84//! ## Errors
85//!
86//! Errors are represented by the `Error` enum, which includes:
87//!
88//! - `InvalidInput`: Invalid input data.
89//! - `InvalidSignature`: Invalid signature provided.
90//! - `InvalidToken`: Invalid token provided.
91//! - `HkdfExpandError`: Error during key expansion.
92//! - `HkdfFillError`: Error during key filling.
93//! - `TokenExpired`: Token has expired.
94//!
95//! ## Contributing
96//!
97//! Contributions are welcome! Feel free to open issues and pull requests on [GitHub](https://github.com/KJHJason/hmac-serialiser-rs).
98//!
99//! ```
100
101pub mod algorithm;
102pub mod errors;
103mod hkdf;
104
105use algorithm::{Algorithm, HkdfAlgorithm};
106use base64::{engine::general_purpose, Engine as _};
107use errors::Error;
108use ring::hmac;
109use serde::{Deserialize, Serialize};
110
111const DELIM: char = '.';
112
113/// An enum for defining the encoding scheme for the payload and the signature.
114#[derive(Debug, Clone)]
115pub enum Encoder {
116    // Standard base64 encoding
117    Standard,
118    // URL-safe base64 encoding
119    UrlSafe,
120    // Standard base64 encoding without padding
121    StandardNoPadding,
122    // URL-safe base64 encoding without padding
123    UrlSafeNoPadding,
124}
125
126impl Encoder {
127    #[inline]
128    fn get_encoder(&self) -> general_purpose::GeneralPurpose {
129        match self {
130            Encoder::Standard => general_purpose::STANDARD,
131            Encoder::UrlSafe => general_purpose::URL_SAFE,
132            Encoder::StandardNoPadding => general_purpose::STANDARD_NO_PAD,
133            Encoder::UrlSafeNoPadding => general_purpose::URL_SAFE_NO_PAD,
134        }
135    }
136}
137
138/// A trait for custom data types that can be signed and verified.
139///
140/// This trait defines methods for retrieving expiration time and is used in conjunction with
141/// signing and verifying operations.
142///
143/// If your data type does not require an expiration time, you can implement the trait as follows:
144/// ```rust
145/// use hmac_serialiser_rs::Data;
146/// use chrono::{DateTime, Utc};
147///
148/// struct CustomData {
149///    data: String,
150/// }
151///
152/// impl Data for CustomData {
153///    fn get_exp(&self) -> Option<DateTime<Utc>> {
154///       None
155///   }
156/// }
157///```
158pub trait Data {
159    fn get_exp(&self) -> Option<chrono::DateTime<chrono::Utc>>;
160}
161
162/// A struct that holds the key information required for key expansion.
163///
164/// The key expansion process is used to derive a new key from the main secret key. Its main purpose is to expand
165/// the key to the HMAC algorithm's block size to avoid padding which can reduce the effort required for a brute force attack.
166///
167/// The `KeyInfo` struct contains the main secret key, salt for key expansion, and optional application-specific info.
168/// - `key` field is the main secret key used for signing and verifying data.
169/// - `salt` field is used for key expansion.
170/// - `info` field is optional and can be used to provide application-specific information.
171///
172/// The `salt` and the `info` fields can help to prevent key reuse and provide additional security.
173#[derive(Debug, Clone)]
174pub struct KeyInfo {
175    // Main secret key
176    pub key: Vec<u8>,
177
178    // Salt for the key expansion (Optional)
179    pub salt: Vec<u8>,
180
181    // Application specific info (Optional)
182    pub info: Vec<u8>,
183}
184
185/// A struct that holds the HMAC signer logic.
186///
187/// The `HmacSigner` struct is used for signing and verifying data using HMAC signatures.
188#[derive(Debug, Clone)]
189pub struct HmacSigner {
190    key: hmac::Key,
191    encoder: general_purpose::GeneralPurpose,
192}
193
194impl HmacSigner {
195    pub fn new(key_info: KeyInfo, algo: Algorithm, encoder: Encoder) -> Self {
196        if key_info.key.is_empty() {
197            panic!("Key cannot be empty"); // panic if key is empty as it is usually due to developer error
198        }
199
200        let hkdf_algo = HkdfAlgorithm::from_hmac(&algo);
201        let hkdf = hkdf::HkdfWrapper::new(&key_info.salt, hkdf_algo);
202        let expanded_key = hkdf
203            .expand(&key_info.key, &key_info.info)
204            .expect("Failed to expand key");
205
206        Self {
207            key: hmac::Key::new(algo.to_hmac(), &expanded_key),
208            encoder: encoder.get_encoder(),
209        }
210    }
211    #[inline]
212    fn sign_data(&self, data: &[u8]) -> Vec<u8> {
213        hmac::sign(&self.key, data).as_ref().to_vec()
214    }
215    #[inline]
216    fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
217        hmac::verify(&self.key, data, signature).is_ok()
218    }
219}
220
221/// A trait for defining the signer logic.
222pub trait SignerLogic {
223    fn unsign<T: for<'de> Deserialize<'de> + Data>(&self, token: &str) -> Result<T, Error>;
224    fn sign<T: Serialize + Data>(&self, data: &T) -> String;
225}
226
227impl SignerLogic for HmacSigner {
228    /// Verifies the token and returns the deserialised data.
229    ///
230    /// Before verifying the payload, the input token is split into two parts: the encoded payload and the signature.
231    /// If the token does not contain two parts, an `InvalidInput` error is returned.
232    ///
233    /// Afterwards, if the encoded payload is empty, an `InvalidToken` error is returned even if the signature is valid.
234    ///
235    /// The signature is then decoded using the provided encoder. If the decoding fails, an `InvalidSignature` error is returned.
236    ///
237    /// The encoded payload and the signature are then verified via HMAC. If the verification fails, an `InvalidToken` error is returned.
238    ///
239    /// If the encoded payload is valid, the payload is decoded and deserialised using serde.
240    /// If the payload's expiration time is not provided, the deserialized data is returned.
241    /// Otherwise, the expiration time is checked against the current time. If the expiration time is earlier than the current time, a `TokenExpired` error is returned.
242    ///
243    /// Sample Usage:
244    /// ```rust
245    /// use hmac_serialiser_rs::{HmacSigner, KeyInfo, Encoder, algorithm::Algorithm, errors::Error, SignerLogic, Data};
246    /// use serde::{Serialize, Deserialize};
247    ///
248    /// #[derive(Serialize, Deserialize, Debug)]
249    /// struct UserData {
250    ///     username: String,
251    /// }
252    /// impl Data for UserData {
253    ///    fn get_exp(&self) -> Option<chrono::DateTime<chrono::Utc>> {
254    ///         None
255    ///     }
256    /// }
257    ///
258    /// let key_info = KeyInfo {
259    ///    key: b"your_secret_key".to_vec(),
260    ///    salt: b"your_salt".to_vec(),
261    ///    info: vec![], // empty info
262    /// };
263    ///
264    /// // Initialize the HMAC signer
265    /// let signer = HmacSigner::new(key_info, Algorithm::SHA256, Encoder::UrlSafe);
266    /// let result: Result<UserData, Error> = signer.unsign(&"token.signature");
267    /// // or
268    /// let result = signer.unsign::<UserData>(&"token.signature");
269    /// ```
270    fn unsign<T: for<'de> Deserialize<'de> + Data>(&self, token: &str) -> Result<T, Error> {
271        let parts: Vec<&str> = token.split(DELIM).collect();
272        if parts.len() != 2 {
273            return Err(Error::InvalidInput(token.to_string()));
274        }
275
276        let encoded_data = parts[0];
277        if encoded_data.is_empty() {
278            return Err(Error::InvalidToken);
279        }
280
281        let signature = match self.encoder.decode(parts[1]) {
282            Ok(signature) => signature,
283            Err(_) => return Err(Error::InvalidSignature),
284        };
285
286        let encoded_data = parts[0].as_bytes();
287        if !self.verify(&encoded_data, &signature) {
288            return Err(Error::InvalidToken);
289        }
290
291        // at this pt, the token is valid and hence we can safely unwrap
292        let decoded_data = self.encoder.decode(encoded_data).unwrap();
293        let data = String::from_utf8(decoded_data).unwrap();
294        let deserialised_data: T = serde_json::from_str(&data).unwrap();
295        if let Some(expiry) = deserialised_data.get_exp() {
296            if expiry < chrono::Utc::now() {
297                return Err(Error::TokenExpired);
298            }
299        }
300        Ok(deserialised_data)
301    }
302
303    /// Signs the data and returns the token which can be sent to the client.
304    ///
305    /// Sample Usage:
306    /// ```rust
307    /// use hmac_serialiser_rs::{HmacSigner, KeyInfo, Encoder, algorithm::Algorithm, errors::Error, SignerLogic, Data};
308    /// use serde::{Serialize, Deserialize};
309    ///
310    /// #[derive(Serialize, Deserialize, Debug)]
311    /// struct UserData {
312    ///     username: String,
313    /// }
314    /// impl Data for UserData {
315    ///    fn get_exp(&self) -> Option<chrono::DateTime<chrono::Utc>> {
316    ///         None
317    ///     }
318    /// }
319    ///
320    /// let key_info = KeyInfo {
321    ///    key: b"your_secret_key".to_vec(),
322    ///    salt: b"your_salt".to_vec(),
323    ///    info: b"auth-context".to_vec(),
324    /// };
325    ///
326    /// // Initialize the HMAC signer
327    /// let signer = HmacSigner::new(key_info, Algorithm::SHA256, Encoder::UrlSafe);
328    /// let user = UserData { username: "user123".to_string() };
329    /// let result: String = signer.sign(&user);
330    /// ```
331    fn sign<T: Serialize + Data>(&self, data: &T) -> String {
332        let token = serde_json::to_string(data).unwrap();
333        let token = self.encoder.encode(token.as_bytes());
334        let signature = self.sign_data(token.as_bytes());
335        let signature = self.encoder.encode(&signature);
336        format!("{}{}{}", token, DELIM, signature)
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use chrono::{Duration, Utc};
344
345    #[derive(Serialize, Deserialize, Debug)]
346    struct TestClaim {
347        #[serde(with = "chrono::serde::ts_seconds")]
348        exp: chrono::DateTime<Utc>,
349        data: String,
350    }
351
352    impl Data for TestClaim {
353        fn get_exp(&self) -> Option<chrono::DateTime<Utc>> {
354            Some(self.exp)
355        }
356    }
357
358    fn setup(salt: Vec<u8>, info: Vec<u8>, algo: Algorithm, encoder: Encoder) -> HmacSigner {
359        let key_info = KeyInfo {
360            key: b"test_secret_key".to_vec(),
361            salt,
362            info,
363        };
364        HmacSigner::new(key_info, algo, encoder)
365    }
366
367    #[test]
368    fn test_sign_and_unsign_valid_token() {
369        let signer = setup(
370            vec![1, 2, 3],
371            vec![4, 5, 6],
372            Algorithm::SHA256,
373            Encoder::UrlSafe,
374        );
375        let claim = TestClaim {
376            exp: Utc::now() + Duration::hours(1),
377            data: "test_data".to_string(),
378        };
379
380        let token = signer.sign(&claim);
381        let verified_claim: TestClaim = signer.unsign(&token).unwrap();
382        println!("Token: {}", token);
383        println!("Verified claim: {:?}", verified_claim);
384        assert_eq!(verified_claim.data, claim.data);
385    }
386
387    #[test]
388    fn test_invalid_token() {
389        let data = "tttttttttttttttttttttttttttttttttttttttttt";
390        let expected_error = Error::InvalidInput(data.to_string());
391        let signer = setup(
392            vec![1, 2, 3],
393            vec![4, 5, 6],
394            Algorithm::SHA256,
395            Encoder::UrlSafe,
396        );
397        match signer.unsign::<TestClaim>(&data) {
398            Ok(_) => panic!("Expected error"),
399            Err(e) => assert_eq!(e, expected_error),
400        };
401    }
402
403    #[test]
404    fn test_invalid_token_with_valid_signature() {
405        let signer = setup(
406            vec![1, 2, 3],
407            vec![4, 5, 6],
408            Algorithm::SHA256,
409            Encoder::UrlSafe,
410        );
411        let claim = TestClaim {
412            exp: Utc::now() + Duration::hours(1),
413            data: "test_data".to_string(),
414        };
415
416        let token = signer.sign(&claim);
417        let valid_signature = token.split('.').collect::<Vec<&str>>()[1];
418        let invalid_token = format!("{}.{}", "bad_data", valid_signature);
419        println!("Invalid token: {}", invalid_token);
420        println!("Valid token: {}", token);
421
422        let result: Result<TestClaim, Error> = signer.unsign(&invalid_token);
423        assert!(matches!(result, Err(Error::InvalidToken)));
424    }
425
426    #[test]
427    fn test_unsign_expired_token() {
428        let signer = setup(
429            vec![1, 2, 3],
430            vec![4, 5, 6],
431            Algorithm::SHA256,
432            Encoder::UrlSafe,
433        );
434        let claim = TestClaim {
435            exp: Utc::now() - Duration::hours(1),
436            data: "test_data".to_string(),
437        };
438
439        let token = signer.sign(&claim);
440        let result: Result<TestClaim, Error> = signer.unsign(&token);
441
442        assert!(matches!(result, Err(Error::TokenExpired)));
443    }
444
445    #[test]
446    fn test_unsign_invalid_signature() {
447        let signer = setup(
448            vec![1, 2, 3],
449            vec![4, 5, 6],
450            Algorithm::SHA256,
451            Encoder::UrlSafe,
452        );
453        let claim = TestClaim {
454            exp: Utc::now() + Duration::hours(1),
455            data: "test_data".to_string(),
456        };
457
458        let token = signer.sign(&claim);
459        let mut invalid_token = token.clone();
460        invalid_token.push_str("invalid");
461
462        let result: Result<TestClaim, Error> = signer.unsign(&invalid_token);
463
464        assert!(matches!(result, Err(Error::InvalidSignature)));
465    }
466
467    #[test]
468    fn test_unsign_malformed_token() {
469        let signer = setup(
470            vec![1, 2, 3],
471            vec![4, 5, 6],
472            Algorithm::SHA256,
473            Encoder::UrlSafe,
474        );
475
476        let malformed_token = "malformed.token";
477
478        let result: Result<TestClaim, Error> = signer.unsign(malformed_token);
479
480        assert!(matches!(result, Err(Error::InvalidSignature)));
481    }
482
483    #[test]
484    fn test_unsign_invalid_base64_signature() {
485        let signer = setup(
486            vec![1, 2, 3],
487            vec![4, 5, 6],
488            Algorithm::SHA256,
489            Encoder::UrlSafe,
490        );
491        let claim = TestClaim {
492            exp: Utc::now() + Duration::hours(1),
493            data: "test_data".to_string(),
494        };
495
496        let token = signer.sign(&claim);
497        let parts: Vec<&str> = token.split(DELIM).collect();
498        let invalid_token = format!("{}.{}", parts[0], "invalid_base64");
499
500        let result: Result<TestClaim, Error> = signer.unsign(&invalid_token);
501
502        assert!(matches!(result, Err(Error::InvalidSignature)));
503    }
504}