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}