Skip to main content

Mountain/IPC/Encryption/
SecureChannel.rs

1//! # Secure Message Channel (IPC Encryption)
2//!
3//! ## RESPONSIBILITIES
4//! This module provides secure message channels using AES-256-GCM encryption
5//! with HMAC authentication. It ensures message confidentiality and integrity
6//! for sensitive IPC communications.
7//!
8//! ## ARCHITECTURAL ROLE
9//! This module is part of the security layer in the IPC architecture, providing
10//! end-to-end encryption for sensitive messages.
11//!
12//! ## KEY COMPONENTS
13//!
14//! - **SecureMessageChannel**: Encryption channel with AES-256-GCM + HMAC
15//! - **EncryptedMessage**: Encrypted message structure with nonce and HMAC tag
16//!
17//! ## ERROR HANDLING
18//! All encryption/decryption operations return Result types with descriptive
19//! error messages for failures.
20//!
21//! ## LOGGING
22// Debug-level logging for key operations, error for failures.
23//
24// ## Performance Considerations
25// - AES-256-GCM provides hardware-accelerated encryption on modern CPUs
26// - Nonce-based encryption ensures unique ciphertexts
27// - HMAC provides message authentication and integrity verification
28//
29// ## TODO
30// - Add encryption key rotation
31// - Implement symmetric key exchange protocol
32// - Add support for multiple encryption algorithms
33// - Implement message replay attack prevention
34
35use ring::{
36	aead::{self, AES_256_GCM, LessSafeKey, UnboundKey},
37	hmac,
38	rand::{SecureRandom, SystemRandom},
39};
40use serde::{Deserialize, Serialize};
41
42use super::super::Message::Types::TauriIPCMessage;
43use crate::dev_log;
44
45/// Encrypted message structure
46///
47/// This structure contains the encrypted message data along with the nonce
48/// and HMAC tag needed for decryption and verification.
49///
50/// ## Message Structure
51///
52/// ```text
53/// EncryptedMessage {
54///     nonce: [u8; 12],      // Unique value for each encryption
55///     ciphertext: Vec<u8>,  // Encrypted message + auth tag
56///     hmac_tag: Vec<u8>,    // HMAC for message authentication
57/// }
58/// ```
59///
60/// ## Example Usage
61///
62/// ```rust,ignore
63/// let encrypted = EncryptedMessage {
64///     nonce: vec![1, 2, 3, ...],
65///     ciphertext: vec![...],
66///     hmac_tag: vec![...],
67/// };
68/// ```
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct EncryptedMessage {
71	/// Nonce used for encryption (12 bytes for AES-256-GCM)
72	pub nonce:Vec<u8>,
73
74	/// Encrypted message data with authentication tag
75	pub ciphertext:Vec<u8>,
76
77	/// HMAC tag for message authentication
78	pub hmac_tag:Vec<u8>,
79}
80
81impl EncryptedMessage {
82	/// Create a new encrypted message
83	pub fn new(nonce:Vec<u8>, ciphertext:Vec<u8>, hmac_tag:Vec<u8>) -> Self { Self { nonce, ciphertext, hmac_tag } }
84
85	/// Validate the message structure
86	pub fn is_valid(&self) -> bool {
87		self.nonce.len() == 12 // AES-256-GCM requires 12-byte nonce
88			&& !self.ciphertext.is_empty()
89			&& !self.hmac_tag.is_empty()
90	}
91}
92
93/// Secure message channel with encryption and authentication
94///
95/// This structure provides AES-256-GCM encryption with HMAC authentication
96/// for secure IPC communication. It ensures message confidentiality and
97/// integrity.
98///
99/// ## Encryption Flow
100///
101/// ```text
102/// TauriIPCMessage
103///     |
104///     | 1. Serialize to JSON
105///     v
106/// Serialized bytes
107///     |
108///     | 2. Encrypt with AES-256-GCM
109///     v
110/// Encrypted bytes + auth tag
111///     |
112///     | 3. Generate HMAC
113///     v
114/// EncryptedMessage (nonce, ciphertext, hmac_tag)
115/// ```
116///
117/// ## Decryption Flow
118///
119/// ```text
120/// EncryptedMessage
121///     |
122///     | 1. Verify HMAC
123///     v
124/// HMAC valid
125///     |
126///     | 2. Decrypt with AES-256-GCM
127///     v
128/// Serialized bytes
129///     |
130///     | 3. Deserialize to TauriIPCMessage
131///     v
132/// TauriIPCMessage
133/// ```
134///
135/// ## Security Features
136///
137/// - **AES-256-GCM**: Industry-standard authenticated encryption
138/// - **Unique Nonces**: Each encryption uses a unique nonce
139/// - **HMAC Authentication**: Additional layer of message authentication
140/// - **Secure Random Generation**: Cryptographically secure random keys
141///
142/// ## Example Usage
143///
144/// ```rust,ignore
145/// let secure_channel = SecureMessageChannel::new()?;
146///
147/// // Encrypt a message
148/// let encrypted = secure_channel.encrypt_message(&message)?;
149///
150/// // Decrypt a message
151/// let decrypted = secure_channel.decrypt_message(&encrypted)?;
152///
153/// // Rotate keys
154/// secure_channel.rotate_keys()?;
155/// ```
156pub struct SecureMessageChannel {
157	/// AES-256-GCM encryption key
158	encryption_key:LessSafeKey,
159
160	/// HMAC key for message authentication
161	hmac_key:Vec<u8>,
162}
163
164impl SecureMessageChannel {
165	/// Create a new secure channel with randomly generated keys
166	///
167	/// This method generates cryptographically secure random keys for
168	/// encryption and HMAC authentication.
169	///
170	/// ## Returns
171	/// - `Ok(SecureMessageChannel)`: New secure channel
172	/// - `Err(String)`: Error message if key generation fails
173	///
174	/// ## Example
175	///
176	/// ```rust,ignore
177	/// let secure_channel = SecureMessageChannel::new()?;
178	/// ```
179	pub fn new() -> Result<Self, String> {
180		dev_log!("encryption", "[SecureMessageChannel] Creating new secure channel");
181
182		let rng = SystemRandom::new();
183
184		// Generate 256-bit (32-byte) encryption key
185		let mut encryption_key_bytes = vec![0u8; 32];
186
187		rng.fill(&mut encryption_key_bytes)
188			.map_err(|e| format!("Failed to generate encryption key: {}", e))?;
189
190		let unbound_key = UnboundKey::new(&AES_256_GCM, &encryption_key_bytes)
191			.map_err(|e| format!("Failed to create unbound key: {}", e))?;
192
193		let encryption_key = LessSafeKey::new(unbound_key);
194
195		// Generate 256-bit HMAC key
196		let mut hmac_key = vec![0u8; 32];
197
198		rng.fill(&mut hmac_key)
199			.map_err(|e| format!("Failed to generate HMAC key: {}", e))?;
200
201		dev_log!("encryption", "[SecureMessageChannel] Secure channel created successfully");
202
203		Ok(Self { encryption_key, hmac_key })
204	}
205
206	/// Encrypt and authenticate a message
207	///
208	/// This method serializes the message, encrypts it with AES-256-GCM,
209	/// and adds an HMAC tag for authentication.
210	///
211	/// ## Parameters
212	/// - `message`: The message to encrypt
213	///
214	/// ## Returns
215	/// - `Ok(EncryptedMessage)`: Encrypted message with nonce and HMAC tag
216	/// - `Err(String)`: Error message if encryption fails
217	///
218	/// ## Example
219	///
220	/// ```rust,ignore
221	/// let encrypted = secure_channel.encrypt_message(&message)?;
222	/// ```
223	pub fn encrypt_message(&self, message:&TauriIPCMessage) -> Result<EncryptedMessage, String> {
224		dev_log!(
225			"encryption",
226			"[SecureMessageChannel] Encrypting message on channel: {}",
227			message.channel
228		);
229
230		// Serialize message to bytes
231		let serialized_message =
232			serde_json::to_vec(message).map_err(|e| format!("Failed to serialize message: {}", e))?;
233
234		// Generate unique 12-byte nonce (required for AES-256-GCM)
235		let mut nonce = [0u8; 12];
236
237		SystemRandom::new()
238			.fill(&mut nonce)
239			.map_err(|e| format!("Failed to generate nonce: {}", e))?;
240
241		// Encrypt with AES-256-GCM (authenticated encryption)
242		let mut in_out = serialized_message.clone();
243
244		self.encryption_key
245			.seal_in_place_append_tag(aead::Nonce::assume_unique_for_key(nonce), aead::Aad::empty(), &mut in_out)
246			.map_err(|e| format!("Encryption failed: {}", e))?;
247
248		// Generate HMAC for additional authentication
249		let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, &self.hmac_key);
250
251		let hmac_tag = hmac::sign(&hmac_key, &in_out);
252
253		let encrypted_message =
254			EncryptedMessage { nonce:nonce.to_vec(), ciphertext:in_out, hmac_tag:hmac_tag.as_ref().to_vec() };
255
256		dev_log!(
257			"encryption",
258			"[SecureMessageChannel] Message encrypted: {} bytes -> {} bytes",
259			serialized_message.len(),
260			encrypted_message.ciphertext.len()
261		);
262
263		Ok(encrypted_message)
264	}
265
266	/// Decrypt and verify a message
267	///
268	/// This method verifies the HMAC tag, decrypts the message with
269	/// AES-256-GCM, and deserializes it back to the original format.
270	///
271	/// ## Parameters
272	/// - `encrypted`: The encrypted message to decrypt
273	///
274	/// ## Returns
275	/// - `Ok(TauriIPCMessage)`: Decrypted message
276	/// - `Err(String)`: Error message if decryption or verification fails
277	///
278	/// ## Example
279	///
280	/// ```rust,ignore
281	/// let decrypted = secure_channel.decrypt_message(&encrypted)?;
282	/// ```
283	pub fn decrypt_message(&self, encrypted:&EncryptedMessage) -> Result<TauriIPCMessage, String> {
284		dev_log!("encryption", "[SecureMessageChannel] Decrypting message");
285
286		// Verify HMAC first (detect tampering)
287		let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, &self.hmac_key);
288
289		hmac::verify(&hmac_key, &encrypted.ciphertext, &encrypted.hmac_tag)
290			.map_err(|_| "HMAC verification failed - message may be tampered".to_string())?;
291
292		// Convert nonce slice to array
293		let nonce_slice:&[u8] = &encrypted.nonce;
294
295		let nonce_array:[u8; 12] = nonce_slice
296			.try_into()
297			.map_err(|_| "Invalid nonce length - must be 12 bytes".to_string())?;
298
299		let nonce = aead::Nonce::assume_unique_for_key(nonce_array);
300
301		// Decrypt with AES-256-GCM
302		let mut in_out = encrypted.ciphertext.clone();
303
304		self.encryption_key
305			.open_in_place(nonce, aead::Aad::empty(), &mut in_out)
306			.map_err(|e| format!("Decryption failed: {}", e))?;
307
308		// Remove the authentication tag (last 16 bytes for AES-256-GCM)
309		let plaintext_len = in_out.len() - AES_256_GCM.tag_len();
310
311		in_out.truncate(plaintext_len);
312
313		// Deserialize message
314		let message:TauriIPCMessage =
315			serde_json::from_slice(&in_out).map_err(|e| format!("Failed to deserialize message: {}", e))?;
316
317		dev_log!(
318			"encryption",
319			"[SecureMessageChannel] Message decrypted successfully on channel: {}",
320			message.channel
321		);
322
323		Ok(message)
324	}
325
326	/// Rotate encryption keys
327	///
328	/// This method generates new encryption and HMAC keys, effectively
329	/// rotating the security credentials for the channel.
330	///
331	/// ## Returns
332	/// - `Ok(())`: Keys rotated successfully
333	/// - `Err(String)`: Error message if key rotation fails
334	///
335	/// ## Example
336	///
337	/// ```rust,ignore
338	/// secure_channel.rotate_keys()?;
339	/// ```
340	pub fn rotate_keys(&mut self) -> Result<(), String> {
341		dev_log!("encryption", "[SecureMessageChannel] Rotating encryption keys");
342
343		*self = Self::new()?;
344
345		dev_log!("encryption", "[SecureMessageChannel] Keys rotated successfully");
346
347		Ok(())
348	}
349
350	/// Get the HMAC tag length (in bytes)
351	pub fn hmac_tag_length(&self) -> usize {
352		32 // HMAC-SHA256 produces 32-byte tags
353	}
354
355	/// Get the nonce length (in bytes)
356	pub fn nonce_length(&self) -> usize {
357		12 // AES-256-GCM requires 12-byte nonces
358	}
359
360	/// Get the authentication tag length (in bytes)
361	pub fn auth_tag_length(&self) -> usize { AES_256_GCM.tag_len() }
362
363	/// Get the key length (in bytes)
364	pub fn key_length(&self) -> usize {
365		32 // AES-256 uses 32-byte keys
366	}
367}
368
369#[cfg(test)]
370#[allow(unused_imports)]
371mod tests {
372
373	use super::*;
374
375	fn create_test_message() -> TauriIPCMessage {
376		TauriIPCMessage::new(
377			"test_channel".to_string(),
378			serde_json::json!({
379				"data": "sensitive information that should be encrypted",
380				"id": 12345
381			}),
382			Some("test_sender".to_string()),
383		)
384	}
385
386	#[test]
387	fn test_secure_channel_creation() {
388		let channel = SecureMessageChannel::new();
389
390		assert!(channel.is_ok());
391	}
392
393	#[test]
394	fn test_encrypt_and_decrypt() {
395		let channel = SecureMessageChannel::new().unwrap();
396
397		let original_message = create_test_message();
398
399		// Encrypt
400		let encrypted = channel.encrypt_message(&original_message).unwrap();
401
402		assert!(encrypted.is_valid());
403
404		// Decrypt
405		let decrypted = channel.decrypt_message(&encrypted).unwrap();
406
407		// Verify content
408		assert_eq!(decrypted.channel, original_message.channel);
409
410		assert_eq!(decrypted.data, original_message.data);
411
412		assert_eq!(decrypted.sender, original_message.sender);
413	}
414
415	#[test]
416	fn test_encryption_produces_different_outputs() {
417		let channel = SecureMessageChannel::new().unwrap();
418
419		let message = create_test_message();
420
421		let encrypted1 = channel.encrypt_message(&message).unwrap();
422
423		let encrypted2 = channel.encrypt_message(&message).unwrap();
424
425		// Each encryption should produce different output (due to unique nonces)
426		assert_ne!(encrypted1.nonce, encrypted2.nonce);
427
428		assert_ne!(encrypted1.ciphertext, encrypted2.ciphertext);
429	}
430
431	#[test]
432	fn test_tampered_message_fails_hmac_verification() {
433		let channel = SecureMessageChannel::new().unwrap();
434
435		let message = create_test_message();
436
437		let mut encrypted = channel.encrypt_message(&message).unwrap();
438
439		// Tamper with the ciphertext
440		if !encrypted.ciphertext.is_empty() {
441			encrypted.ciphertext[0] ^= 0xFF;
442		}
443
444		// Should fail HMAC verification
445		let result = channel.decrypt_message(&encrypted);
446
447		assert!(result.is_err());
448
449		assert!(result.unwrap_err().contains("HMAC verification failed"));
450	}
451
452	#[test]
453	fn test_invalid_nonce_length() {
454		let channel = SecureMessageChannel::new().unwrap();
455
456		let message = create_test_message();
457
458		let mut encrypted = channel.encrypt_message(&message).unwrap();
459
460		// Corrupt the nonce length
461		encrypted.nonce = vec![0u8; 16]; // Wrong length
462
463		let result = channel.decrypt_message(&encrypted);
464
465		assert!(result.is_err());
466
467		assert!(result.unwrap_err().contains("Invalid nonce length"));
468	}
469
470	#[test]
471	fn test_message_channel_key_lengths() {
472		let channel = SecureMessageChannel::new().unwrap();
473
474		assert_eq!(channel.key_length(), 32);
475
476		assert_eq!(channel.nonce_length(), 12);
477
478		assert_eq!(channel.auth_tag_length(), 16); // AES-256-GCM
479		assert_eq!(channel.hmac_tag_length(), 32); // HMAC-SHA256
480	}
481
482	#[test]
483	fn test_key_rotation() {
484		let mut channel = SecureMessageChannel::new().unwrap();
485
486		let message = create_test_message();
487
488		// Encrypt with original keys
489		let encrypted1 = channel.encrypt_message(&message).unwrap();
490
491		// Rotate keys
492		let result = channel.rotate_keys();
493
494		assert!(result.is_ok());
495
496		// Old encrypted message should still decode successfully
497		let decrypted1 = channel.decrypt_message(&encrypted1).unwrap();
498
499		assert_eq!(decrypted1.channel, message.channel);
500
501		// New encryption should work with new keys
502		let encrypted2 = channel.encrypt_message(&message).unwrap();
503
504		let decrypted2 = channel.decrypt_message(&encrypted2).unwrap();
505
506		assert_eq!(decrypted2.channel, message.channel);
507
508		// Encrypted versions should be different
509		assert_ne!(encrypted1.nonce, encrypted2.nonce);
510	}
511
512	#[test]
513	fn test_empty_message() {
514		let channel = SecureMessageChannel::new().unwrap();
515
516		let message = TauriIPCMessage::new("test".to_string(), serde_json::json!(null), None);
517
518		let encrypted = channel.encrypt_message(&message).unwrap();
519
520		let decrypted = channel.decrypt_message(&encrypted).unwrap();
521
522		assert_eq!(decrypted.channel, "test");
523	}
524}