devolutions_crypto/derive_encrypt/
mod.rs1mod kdf_encrypted_data_v1;
2
3use std::borrow::Borrow;
4use std::convert::TryFrom;
5
6use crate::ciphertext;
7use crate::enums::KdfEncryptedDataSubtype;
8use crate::key_derivation::DerivationParameters;
9use crate::{
10 CiphertextVersion, DataType, Error, Header, HeaderType, KdfEncryptedDataVersion, Result,
11};
12
13use kdf_encrypted_data_v1::KdfEncryptedDataV1;
14
15#[cfg(feature = "fuzz")]
16use arbitrary::Arbitrary;
17
18#[derive(Clone, Debug)]
25#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
26pub struct KdfEncryptedData {
27 pub(crate) header: Header<KdfEncryptedData>,
28 payload: KdfEncryptedDataPayload,
29}
30
31impl HeaderType for KdfEncryptedData {
32 type Version = KdfEncryptedDataVersion;
33 type Subtype = KdfEncryptedDataSubtype;
34
35 fn data_type() -> DataType {
36 DataType::KdfEncryptedData
37 }
38}
39
40#[derive(Clone, Debug)]
41#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
42enum KdfEncryptedDataPayload {
43 V1(KdfEncryptedDataV1),
44}
45
46pub fn encrypt_with_password(
69 data: &[u8],
70 password: &[u8],
71 derivation_parameters: DerivationParameters,
72 ciphertext_version: CiphertextVersion,
73) -> Result<KdfEncryptedData> {
74 encrypt_with_password_and_aad(
75 data,
76 password,
77 [].as_slice(),
78 derivation_parameters,
79 ciphertext_version,
80 )
81}
82
83pub fn encrypt_with_password_and_aad(
95 data: &[u8],
96 password: &[u8],
97 aad: &[u8],
98 derivation_parameters: DerivationParameters,
99 ciphertext_version: CiphertextVersion,
100) -> Result<KdfEncryptedData> {
101 let mut header: Header<KdfEncryptedData> = Header::default();
102 header.version = KdfEncryptedDataVersion::V1;
103
104 let secret_key = derivation_parameters.derive(password)?;
105 let ciphertext =
106 ciphertext::encrypt_with_secret_key_and_aad(data, &secret_key, aad, ciphertext_version)?;
107
108 Ok(KdfEncryptedData {
109 header,
110 payload: KdfEncryptedDataPayload::V1(KdfEncryptedDataV1 {
111 derivation_parameters,
112 ciphertext,
113 }),
114 })
115}
116
117impl KdfEncryptedData {
118 pub fn decrypt_with_password(&self, password: &[u8]) -> Result<Vec<u8>> {
127 self.decrypt_with_password_and_aad(password, [].as_slice())
128 }
129
130 pub fn decrypt_with_password_and_aad(&self, password: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
139 match &self.payload {
140 KdfEncryptedDataPayload::V1(payload) => {
141 let secret_key = payload.derivation_parameters.derive(password)?;
142 payload
143 .ciphertext
144 .decrypt_with_secret_key_and_aad(&secret_key, aad)
145 }
146 }
147 }
148}
149
150impl From<KdfEncryptedData> for Vec<u8> {
151 fn from(data: KdfEncryptedData) -> Self {
152 let mut header: Self = data.header.borrow().into();
153 let mut payload: Self = data.payload.into();
154 header.append(&mut payload);
155 header
156 }
157}
158
159impl From<KdfEncryptedDataPayload> for Vec<u8> {
160 fn from(data: KdfEncryptedDataPayload) -> Self {
161 match data {
162 KdfEncryptedDataPayload::V1(v1) => v1.into(),
163 }
164 }
165}
166
167impl TryFrom<&[u8]> for KdfEncryptedData {
168 type Error = Error;
169
170 fn try_from(data: &[u8]) -> Result<Self> {
171 if data.len() < Header::len() {
172 return Err(Error::InvalidLength);
173 }
174
175 let header = Header::try_from(&data[0..Header::len()])?;
176
177 let payload = match header.version {
178 KdfEncryptedDataVersion::V1 => {
179 KdfEncryptedDataPayload::V1(KdfEncryptedDataV1::try_from(&data[Header::len()..])?)
180 }
181 KdfEncryptedDataVersion::Latest => return Err(Error::UnknownVersion),
184 };
185
186 Ok(Self { header, payload })
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use crate::key_derivation::Argon2;
193 use crate::utils::validate_header;
194 use crate::Pbkdf2;
195
196 use super::*;
197
198 #[test]
199 fn derive_encrypt_roundtrip_latest() {
200 let data = b"derive encrypt payload";
201 let password = b"a very strong password";
202 let aad = b"public data";
203
204 let params = Argon2::new().parameters();
205 let wrapped =
206 encrypt_with_password_and_aad(data, password, aad, params, CiphertextVersion::Latest)
207 .unwrap();
208
209 let wrapped_raw: Vec<u8> = wrapped.into();
210 let wrapped = KdfEncryptedData::try_from(wrapped_raw.as_slice()).unwrap();
211 let decrypted = wrapped
212 .decrypt_with_password_and_aad(password, aad)
213 .unwrap();
214
215 assert_eq!(decrypted, data);
216 }
217
218 #[test]
219 fn derive_encrypt_pbkdf2() {
220 let data = b"derive encrypt payload";
221 let password = b"a very strong password";
222
223 let params = Pbkdf2::with_params(10).parameters().unwrap();
224 let wrapped =
225 encrypt_with_password(data, password, params, CiphertextVersion::Latest).unwrap();
226
227 assert!(wrapped.decrypt_with_password(b"wrong password").is_err());
228 }
229
230 #[test]
231 fn derive_encrypt_wrong_password_fails() {
232 let data = b"derive encrypt payload";
233 let password = b"a very strong password";
234
235 let params = Argon2::new().parameters();
236 let wrapped =
237 encrypt_with_password(data, password, params, CiphertextVersion::Latest).unwrap();
238
239 assert!(wrapped.decrypt_with_password(b"wrong password").is_err());
240 }
241
242 #[test]
243 fn derive_encrypt_wrong_aad_fails() {
244 let data = b"derive encrypt payload";
245 let password = b"a very strong password";
246
247 let params = Argon2::new().parameters();
248 let wrapped = encrypt_with_password_and_aad(
249 data,
250 password,
251 b"the right aad",
252 params,
253 CiphertextVersion::Latest,
254 )
255 .unwrap();
256
257 assert!(wrapped
258 .decrypt_with_password_and_aad(password, b"wrong aad")
259 .is_err());
260 }
261
262 #[test]
263 fn validate_header_accepts_derive_encrypt() {
264 let password = b"a very strong password";
265 let params = Argon2::new().parameters();
266 let wrapped = encrypt_with_password(
267 b"derive encrypt payload",
268 password,
269 params,
270 CiphertextVersion::Latest,
271 )
272 .unwrap();
273
274 let wrapped_raw: Vec<u8> = wrapped.into();
275 assert!(validate_header(
276 wrapped_raw.as_slice(),
277 DataType::KdfEncryptedData
278 ));
279 assert!(!validate_header(
280 wrapped_raw.as_slice(),
281 DataType::Ciphertext
282 ));
283 }
284}