Skip to content

Commit 56b4de1

Browse files
committed
refactor(cipher): Improve security of the cipher module by encapsulating the decrypted string within the expose method. Implement serde's deserialization trait to make sure data is encrypted as soon as it is received
1 parent 096b9d0 commit 56b4de1

File tree

8 files changed

+258
-203
lines changed

8 files changed

+258
-203
lines changed

src/cipher.rs

Lines changed: 137 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ static CIPHER: LazyLock<Aes256Gcm> = LazyLock::new(|| {
1414
});
1515

1616
/// Represents encrypted data with the encrypted string and the nonce used for encryption.
17-
pub(crate) struct Encrypted {
17+
#[derive(Clone)]
18+
pub(crate) struct EncryptedString {
19+
/// The cached decrypted data as a UTF-8 string.
20+
/// This field is only set if exposed using the `expose` method.
21+
decrypted: Option<String>,
1822
/// The encrypted data as a base64 encoded string.
19-
pub(crate) data: String,
23+
pub(crate) encrypted: String,
2024
/// The nonce used for encryption as a base64 encoded string.
2125
pub(crate) nonce: String,
2226
}
@@ -49,64 +53,106 @@ fn get_encryption_key() -> Key<Aes256Gcm> {
4953
*Key::<Aes256Gcm>::from_slice(key.as_bytes())
5054
}
5155

52-
/// Encrypts the given data using AES-256-GCM.
53-
///
54-
/// This function encrypts the provided data using the AES-256-GCM algorithm and a randomly generated nonce.
55-
/// The encrypted data and nonce are returned as base64 encoded strings.
56-
///
57-
/// # Arguments
58-
///
59-
/// * `data` - A byte slice of the data to be encrypted.
60-
///
61-
/// # Returns
62-
///
63-
/// A [`Result`] containing an [`Encrypted`] struct on success, or a [`String`] error message on failure.
64-
pub(crate) fn encrypt(data: &[u8]) -> Result<Encrypted, String> {
65-
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
66-
let encrypted_bytes = CIPHER
67-
.encrypt(&nonce, data)
68-
.map_err(|e| format!("Failed to encrypt data: {}", e))?;
69-
70-
Ok(Encrypted {
71-
data: base64::encode(&encrypted_bytes),
72-
nonce: base64::encode(nonce.as_slice()),
73-
})
56+
impl EncryptedString {
57+
/// Creates a new [`EncryptedString`] struct with the provided data and nonce.
58+
pub(crate) fn new<S>(data: S, nonce: S) -> Self
59+
where
60+
S: Into<String>,
61+
{
62+
EncryptedString {
63+
encrypted: data.into(),
64+
nonce: nonce.into(),
65+
decrypted: None,
66+
}
67+
}
68+
69+
/// Encrypts the given data using AES-256-GCM.
70+
///
71+
/// This function encrypts the provided data using the AES-256-GCM algorithm and a randomly generated nonce.
72+
/// The encrypted data and nonce are returned as base64 encoded strings.
73+
///
74+
/// # Arguments
75+
///
76+
/// * `data` - A byte slice of the data to be encrypted.
77+
///
78+
/// # Returns
79+
///
80+
/// A [`Result`] containing an [`EncryptedString`] struct on success, or a [`String`] error message on failure.
81+
pub(crate) fn encrypt(data: &[u8]) -> Result<Self, String> {
82+
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
83+
let encrypted_bytes = CIPHER
84+
.encrypt(&nonce, data)
85+
.map_err(|e| format!("Failed to encrypt data: {}", e))?;
86+
87+
Ok(EncryptedString::new(
88+
base64::encode(&encrypted_bytes),
89+
base64::encode(nonce.as_slice()),
90+
))
91+
}
92+
93+
/// Decrypts the given encrypted data using AES-256-GCM.
94+
///
95+
/// This function decrypts the provided [`EncryptedString`] using the AES-256-GCM algorithm and the nonce.
96+
/// The decrypted data is returned as a UTF-8 string.
97+
///
98+
/// # Arguments
99+
///
100+
/// * `data` - A reference to the [`EncryptedString`] struct containing the encrypted data and nonce.
101+
///
102+
/// # Returns
103+
///
104+
/// A [`Result`] containing the decrypted data as a [`String`] on success, or a [`String`] error message on failure.
105+
fn decrypt(&self) -> Result<String, String> {
106+
// Decode the base64 encoded data and nonce
107+
let encrypted_bytes = base64::decode(&self.encrypted)
108+
.map_err(|e| format!("Failed to decode base64 data: {}", e))?;
109+
let nonce = base64::decode(&self.nonce)
110+
.map_err(|e| format!("Failed to decode base64 nonce: {}", e))?;
111+
let nonce = Nonce::<Aes256Gcm>::from_slice(nonce.as_slice());
112+
113+
// Decrypt the data and convert it to a UTF-8 string
114+
let decrypted_bytes = CIPHER
115+
.decrypt(nonce, encrypted_bytes.as_slice())
116+
.map_err(|e| format!("Failed to decrypt data: {}", e))?;
117+
let decrypted_data = String::from_utf8(decrypted_bytes)
118+
.map_err(|e| format!("Failed to convert decrypted data to UTF-8: {}", e))?;
119+
120+
Ok(decrypted_data)
121+
}
122+
123+
/// Exposes the decrypted data to the provided function.
124+
/// This method should only be used if the program intends to use the decrypted data **once**.
125+
pub(crate) fn expose_once<T>(&self, f: impl FnOnce(&str) -> T) -> Result<T, String> {
126+
let decrypted_data = self.decrypt()?;
127+
Ok(f(&decrypted_data))
128+
}
129+
130+
/// Exposes the decrypted data to the provided function.
131+
/// This method should only be used if the program intends to use the decrypted data **multiple times**.
132+
pub(crate) fn expose<T>(&mut self, f: impl FnOnce(&str) -> T) -> Result<T, String> {
133+
if self.decrypted.is_none() {
134+
let decrypted_data = self.decrypt()?;
135+
self.decrypted = Some(decrypted_data);
136+
}
137+
138+
Ok(f(self.decrypted.as_ref().unwrap()))
139+
}
74140
}
75141

76-
/// Decrypts the given encrypted data using AES-256-GCM.
77-
///
78-
/// This function decrypts the provided [`Encrypted`] using the AES-256-GCM algorithm and the nonce.
79-
/// The decrypted data is returned as a UTF-8 string.
80-
///
81-
/// # Arguments
82-
///
83-
/// * `data` - A reference to the [`Encrypted`] struct containing the encrypted data and nonce.
84-
///
85-
/// # Returns
86-
///
87-
/// A [`Result`] containing the decrypted data as a [`String`] on success, or a [`String`] error message on failure.
88-
pub(crate) fn decrypt(data: &Encrypted) -> Result<String, String> {
89-
// Decode the base64 encoded data and nonce
90-
let encrypted_bytes =
91-
base64::decode(&data.data).map_err(|e| format!("Failed to decode base64 data: {}", e))?;
92-
let nonce =
93-
base64::decode(&data.nonce).map_err(|e| format!("Failed to decode base64 nonce: {}", e))?;
94-
let nonce = Nonce::<Aes256Gcm>::from_slice(nonce.as_slice());
95-
96-
// Decrypt the data and convert it to a UTF-8 string
97-
let decrypted_bytes = CIPHER
98-
.decrypt(nonce, encrypted_bytes.as_slice())
99-
.map_err(|e| format!("Failed to decrypt data: {}", e))?;
100-
let decrypted_data = std::str::from_utf8(&decrypted_bytes)
101-
.map_err(|e| format!("Failed to convert decrypted data to UTF-8: {}", e))?;
102-
103-
Ok(decrypted_data.to_string())
142+
impl<'de> rocket::serde::Deserialize<'de> for EncryptedString {
143+
fn deserialize<D>(deserializer: D) -> Result<EncryptedString, D::Error>
144+
where
145+
D: rocket::serde::Deserializer<'de>,
146+
{
147+
let b = <&[u8]>::deserialize(deserializer)?;
148+
Self::encrypt(b).map_err(rocket::serde::de::Error::custom)
149+
}
104150
}
105151

106152
#[cfg(test)]
107153
#[serial_test::file_serial(env)]
108154
mod tests {
109-
use super::{decrypt, encrypt, get_encryption_key, Encrypted};
155+
use super::{get_encryption_key, EncryptedString};
110156
use crate::constants;
111157

112158
const DATA: &[u8] = b"Hello, world!";
@@ -158,10 +204,10 @@ mod tests {
158204
constants::env::ENCRYPTION_KEY,
159205
constants::test::ENCRYPTION_KEY,
160206
);
161-
let encrypted_data = encrypt(DATA).unwrap();
162-
let decrypted_data = decrypt(&encrypted_data).unwrap();
163207

164-
assert_eq!(decrypted_data.as_bytes(), DATA);
208+
let data = EncryptedString::encrypt(DATA).unwrap().decrypt().unwrap();
209+
assert_eq!(data.as_bytes(), DATA);
210+
165211
std::env::remove_var(constants::env::ENCRYPTION_KEY);
166212
}
167213

@@ -171,13 +217,10 @@ mod tests {
171217
constants::env::ENCRYPTION_KEY,
172218
constants::test::ENCRYPTION_KEY,
173219
);
174-
let encrypted_data = Encrypted {
175-
data: "valid+data".to_string(),
176-
nonce: "valid+nonce".to_string(),
177-
};
178-
let result = decrypt(&encrypted_data);
179220

221+
let result = EncryptedString::new("valid+data", "valid+nonce").decrypt();
180222
assert!(result.is_err());
223+
181224
std::env::remove_var(constants::env::ENCRYPTION_KEY);
182225
}
183226

@@ -188,11 +231,40 @@ mod tests {
188231
constants::test::ENCRYPTION_KEY,
189232
);
190233

191-
let mut encrypted_data = encrypt(DATA).unwrap();
192-
encrypted_data.nonce = "@".to_string();
193-
let result = decrypt(&encrypted_data);
194-
234+
let result = EncryptedString::new("valid", "@").decrypt();
195235
assert!(result.is_err());
236+
237+
std::env::remove_var(constants::env::ENCRYPTION_KEY);
238+
}
239+
240+
#[test]
241+
fn expose_once() {
242+
std::env::set_var(
243+
constants::env::ENCRYPTION_KEY,
244+
constants::test::ENCRYPTION_KEY,
245+
);
246+
247+
let encrypted_data = EncryptedString::encrypt(DATA).unwrap();
248+
let decrypted_data = encrypted_data.expose_once(|data| data.to_owned()).unwrap();
249+
assert_eq!(decrypted_data.as_bytes(), DATA);
250+
251+
std::env::remove_var(constants::env::ENCRYPTION_KEY);
252+
}
253+
254+
#[test]
255+
fn expose() {
256+
std::env::set_var(
257+
constants::env::ENCRYPTION_KEY,
258+
constants::test::ENCRYPTION_KEY,
259+
);
260+
261+
let mut encrypted_data = EncryptedString::encrypt(DATA).unwrap();
262+
assert!(encrypted_data.decrypted.is_none());
263+
264+
let decrypted_data = encrypted_data.expose(|data| data.to_owned()).unwrap();
265+
assert_eq!(decrypted_data.as_bytes(), DATA);
266+
assert_eq!(encrypted_data.decrypted.unwrap().as_bytes(), DATA);
267+
196268
std::env::remove_var(constants::env::ENCRYPTION_KEY);
197269
}
198270
}

0 commit comments

Comments
 (0)