1use crate::error::SecurityError;
22use crate::{P2PError, Result};
23use argon2::{
24 Algorithm, Argon2, Params, Version,
25 password_hash::{PasswordHasher, SaltString, rand_core::RngCore},
26};
27use saorsa_pqc::{
28 ChaCha20Poly1305Cipher, HkdfSha3_256, SymmetricEncryptedMessage, SymmetricKey, api::traits::Kdf,
29};
30use serde::{Deserialize, Serialize};
31
32const CHACHA_KEY_SIZE: usize = 32;
34
35const SALT_SIZE: usize = 32;
37
38const DEVICE_ARGON2_MEMORY: u32 = 32768; const DEVICE_ARGON2_TIME: u32 = 2;
41const DEVICE_ARGON2_PARALLELISM: u32 = 2;
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct EncryptedData {
46 pub encrypted_message: SymmetricEncryptedMessage,
48 pub salt: [u8; SALT_SIZE],
50}
51
52pub fn encrypt_with_device_password(data: &[u8], device_password: &str) -> Result<EncryptedData> {
54 let mut salt = [0u8; SALT_SIZE];
56 let mut rng = rand::thread_rng();
57 rng.fill_bytes(&mut salt);
58
59 let key_bytes = derive_key_from_password(device_password, &salt)?;
61 let symmetric_key = SymmetricKey::from_bytes(key_bytes);
62
63 let cipher = ChaCha20Poly1305Cipher::new(&symmetric_key);
65 let (ciphertext, nonce) = cipher.encrypt(data, None).map_err(|e| {
66 P2PError::Security(SecurityError::EncryptionFailed(
67 format!("ChaCha20Poly1305 encryption failed: {:?}", e).into(),
68 ))
69 })?;
70
71 let encrypted_message = SymmetricEncryptedMessage::new(ciphertext, nonce, None);
72
73 Ok(EncryptedData {
74 encrypted_message,
75 salt,
76 })
77}
78
79pub fn decrypt_with_device_password(
81 encrypted: &EncryptedData,
82 device_password: &str,
83) -> Result<Vec<u8>> {
84 let key_bytes = derive_key_from_password(device_password, &encrypted.salt)?;
86 let symmetric_key = SymmetricKey::from_bytes(key_bytes);
87
88 let cipher = ChaCha20Poly1305Cipher::new(&symmetric_key);
90 let plaintext = cipher
91 .decrypt(
92 &encrypted.encrypted_message.ciphertext,
93 &encrypted.encrypted_message.nonce,
94 None,
95 )
96 .map_err(|e| {
97 P2PError::Security(SecurityError::DecryptionFailed(
98 format!("ChaCha20Poly1305 decryption failed: {:?}", e).into(),
99 ))
100 })?;
101
102 Ok(plaintext)
103}
104
105fn derive_key_from_password(
107 password: &str,
108 salt: &[u8; SALT_SIZE],
109) -> Result<[u8; CHACHA_KEY_SIZE]> {
110 let argon2 = Argon2::new(
112 Algorithm::Argon2id,
113 Version::V0x13,
114 Params::new(
115 DEVICE_ARGON2_MEMORY,
116 DEVICE_ARGON2_TIME,
117 DEVICE_ARGON2_PARALLELISM,
118 Some(CHACHA_KEY_SIZE),
119 )
120 .map_err(|e| {
121 P2PError::Security(SecurityError::InvalidKey(
122 format!("Invalid Argon2 params: {}", e).into(),
123 ))
124 })?,
125 );
126
127 let salt_string = SaltString::encode_b64(salt).map_err(|e| {
129 P2PError::Security(SecurityError::InvalidKey(
130 format!("Failed to encode salt: {}", e).into(),
131 ))
132 })?;
133
134 let hash = argon2
136 .hash_password(password.as_bytes(), &salt_string)
137 .map_err(|e| {
138 P2PError::Security(SecurityError::KeyGenerationFailed(
139 format!("Argon2id key derivation failed: {}", e).into(),
140 ))
141 })?;
142
143 let hash_output = hash.hash.ok_or_else(|| {
144 P2PError::Security(SecurityError::KeyGenerationFailed(
145 "No hash output from Argon2".to_string().into(),
146 ))
147 })?;
148
149 let key_bytes = hash_output.as_bytes();
150 if key_bytes.len() < CHACHA_KEY_SIZE {
151 return Err(P2PError::Security(SecurityError::KeyGenerationFailed(
152 "Insufficient key material from Argon2".to_string().into(),
153 )));
154 }
155
156 let mut result = [0u8; CHACHA_KEY_SIZE];
157 result.copy_from_slice(&key_bytes[..CHACHA_KEY_SIZE]);
158 Ok(result)
159}
160
161pub fn encrypt_with_shared_secret(
163 data: &[u8],
164 shared_secret: &[u8; 32],
165 info: &[u8],
166) -> Result<EncryptedData> {
167 let mut salt = [0u8; SALT_SIZE];
169 let mut rng = rand::thread_rng();
170 rng.fill_bytes(&mut salt);
171
172 let mut key_bytes = [0u8; CHACHA_KEY_SIZE];
174 HkdfSha3_256::derive(shared_secret, Some(&salt), info, &mut key_bytes).map_err(|e| {
175 P2PError::Security(SecurityError::KeyGenerationFailed(
176 format!("HKDF-SHA3 derivation failed: {:?}", e).into(),
177 ))
178 })?;
179
180 let symmetric_key = SymmetricKey::from_bytes(key_bytes);
181
182 let cipher = ChaCha20Poly1305Cipher::new(&symmetric_key);
184 let (ciphertext, nonce) = cipher.encrypt(data, None).map_err(|e| {
185 P2PError::Security(SecurityError::EncryptionFailed(
186 format!("ChaCha20Poly1305 encryption failed: {:?}", e).into(),
187 ))
188 })?;
189
190 let encrypted_message = SymmetricEncryptedMessage::new(ciphertext, nonce, None);
191
192 Ok(EncryptedData {
193 encrypted_message,
194 salt,
195 })
196}
197
198pub fn decrypt_with_shared_secret(
200 encrypted: &EncryptedData,
201 shared_secret: &[u8; 32],
202 info: &[u8],
203) -> Result<Vec<u8>> {
204 let mut key_bytes = [0u8; CHACHA_KEY_SIZE];
206 HkdfSha3_256::derive(shared_secret, Some(&encrypted.salt), info, &mut key_bytes).map_err(
207 |e| {
208 P2PError::Security(SecurityError::KeyGenerationFailed(
209 format!("HKDF-SHA3 derivation failed: {:?}", e).into(),
210 ))
211 },
212 )?;
213
214 let symmetric_key = SymmetricKey::from_bytes(key_bytes);
215
216 let cipher = ChaCha20Poly1305Cipher::new(&symmetric_key);
218 let plaintext = cipher
219 .decrypt(
220 &encrypted.encrypted_message.ciphertext,
221 &encrypted.encrypted_message.nonce,
222 None,
223 )
224 .map_err(|e| {
225 P2PError::Security(SecurityError::DecryptionFailed(
226 format!("ChaCha20Poly1305 decryption failed: {:?}", e).into(),
227 ))
228 })?;
229
230 Ok(plaintext)
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_device_password_encryption() {
239 let data = b"Secret identity data";
240 let password = "MyDevicePassword123!";
241
242 let encrypted =
244 encrypt_with_device_password(data, password).expect("Encryption should succeed");
245
246 assert!(!encrypted.encrypted_message.ciphertext.is_empty());
248
249 let decrypted =
251 decrypt_with_device_password(&encrypted, password).expect("Decryption should succeed");
252
253 assert_eq!(decrypted, data);
254 }
255
256 #[test]
257 fn test_encryption_serialization() {
258 let data = b"Test data for serialization";
259 let password = "SerializeTest123!";
260
261 let encrypted =
263 encrypt_with_device_password(data, password).expect("Encryption should succeed");
264
265 let serialized = postcard::to_stdvec(&encrypted).expect("Serialization should succeed");
267
268 let deserialized: EncryptedData =
270 postcard::from_bytes(&serialized).expect("Deserialization should succeed");
271
272 assert_eq!(
274 encrypted.encrypted_message.ciphertext,
275 deserialized.encrypted_message.ciphertext
276 );
277 assert_eq!(encrypted.salt, deserialized.salt);
278
279 let decrypted = decrypt_with_device_password(&deserialized, password)
281 .expect("Decryption should succeed");
282
283 assert_eq!(decrypted, data);
284 }
285
286 #[test]
287 fn test_wrong_password_fails() {
288 let data = b"Secret identity data";
289 let password = "MyDevicePassword123!";
290 let wrong_password = "WrongPassword456!";
291
292 let encrypted =
294 encrypt_with_device_password(data, password).expect("Encryption should succeed");
295
296 let result = decrypt_with_device_password(&encrypted, wrong_password);
298
299 assert!(result.is_err());
300 }
301
302 #[test]
303 fn test_shared_secret_encryption() {
304 let data = b"Peer to peer message";
305 let shared_secret = [42u8; 32];
306 let info = b"p2p-identity-sync";
307
308 let encrypted = encrypt_with_shared_secret(data, &shared_secret, info)
310 .expect("Encryption should succeed");
311
312 let decrypted = decrypt_with_shared_secret(&encrypted, &shared_secret, info)
314 .expect("Decryption should succeed");
315
316 assert_eq!(decrypted, data);
317 }
318
319 #[test]
320 fn test_different_info_fails() {
321 let data = b"Peer to peer message";
322 let shared_secret = [42u8; 32];
323 let info1 = b"p2p-identity-sync";
324 let info2 = b"different-context";
325
326 let encrypted = encrypt_with_shared_secret(data, &shared_secret, info1)
328 .expect("Encryption should succeed");
329
330 let result = decrypt_with_shared_secret(&encrypted, &shared_secret, info2);
332
333 assert!(result.is_err());
334 }
335
336 #[test]
337 fn test_encryption_produces_unique_nonces() {
338 let data = b"Test data";
339 let password = "TestPassword123!";
340
341 let encrypted1 =
343 encrypt_with_device_password(data, password).expect("First encryption should succeed");
344 let encrypted2 =
345 encrypt_with_device_password(data, password).expect("Second encryption should succeed");
346
347 assert_ne!(encrypted1.salt, encrypted2.salt);
349 assert_ne!(
351 encrypted1.encrypted_message.ciphertext,
352 encrypted2.encrypted_message.ciphertext
353 );
354 }
355}