dmsc/auth/
security.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Security Utilities Module
19//!
20//! This module provides security-related utilities for DMSC, including:
21//! - Configuration encryption and decryption using AES-256-GCM
22//! - Sensitive data protection with HMAC-SHA256 signing
23//! - Cryptographic key generation and management
24//!
25//! ## Encryption
26//!
27//! The module uses AES-256-GCM (Galois/Counter Mode) for symmetric encryption,
28//! providing both confidentiality and authenticity. Nonce values are generated
29//! randomly for each encryption operation.
30//!
31//! ## HMAC Signing
32//!
33//! HMAC-SHA256 is used for message authentication, ensuring data integrity
34//! and authenticity. Both signing and verification functions are provided.
35//!
36//! ## Key Management
37//!
38//! Encryption and HMAC keys are loaded from environment variables:
39//! - `DMSC_ENCRYPTION_KEY`: 32-byte hex-encoded key for encryption
40//! - `DMSC_HMAC_KEY`: 32-byte hex-encoded key for HMAC
41//!
42//! If not set, keys are generated randomly using cryptographically secure
43//! random number generators.
44//!
45//! ## Security Considerations
46//!
47//! - Keys should be stored securely in production environments
48//! - Randomly generated keys are lost on application restart
49//! - Consider using a secrets management solution for production
50//! - Encrypted data includes a random nonce, so the same plaintext encrypts differently each time
51
52use aes_gcm::aead::Aead;
53use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
54use base64::{engine::general_purpose::STANDARD, Engine as _};
55use generic_array::GenericArray;
56use rand::RngCore;
57use ring::hmac;
58use std::env;
59
60const ENCRYPTION_KEY_ENV: &str = "DMSC_ENCRYPTION_KEY";
61const HMAC_KEY_ENV: &str = "DMSC_HMAC_KEY";
62const DEFAULT_KEY_LENGTH: usize = 32;
63const NONCE_LENGTH: usize = 12;
64
65fn load_or_generate_key(env_var: &str, length: usize) -> Vec<u8> {
66    env::var(env_var)
67        .ok()
68        .and_then(|s| hex::decode(s).ok())
69        .unwrap_or_else(|| {
70            let mut key = vec![0u8; length];
71            rand::thread_rng().fill_bytes(&mut key);
72            key
73        })
74}
75
76fn load_encryption_key() -> Vec<u8> {
77    load_or_generate_key(ENCRYPTION_KEY_ENV, DEFAULT_KEY_LENGTH)
78}
79
80fn load_hmac_key() -> Vec<u8> {
81    load_or_generate_key(HMAC_KEY_ENV, DEFAULT_KEY_LENGTH)
82}
83
84/// Security utilities manager for DMSC.
85///
86/// This struct provides static methods for encryption, decryption, HMAC signing,
87/// and key management operations. It is designed as a singleton utility class
88/// with no instance state.
89///
90/// ## Thread Safety
91///
92/// All methods are stateless and can be safely called concurrently from multiple threads.
93///
94/// ## Usage
95///
96/// ```rust,ignore
97/// use dmsc::auth::security::DMSCSecurityManager;
98///
99/// // Encrypt sensitive data
100/// let encrypted = DMSCSecurityManager::encrypt("secret data");
101///
102/// // Decrypt data
103/// let decrypted = DMSCSecurityManager::decrypt(&encrypted);
104///
105/// // Sign data with HMAC
106/// let signature = DMSCSecurityManager::hmac_sign("data to sign");
107///
108/// // Verify HMAC signature
109/// let is_valid = DMSCSecurityManager::hmac_verify("data to verify", &signature);
110/// ```
111pub struct DMSCSecurityManager;
112
113impl DMSCSecurityManager {
114    /// Encrypts plaintext data using AES-256-GCM.
115    ///
116    /// This method encrypts the input string using AES-256-GCM (Galois/Counter Mode),
117    /// which provides both confidentiality and authenticity. A random nonce is generated
118    /// for each encryption operation, so the same plaintext produces different ciphertext
119    /// each time it is encrypted.
120    ///
121    /// ## Output Format
122    ///
123    /// The output is Base64-encoded and contains:
124    /// - 12-byte nonce (randomly generated)
125    /// - Encrypted data with authentication tag
126    ///
127    /// # Parameters
128    ///
129    /// - `plaintext`: The text string to encrypt
130    ///
131    /// # Returns
132    ///
133    /// Base64-encoded encrypted data
134    ///
135    /// # Examples
136    ///
137    /// ```rust,ignore
138    /// use dmsc::auth::security::DMSCSecurityManager;
139    ///
140    /// let encrypted = DMSCSecurityManager::encrypt("sensitive data");
141    /// println!("Encrypted: {}", encrypted);
142    /// ```
143    pub fn encrypt(plaintext: &str) -> String {
144        let key = load_encryption_key();
145        let nonce = {
146            let mut n = [0u8; NONCE_LENGTH];
147            rand::thread_rng().fill_bytes(&mut n);
148            n
149        };
150
151        let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
152        let ciphertext = cipher
153            .encrypt(Nonce::from_slice(&nonce), plaintext.as_bytes())
154            .expect("encryption failure");
155
156        let mut result = Vec::with_capacity(nonce.len() + ciphertext.len());
157        result.extend_from_slice(&nonce);
158        result.extend_from_slice(&ciphertext);
159
160        STANDARD.encode(result)
161    }
162
163    /// Decrypts encrypted data using AES-256-GCM.
164    ///
165    /// This method decrypts data that was encrypted using the `encrypt` method.
166    /// It verifies the authentication tag and returns the original plaintext.
167    ///
168    /// ## Failure Conditions
169    ///
170    /// Returns `None` if:
171    /// - The input is not valid Base64
172    /// - The input is shorter than the nonce length
173    /// - The authentication tag verification fails (wrong key or tampered data)
174    ///
175    /// # Parameters
176    ///
177    /// - `encrypted`: Base64-encoded encrypted data
178    ///
179    /// # Returns
180    ///
181    /// `Some(String)` containing the decrypted plaintext, or `None` if decryption fails
182    ///
183    /// # Examples
184    ///
185    /// ```rust,ignore
186    /// use dmsc::auth::security::DMSCSecurityManager;
187    ///
188    /// let encrypted = DMSCSecurityManager::encrypt("secret");
189    /// let decrypted = DMSCSecurityManager::decrypt(&encrypted);
190    ///
191    /// match decrypted {
192    ///     Some(text) => println!("Decrypted: {}", text),
193    ///     None => println!("Decryption failed!"),
194    /// }
195    /// ```
196    pub fn decrypt(encrypted: &str) -> Option<String> {
197        let key = load_encryption_key();
198        let data = STANDARD.decode(encrypted).ok()?;
199
200        if data.len() < NONCE_LENGTH {
201            return None;
202        }
203
204        let (nonce, ciphertext) = data.split_at(NONCE_LENGTH);
205        let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
206
207        cipher
208            .decrypt(Nonce::from_slice(nonce), ciphertext)
209            .ok()
210            .map(|v| String::from_utf8(v).ok())?
211    }
212
213    /// Signs data using HMAC-SHA256.
214    ///
215    /// This method creates an HMAC signature using the configured HMAC key
216    /// and SHA-256 hash algorithm. The signature is returned as a hex-encoded string.
217    ///
218    /// ## Security
219    ///
220    /// HMAC provides message integrity and authenticity verification. Only parties
221    /// with access to the HMAC key can create or verify signatures.
222    ///
223    /// # Parameters
224    ///
225    /// - `data`: The data string to sign
226    ///
227    /// # Returns
228    ///
229    /// Hex-encoded HMAC signature
230    ///
231    /// # Examples
232    ///
233    /// ```rust,ignore
234    /// use dmsc::auth::security::DMSCSecurityManager;
235    ///
236    /// let data = "important message";
237    /// let signature = DMSCSecurityManager::hmac_sign(data);
238    /// println!("Signature: {}", signature);
239    /// ```
240    pub fn hmac_sign(data: &str) -> String {
241        let key = load_hmac_key();
242        let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &key);
243        let signature = hmac::sign(&signing_key, data.as_bytes());
244        hex::encode(signature)
245    }
246
247    /// Verifies an HMAC-SHA256 signature.
248    ///
249    /// This method verifies that the provided signature matches the data using
250    /// constant-time comparison to prevent timing attacks.
251    ///
252    /// ## Signature Format
253    ///
254    /// The signature must be a valid hex-encoded string as produced by `hmac_sign`.
255    ///
256    /// # Parameters
257    ///
258    /// - `data`: The original data that was signed
259    /// - `signature`: The hex-encoded signature to verify
260    ///
261    /// # Returns
262    ///
263    /// `true` if the signature is valid, `false` otherwise
264    ///
265    /// # Examples
266    ///
267    /// ```rust,ignore
268    /// use dmsc::auth::security::DMSCSecurityManager;
269    ///
270    /// let data = "important message";
271    /// let signature = DMSCSecurityManager::hmac_sign(data);
272    ///
273    /// if DMSCSecurityManager::hmac_verify(data, &signature) {
274    ///     println!("Signature is valid!");
275    /// } else {
276    ///     println!("Signature is invalid!");
277    /// }
278    /// ```
279    pub fn hmac_verify(data: &str, signature: &str) -> bool {
280        let expected = hex::decode(signature).ok().unwrap_or_default();
281        let key = load_hmac_key();
282        let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &key);
283        hmac::verify(&signing_key, data.as_bytes(), &expected).is_ok()
284    }
285
286    /// Generates a new encryption key.
287    ///
288    /// This method generates a cryptographically secure random 32-byte (256-bit) key
289    /// suitable for AES-256 encryption. The key is returned as a hex-encoded string.
290    ///
291    /// ## Usage
292    ///
293    /// This method can be used to generate keys for initial configuration or key rotation.
294    /// Store the generated key securely and set it via the `DMSC_ENCRYPTION_KEY` environment variable.
295    ///
296    /// # Returns
297    ///
298    /// Hex-encoded 32-byte encryption key
299    ///
300    /// # Examples
301    ///
302    /// ```rust,ignore
303    /// use dmsc::auth::security::DMSCSecurityManager;
304    ///
305    /// let key = DMSCSecurityManager::generate_encryption_key();
306    /// println!("New encryption key: {}", key);
307    /// ```
308    pub fn generate_encryption_key() -> String {
309        let mut key = vec![0u8; DEFAULT_KEY_LENGTH];
310        rand::thread_rng().fill_bytes(&mut key);
311        hex::encode(key)
312    }
313
314    /// Generates a new HMAC key.
315    ///
316    /// This method generates a cryptographically secure random 32-byte (256-bit) key
317    /// suitable for HMAC-SHA256 signing. The key is returned as a hex-encoded string.
318    ///
319    /// ## Usage
320    ///
321    /// This method can be used to generate keys for initial configuration or key rotation.
322    /// Store the generated key securely and set it via theDMSC_HMAC_KEY` environment variable.
323    ///
324    /// # Returns
325    ///
326    /// Hex-encoded 32-byte HMAC key
327    ///
328    /// # Examples
329    ///
330    /// ```rust,ignore
331    /// use dmsc::auth::security::DMSCSecurityManager;
332    ///
333    /// let key = DMSCSecurityManager::generate_hmac_key();
334    /// println!("New HMAC key: {}", key);
335    /// ```
336    pub fn generate_hmac_key() -> String {
337        let mut key = vec![0u8; DEFAULT_KEY_LENGTH];
338        rand::thread_rng().fill_bytes(&mut key);
339        hex::encode(key)
340    }
341}