devolutions_crypto/online_ciphertext/
mod.rs

1//! Module for symmetric/asymmetric encryption/decryption.
2//!
3//! This module contains everything related to encryption. You can use it to encrypt and decrypt data using either a shared key of a keypair.
4//! Either way, the encryption will give you a `Ciphertext`, which has a method to decrypt it.
5//!
6//! ### Symmetric
7//!
8//! ```rust
9//! use devolutions_crypto::utils::generate_key;
10//! use devolutions_crypto::ciphertext::{ encrypt, CiphertextVersion, Ciphertext };
11//!
12//! let key: Vec<u8> = generate_key(32);
13//!
14//! let data = b"somesecretdata";
15//!
16//! let encrypted_data: Ciphertext = encrypt(data, &key, CiphertextVersion::Latest).expect("encryption shouldn't fail");
17//!
18//! let decrypted_data = encrypted_data.decrypt(&key).expect("The decryption shouldn't fail");
19//!
20//! assert_eq!(decrypted_data, data);
21//! ```
22//!
23//! ### Asymmetric
24//! Here, you will need a `PublicKey` to encrypt data and the corresponding
25//! `PrivateKey` to decrypt it. You can generate them by using `generate_keypair`
26//! in the [Key module](#key).
27//!
28//! ```rust
29//! use devolutions_crypto::key::{generate_keypair, KeyVersion, KeyPair};
30//! use devolutions_crypto::ciphertext::{ encrypt_asymmetric, CiphertextVersion, Ciphertext };
31//!
32//! let keypair: KeyPair = generate_keypair(KeyVersion::Latest);
33//!
34//! let data = b"somesecretdata";
35//!
36//! let encrypted_data: Ciphertext = encrypt_asymmetric(data, &keypair.public_key, CiphertextVersion::Latest).expect("encryption shouldn't fail");
37//!
38//! let decrypted_data = encrypted_data.decrypt_asymmetric(&keypair.private_key).expect("The decryption shouldn't fail");
39//!
40//! assert_eq!(decrypted_data, data);
41//! ```
42
43mod online_ciphertext_v1;
44
45use std::borrow::Borrow;
46
47use super::CiphertextSubtype;
48use super::DataType;
49use super::Error;
50use super::Header;
51use super::HeaderType;
52pub use super::OnlineCiphertextVersion;
53use super::Result;
54
55use super::key::{PrivateKey, PublicKey};
56
57use online_ciphertext_v1::{OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor};
58use online_ciphertext_v1::{
59    OnlineCiphertextV1Header, OnlineCiphertextV1HeaderAsymmetric, OnlineCiphertextV1HeaderSymmetric,
60};
61
62use paste::paste;
63
64impl OnlineCiphertextHeader {
65    pub fn into_decryptor(self, key: &[u8], aad: &[u8]) -> Result<OnlineCiphertextDecryptor> {
66        let mut full_aad: Vec<u8> = self.header.borrow().into();
67        full_aad.extend_from_slice(aad);
68
69        match self.payload {
70            OnlineCiphertextHeaderPayload::V1(header) => match header {
71                OnlineCiphertextV1Header::Symmetric(header) => {
72                    let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header);
73
74                    Ok(OnlineCiphertextDecryptor::V1(cipher))
75                }
76                _ => Err(Error::InvalidDataType),
77            },
78        }
79    }
80
81    pub fn into_decryptor_asymmetric(
82        self,
83        key: &PrivateKey,
84        aad: &[u8],
85    ) -> Result<OnlineCiphertextDecryptor> {
86        let mut full_aad: Vec<u8> = self.header.borrow().into();
87        full_aad.extend_from_slice(aad);
88
89        match self.payload {
90            OnlineCiphertextHeaderPayload::V1(header) => match header {
91                OnlineCiphertextV1Header::Asymmetric(header) => {
92                    let cipher = OnlineCiphertextV1Decryptor::new_asymmetric(key, full_aad, header);
93
94                    Ok(OnlineCiphertextDecryptor::V1(cipher))
95                }
96                _ => Err(Error::InvalidDataType),
97            },
98        }
99    }
100
101    pub fn get_serialized_size(&self) -> usize {
102        self.payload.get_serialized_size() + 8
103    }
104
105    pub fn get_chunk_size(&self) -> u32 {
106        self.payload.get_chunk_size()
107    }
108}
109
110impl OnlineCiphertextEncryptor {
111    pub fn new(
112        key: &[u8],
113        aad: &[u8],
114        chunk_size: u32,
115        version: OnlineCiphertextVersion,
116    ) -> OnlineCiphertextEncryptor {
117        let mut header = Header::<OnlineCiphertextHeader> {
118            data_subtype: CiphertextSubtype::Symmetric,
119            ..Default::default()
120        };
121
122        let mut full_aad: Vec<u8> = header.borrow().into();
123        full_aad.extend_from_slice(aad);
124
125        match version {
126            OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => {
127                header.version = OnlineCiphertextVersion::V1;
128
129                let cipher = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size);
130
131                OnlineCiphertextEncryptor::V1(cipher)
132            }
133        }
134    }
135
136    pub fn new_asymmetric(
137        public_key: &PublicKey,
138        aad: &[u8],
139        chunk_size: u32,
140        version: OnlineCiphertextVersion,
141    ) -> OnlineCiphertextEncryptor {
142        let mut header = Header::<OnlineCiphertextHeader> {
143            data_subtype: CiphertextSubtype::Asymmetric,
144            ..Default::default()
145        };
146
147        let mut full_aad: Vec<u8> = header.borrow().into();
148        full_aad.extend_from_slice(aad);
149
150        match version {
151            OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => {
152                header.version = OnlineCiphertextVersion::V1;
153
154                let cipher =
155                    OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size);
156
157                OnlineCiphertextEncryptor::V1(cipher)
158            }
159        }
160    }
161}
162
163macro_rules! online_ciphertext_header_impl {
164    ($($version_name:ident),+) => {
165        paste! {
166            /// A versionned online ciphertext.
167            #[derive(Clone, Debug)]
168            pub struct OnlineCiphertextHeader {
169                pub(crate) header: Header<OnlineCiphertextHeader>,
170                payload: OnlineCiphertextHeaderPayload,
171            }
172
173            impl HeaderType for OnlineCiphertextHeader {
174                type Version = OnlineCiphertextVersion;
175                type Subtype = CiphertextSubtype;
176
177                fn data_type() -> DataType {
178                    DataType::OnlineCiphertext
179                }
180            }
181
182            #[derive(Clone, Debug)]
183            enum OnlineCiphertextHeaderPayload {
184                $(
185                    $version_name([<OnlineCiphertext $version_name Header>]),
186                ),+
187            }
188
189            impl OnlineCiphertextHeaderPayload {
190                pub fn get_serialized_size(&self) -> usize {
191                    match &self {
192                        $(
193                            Self::$version_name(p) => p.get_serialized_size(),
194                        ),+
195                    }
196                }
197
198                pub fn get_chunk_size(&self) -> u32 {
199                    match &self {
200                        $(
201                            Self::$version_name(p) => p.get_chunk_size(),
202                        ),+
203                    }
204                }
205            }
206
207            impl From<&OnlineCiphertextHeader> for Vec<u8> {
208                fn from(value: &OnlineCiphertextHeader) -> Self {
209                    let mut output: Vec<u8> = value.header.borrow().into();
210                    let mut payload: Vec<u8> = match &value.payload {
211                        $(
212                            OnlineCiphertextHeaderPayload::$version_name(p) => {
213                                p.into()
214                            },
215                        ),+
216                    };
217
218                    output.append(&mut payload);
219                    output
220                }
221            }
222
223            impl TryFrom<&[u8]> for OnlineCiphertextHeader {
224                type Error = Error;
225
226                fn try_from(value: &[u8]) -> Result<Self> {
227                    let header = Header::<OnlineCiphertextHeader>::try_from(&value[..8])?;
228
229                    let version = if header.version == OnlineCiphertextVersion::Latest { OnlineCiphertextVersion::V1 } else { header.version };
230                    match version {
231                        $(
232                            OnlineCiphertextVersion::$version_name => {
233                                match header.data_subtype {
234                                    CiphertextSubtype::Symmetric => {
235                                        Ok(Self {
236                                            header,
237                                            payload:
238                                                OnlineCiphertextHeaderPayload::$version_name
239                                                    ([<OnlineCiphertext $version_name Header>]::Symmetric
240                                                        ([<OnlineCiphertext $version_name HeaderSymmetric>]::try_from(&value[8..])?))
241                                        })
242                                    }
243                                    CiphertextSubtype::Asymmetric => {
244                                        Ok(Self {
245                                            header,
246                                            payload:
247                                                OnlineCiphertextHeaderPayload::$version_name
248                                                    ([<OnlineCiphertext $version_name Header>]::Asymmetric
249                                                        ([<OnlineCiphertext $version_name HeaderAsymmetric>]::try_from(&value[8..])?))
250                                        })
251                                    }
252                                    CiphertextSubtype::None => Err(Error::UnknownSubtype)
253                                }
254                            }
255                        ),+
256                        OnlineCiphertextVersion::Latest => unreachable!("Latest is checked before the match arm")
257                    }
258                }
259            }
260        }
261    };
262}
263
264macro_rules! online_ciphertext_impl {
265    ($name:ident, $func:ident, $($version_name:ident),+) => {
266        paste! {
267            pub enum [<OnlineCiphertext $name>] {
268            $(
269                $version_name([<OnlineCiphertext $version_name $name>]),
270            ),+
271            }
272
273            impl [<OnlineCiphertext $name>] {
274                pub fn get_chunk_size(&self) -> u32 {
275                    match &self {
276                    $(
277                        [<OnlineCiphertext $name>]::$version_name(cipher) => {
278                            cipher.get_chunk_size()
279                        }
280                    ),+
281                    }
282                }
283
284                pub fn get_tag_size(&self) -> usize {
285                    match &self {
286                    $(
287                        [<OnlineCiphertext $name>]::$version_name(cipher) => {
288                            cipher.get_tag_size()
289                        }
290                    ),+
291                    }
292                }
293
294                pub fn get_header(&self) -> OnlineCiphertextHeader {
295                    match self {
296                    $(
297                        Self::$version_name(encryptor) => {
298                            let data_subtype = encryptor.get_header().get_subtype();
299                            OnlineCiphertextHeader {
300                                header: Header::<OnlineCiphertextHeader> {
301                                    data_subtype,
302                                    ..Default::default()
303                                },
304                                payload: OnlineCiphertextHeaderPayload::$version_name(encryptor.get_header().clone()),
305                            }
306                        }
307                    ),+
308                    }
309                }
310
311                pub fn [<$func _next_chunk>](
312                    &mut self,
313                    data: &[u8],
314                    aad: &[u8],
315                ) -> Result<Vec<u8>> {
316                    match self {
317                    $(
318                        [<OnlineCiphertext $name>]::$version_name(cipher) => {
319                            cipher.[<$func _next_chunk>](data, aad)
320                        }
321                    ),+
322                    }
323                }
324
325                pub fn [<$func _next_chunk_in_place>](
326                    &mut self,
327                    data: &mut Vec<u8>,
328                    aad: &[u8],
329                ) -> Result<()> {
330                    match self {
331                    $(
332                        [<OnlineCiphertext $name>]::$version_name(cipher) => {
333                            cipher.[<$func _next_chunk_in_place>](data, aad)
334                        }
335                    ),+
336                    }
337                }
338
339                pub fn [<$func _last_chunk>](
340                    self,
341                    data: &[u8],
342                    aad: &[u8],
343                ) -> Result<Vec<u8>> {
344                    match self {
345                    $(
346                        [<OnlineCiphertext $name>]::$version_name(cipher) => {
347                            cipher.[<$func _last_chunk>](data, aad)
348                        }
349                    ),+
350                    }
351                }
352
353                pub fn [<$func _last_chunk_in_place>](
354                    self,
355                    data: &mut Vec<u8>,
356                    aad: &[u8],
357                ) -> Result<()> {
358                    match self {
359                    $(
360                        [<OnlineCiphertext $name>]::$version_name(cipher) => {
361                            cipher.[<$func _last_chunk_in_place>](data, aad)
362                        }
363                    ),+
364                    }
365                }
366            }
367        }
368    };
369}
370
371online_ciphertext_impl!(Encryptor, encrypt, V1);
372online_ciphertext_impl!(Decryptor, decrypt, V1);
373
374online_ciphertext_header_impl!(V1);