1use super::*;
2
3use bincode::{config::standard, encode_to_vec};
4use chacha20poly1305::{
5 AeadCore, KeyInit, XChaCha20Poly1305,
6 aead::{Aead, OsRng, Payload, generic_array::GenericArray, rand_core::RngCore},
7};
8use secure_types::SecureBytes;
9
10pub const HEADER: &[u8; 8] = b"nCrypt1\0";
26
27pub fn encrypt_data(
35 argon2: Argon2,
36 data: SecureBytes,
37 credentials: Credentials,
38) -> Result<Vec<u8>, Error> {
39 let (encrypted_data, info) = encrypt(argon2, credentials, data)?;
40
41 let encoded_info =
42 encode_to_vec(&info, standard()).map_err(|e| Error::EncodingFailed(e.to_string()))?;
43
44 let mut result = Vec::new();
46
47 result.extend_from_slice(HEADER);
49
50 let info_length = encoded_info.len() as u32;
52 result.extend_from_slice(&info_length.to_le_bytes());
53
54 result.extend_from_slice(&encoded_info);
56
57 result.extend_from_slice(&encrypted_data);
59
60 Ok(result)
61}
62
63fn encrypt(
64 argon2: Argon2,
65 credentials: Credentials,
66 data: SecureBytes,
67) -> Result<(Vec<u8>, EncryptedInfo), Error> {
68 credentials.is_valid()?;
69
70 if argon2.hash_length < 32 {
71 return Err(Error::HashLength);
72 }
73
74 let mut password_salt = vec![0u8; RECOMMENDED_SALT_LEN];
75 let mut username_salt = vec![0u8; RECOMMENDED_SALT_LEN];
76
77 OsRng
78 .try_fill_bytes(&mut password_salt)
79 .map_err(|e| Error::Custom(e.to_string()))?;
80 OsRng
81 .try_fill_bytes(&mut username_salt)
82 .map_err(|e| Error::Custom(e.to_string()))?;
83
84 let cipher_nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
85
86 let mut aad = credentials
87 .username
88 .unlock_str(|username_str| argon2.hash_password(&username_str, username_salt.clone()))
89 .map_err(|e| Error::Custom(e.to_string()))?;
90
91 let password_hash = credentials
92 .password
93 .unlock_str(|password_str| argon2.hash_password(&password_str, password_salt.clone()))
94 .map_err(|e| Error::Custom(e.to_string()))?;
95
96 data.unlock_slice(|data| {
97 let payload = Payload {
98 msg: data,
99 aad: &aad,
100 };
101
102 let cipher = xchacha20_poly_1305(password_hash);
103
104 let encrypted_data_res = cipher.encrypt(&cipher_nonce, payload);
105 aad.zeroize();
106
107 let encrypted_data = match encrypted_data_res {
108 Ok(data) => data,
109 Err(e) => {
110 return Err(Error::EncryptionFailed(e.to_string()));
111 }
112 };
113
114 let info = EncryptedInfo::new(
115 password_salt,
116 username_salt,
117 cipher_nonce.to_vec(),
118 argon2,
119 );
120
121 Ok((encrypted_data, info))
122 })
123}
124
125pub(crate) fn xchacha20_poly_1305(mut hash_output: Vec<u8>) -> XChaCha20Poly1305 {
126 let mut key = GenericArray::clone_from_slice(&hash_output[..32]);
127 hash_output.zeroize();
128
129 let cipher = XChaCha20Poly1305::new(&key);
130 key.zeroize();
131 cipher
132}