aws_sdk_manager/kms/
envelope.rs

1use std::{
2    fs::{self, File},
3    io::{Cursor, Read, Write},
4    sync::Arc,
5};
6
7use crate::{
8    errors::{Error::Other, Result},
9    kms,
10};
11use aws_sdk_kms::model::{DataKeySpec, EncryptionAlgorithmSpec};
12use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
13use compress_manager::{self, Decoder, Encoder};
14use log::info;
15/// "NONCE_LEN" is the per-record nonce (iv_length), 12-byte
16/// ref. https://www.rfc-editor.org/rfc/rfc8446#appendix-E.2
17use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM, NONCE_LEN};
18use ring::rand::{SecureRandom, SystemRandom};
19
20const DEK_AES_256_LENGTH: usize = 32;
21
22/// Implements envelope encryption manager.
23#[derive(std::clone::Clone)]
24pub struct Envelope {
25    pub kms_manager: kms::Manager,
26    pub kms_key_id: String,
27
28    /// Represents additional authenticated data (AAD) that attaches information
29    /// to the ciphertext that is not encrypted.
30    pub aad_tag: String,
31}
32
33impl Envelope {
34    /// Envelope-encrypts the data using AWS KMS data-encryption key (DEK)
35    /// and "AES_256_GCM", since kms:Encrypt can only encrypt 4 KiB).
36    /// The encrypted data are aligned as below:
37    /// [ Nonce bytes "length" ][ DEK.ciphertext "length" ][ Nonce bytes ][ DEK.ciphertext ][ data ciphertext ]
38    pub async fn seal_aes_256(&self, d: &[u8]) -> Result<Vec<u8>> {
39        info!(
40            "AES_256 envelope-encrypting data (size before encryption {})",
41            human_readable::bytes(d.len() as f64)
42        );
43
44        let dek = self
45            .kms_manager
46            .generate_data_key(&self.kms_key_id, Some(DataKeySpec::Aes256))
47            .await?;
48        if dek.plaintext.len() != DEK_AES_256_LENGTH {
49            return Err(Other {
50                message: format!(
51                    "DEK.plaintext for AES_256 must be {}-byte, got {}-byte",
52                    DEK_AES_256_LENGTH,
53                    dek.plaintext.len()
54                ),
55                is_retryable: false,
56            });
57        }
58
59        let random = SystemRandom::new();
60        let mut nonce_bytes = [0u8; NONCE_LEN];
61        match random.fill(&mut nonce_bytes) {
62            Ok(_) => {}
63            Err(e) => {
64                return Err(Other {
65                    message: format!("failed to generate ring.random for nonce ({:?})", e),
66                    is_retryable: false,
67                });
68            }
69        }
70        let unbound_key = match UnboundKey::new(&AES_256_GCM, &dek.plaintext) {
71            Ok(v) => v,
72            Err(e) => {
73                return Err(Other {
74                    message: format!("failed to create UnboundKey ({:?})", e),
75                    is_retryable: false,
76                });
77            }
78        };
79        let safe_key = LessSafeKey::new(unbound_key);
80
81        // overwrites the original array
82        let mut cipher = d.to_vec();
83        match safe_key.seal_in_place_append_tag(
84            Nonce::assume_unique_for_key(nonce_bytes),
85            Aad::from(self.aad_tag.clone()),
86            &mut cipher,
87        ) {
88            Ok(_) => {}
89            Err(e) => {
90                return Err(Other {
91                    message: format!("failed to seal ({:?})", e),
92                    is_retryable: false,
93                });
94            }
95        }
96
97        // align bytes in the order of
98        // - Nonce bytes "length"
99        // - DEK.ciphertext "length"
100        // - Nonce bytes
101        // - DEK.ciphertext
102        // - data ciphertext
103        let mut encrypted = Vec::new();
104
105        // Nonce bytes "length"
106        match encrypted.write_u16::<LittleEndian>(NONCE_LEN as u16) {
107            Ok(_) => {}
108            Err(e) => {
109                return Err(Other {
110                    message: format!("failed to write ({:?})", e),
111                    is_retryable: false,
112                });
113            }
114        }
115
116        // DEK.ciphertext "length"
117        match encrypted.write_u16::<LittleEndian>(dek.ciphertext.len() as u16) {
118            Ok(_) => {}
119            Err(e) => {
120                return Err(Other {
121                    message: format!("failed to write ({:?})", e),
122                    is_retryable: false,
123                });
124            }
125        }
126
127        // Nonce bytes
128        match encrypted.write_all(&nonce_bytes) {
129            Ok(_) => {}
130            Err(e) => {
131                return Err(Other {
132                    message: format!("failed to write ({:?})", e),
133                    is_retryable: false,
134                });
135            }
136        }
137
138        // DEK.ciphertext
139        match encrypted.write_all(&dek.ciphertext) {
140            Ok(_) => {}
141            Err(e) => {
142                return Err(Other {
143                    message: format!("failed to write ({:?})", e),
144                    is_retryable: false,
145                });
146            }
147        }
148
149        // data ciphertext
150        match encrypted.write_all(&cipher) {
151            Ok(_) => {}
152            Err(e) => {
153                return Err(Other {
154                    message: format!("failed to write ({:?})", e),
155                    is_retryable: false,
156                });
157            }
158        }
159
160        info!(
161            "AES_256 envelope-encrypted data (encrypted size {})",
162            human_readable::bytes(encrypted.len() as f64)
163        );
164        Ok(encrypted)
165    }
166
167    /// Envelope-decrypts using KMS DEK and "AES_256_GCM".
168    /// Assume the input (ciphertext) data are packed in the order of:
169    /// [ Nonce bytes "length" ][ DEK.ciphertext "length" ][ Nonce bytes ][ DEK.ciphertext ][ data ciphertext ]
170    pub async fn unseal_aes_256(&self, d: &[u8]) -> Result<Vec<u8>> {
171        info!(
172            "AES_256 envelope-decrypting data (size before decryption {})",
173            human_readable::bytes(d.len() as f64)
174        );
175
176        // bytes are packed in the order of
177        // - Nonce bytes "length"
178        // - DEK.ciphertext "length"
179        // - Nonce bytes
180        // - DEK.ciphertext
181        // - data ciphertext
182        let mut buf = Cursor::new(d);
183
184        let nonce_len = match buf.read_u16::<LittleEndian>() {
185            Ok(v) => v as usize,
186            Err(e) => {
187                return Err(Other {
188                    message: format!("failed to read_u16 for nonce_len ({:?})", e),
189                    is_retryable: false,
190                });
191            }
192        };
193        if nonce_len != NONCE_LEN {
194            return Err(Other {
195                message: format!("nonce_len {} != NONCE_LEN {}", nonce_len, NONCE_LEN),
196                is_retryable: false,
197            });
198        }
199
200        let dek_ciphertext_len = match buf.read_u16::<LittleEndian>() {
201            Ok(v) => v as usize,
202            Err(e) => {
203                return Err(Other {
204                    message: format!("failed to read_u16 for dek_ciphertext_len ({:?})", e),
205                    is_retryable: false,
206                });
207            }
208        };
209        if dek_ciphertext_len > d.len() {
210            return Err(Other {
211                message: format!(
212                    "invalid DEK ciphertext len {} > cipher.len {}",
213                    dek_ciphertext_len,
214                    d.len()
215                ),
216                is_retryable: false,
217            });
218        }
219
220        let mut nonce_bytes = [0u8; NONCE_LEN];
221        match buf.read_exact(&mut nonce_bytes) {
222            Ok(_) => {}
223            Err(e) => {
224                return Err(Other {
225                    message: format!("failed to read_exact for nonce_bytes ({:?})", e),
226                    is_retryable: false,
227                });
228            }
229        };
230        let nonce = Nonce::assume_unique_for_key(nonce_bytes);
231
232        let mut dek_ciphertext = zero_vec(dek_ciphertext_len);
233        match buf.read_exact(&mut dek_ciphertext) {
234            Ok(_) => {}
235            Err(e) => {
236                return Err(Other {
237                    message: format!("failed to read_exact for DEK.ciphertext ({:?})", e),
238                    is_retryable: false,
239                });
240            }
241        };
242        // use the default "SYMMETRIC_DEFAULT"
243        let dek_plain = self
244            .kms_manager
245            .decrypt(
246                &self.kms_key_id,
247                Some(EncryptionAlgorithmSpec::SymmetricDefault),
248                dek_ciphertext,
249            )
250            .await?;
251        let unbound_key = match UnboundKey::new(&AES_256_GCM, &dek_plain) {
252            Ok(v) => v,
253            Err(e) => {
254                return Err(Other {
255                    message: format!("failed to create UnboundKey ({:?})", e),
256                    is_retryable: false,
257                });
258            }
259        };
260        let safe_key = LessSafeKey::new(unbound_key);
261
262        let mut cipher = Vec::new();
263        match buf.read_to_end(&mut cipher) {
264            Ok(_) => {}
265            Err(e) => {
266                return Err(Other {
267                    message: format!("failed to read_to_end for ciphertext ({:?})", e),
268                    is_retryable: false,
269                });
270            }
271        };
272
273        let decrypted =
274            match safe_key.open_in_place(nonce, Aad::from(self.aad_tag.clone()), &mut cipher) {
275                Ok(plaintext) => plaintext.to_vec(),
276                Err(e) => {
277                    return Err(Other {
278                        message: format!("failed to open_in_place ciphertext ({:?})", e),
279                        is_retryable: false,
280                    });
281                }
282            };
283
284        info!(
285            "AES_256 envelope-decrypted data (decrypted size {})",
286            human_readable::bytes(decrypted.len() as f64)
287        );
288        Ok(decrypted)
289    }
290
291    /// Envelope-encrypts data from a file and save the ciphertext to the other file.
292    ///
293    /// "If a single piece of data must be accessible from more than one task
294    /// concurrently, then it must be shared using synchronization primitives such as Arc."
295    /// ref. https://tokio.rs/tokio/tutorial/spawning
296    pub async fn seal_aes_256_file(
297        &self,
298        src_file: Arc<String>,
299        dst_file: Arc<String>,
300    ) -> Result<()> {
301        info!("envelope-encrypting file {} to {}", src_file, dst_file);
302        let d = match fs::read(src_file.to_string()) {
303            Ok(d) => d,
304            Err(e) => {
305                return Err(Other {
306                    message: format!("failed read {:?}", e),
307                    is_retryable: false,
308                });
309            }
310        };
311
312        let ciphertext = match self.seal_aes_256(&d).await {
313            Ok(d) => d,
314            Err(e) => {
315                return Err(e);
316            }
317        };
318
319        let mut f = match File::create(dst_file.to_string()) {
320            Ok(f) => f,
321            Err(e) => {
322                return Err(Other {
323                    message: format!("failed File::create {:?}", e),
324                    is_retryable: false,
325                });
326            }
327        };
328        match f.write_all(&ciphertext) {
329            Ok(_) => {}
330            Err(e) => {
331                return Err(Other {
332                    message: format!("failed File::write_all {:?}", e),
333                    is_retryable: false,
334                });
335            }
336        };
337
338        Ok(())
339    }
340
341    /// Envelope-decrypts data from a file and save the plaintext to the other file.
342    pub async fn unseal_aes_256_file(
343        &self,
344        src_file: Arc<String>,
345        dst_file: Arc<String>,
346    ) -> Result<()> {
347        info!("envelope-decrypting file {} to {}", src_file, dst_file);
348        let d = match fs::read(src_file.to_string()) {
349            Ok(d) => d,
350            Err(e) => {
351                return Err(Other {
352                    message: format!("failed read {:?}", e),
353                    is_retryable: false,
354                });
355            }
356        };
357
358        let plaintext = match self.unseal_aes_256(&d).await {
359            Ok(d) => d,
360            Err(e) => {
361                return Err(e);
362            }
363        };
364
365        let mut f = match File::create(dst_file.to_string()) {
366            Ok(f) => f,
367            Err(e) => {
368                return Err(Other {
369                    message: format!("failed File::create {:?}", e),
370                    is_retryable: false,
371                });
372            }
373        };
374        match f.write_all(&plaintext) {
375            Ok(_) => {}
376            Err(e) => {
377                return Err(Other {
378                    message: format!("failed File::write_all {:?}", e),
379                    is_retryable: false,
380                });
381            }
382        };
383
384        Ok(())
385    }
386
387    /// Compresses the source file ("src_file") and envelope-encrypts to "dst_file".
388    /// The compression uses "zstd".
389    /// The encryption uses AES 256.
390    pub async fn compress_seal(&self, src_file: Arc<String>, dst_file: Arc<String>) -> Result<()> {
391        info!(
392            "compress-seal: compressing the file '{}'",
393            src_file.to_string()
394        );
395        let compressed_path = random_manager::tmp_path(10, None).unwrap();
396        compress_manager::pack_file(&src_file.to_string(), &compressed_path, Encoder::Zstd(3))
397            .map_err(|e| Other {
398                message: format!("failed compression {}", e),
399                is_retryable: false,
400            })?;
401
402        info!(
403            "compress-seal: sealing the compressed file '{}'",
404            compressed_path
405        );
406        self.seal_aes_256_file(Arc::new(compressed_path), dst_file.clone())
407            .await
408    }
409
410    /// Reverse of "compress_seal".
411    /// The decompression uses "zstd".
412    /// The decryption uses AES 256.
413    pub async fn unseal_decompress(
414        &self,
415        src_file: Arc<String>,
416        dst_file: Arc<String>,
417    ) -> Result<()> {
418        info!(
419            "unseal-decompress: unsealing the encrypted file '{}'",
420            src_file.as_ref()
421        );
422        let unsealed_path = random_manager::tmp_path(10, None).unwrap();
423        self.unseal_aes_256_file(src_file.clone(), Arc::new(unsealed_path.clone()))
424            .await?;
425
426        info!(
427            "unseal-decompress: decompressing the file '{}'",
428            src_file.as_ref()
429        );
430        compress_manager::unpack_file(&unsealed_path, dst_file.as_ref(), Decoder::Zstd).map_err(
431            |e| Other {
432                message: format!("failed decompression {}", e),
433                is_retryable: false,
434            },
435        )
436    }
437}
438
439fn zero_vec(n: usize) -> Vec<u8> {
440    (0..n).map(|_| 0).collect()
441}
442
443pub async fn spawn_seal_aes_256_file<S>(envelope: Envelope, src_file: S, dst_file: S) -> Result<()>
444where
445    S: AsRef<str>,
446{
447    let envelope_arc = Arc::new(envelope);
448    let src_file_arc = Arc::new(src_file.as_ref().to_string());
449    let dst_file_arc = Arc::new(dst_file.as_ref().to_string());
450    tokio::spawn(async move {
451        envelope_arc
452            .seal_aes_256_file(src_file_arc, dst_file_arc)
453            .await
454    })
455    .await
456    .expect("failed spawn await")
457}
458
459pub async fn spawn_unseal_aes_256_file<S>(
460    envelope: Envelope,
461    src_file: S,
462    dst_file: S,
463) -> Result<()>
464where
465    S: AsRef<str>,
466{
467    let envelope_arc = Arc::new(envelope);
468    let src_file_arc = Arc::new(src_file.as_ref().to_string());
469    let dst_file_arc = Arc::new(dst_file.as_ref().to_string());
470    tokio::spawn(async move {
471        envelope_arc
472            .unseal_aes_256_file(src_file_arc, dst_file_arc)
473            .await
474    })
475    .await
476    .expect("failed spawn await")
477}
478
479pub async fn spawn_compress_seal<S>(envelope: Envelope, src_file: S, dst_file: S) -> Result<()>
480where
481    S: AsRef<str>,
482{
483    let envelope_arc = Arc::new(envelope);
484    let src_file_arc = Arc::new(src_file.as_ref().to_string());
485    let dst_file_arc = Arc::new(dst_file.as_ref().to_string());
486    tokio::spawn(async move { envelope_arc.compress_seal(src_file_arc, dst_file_arc).await })
487        .await
488        .expect("failed spawn await")
489}
490
491pub async fn spawn_unseal_decompress<S>(envelope: Envelope, src_file: S, dst_file: S) -> Result<()>
492where
493    S: AsRef<str>,
494{
495    let envelope_arc = Arc::new(envelope);
496    let src_file_arc = Arc::new(src_file.as_ref().to_string());
497    let dst_file_arc = Arc::new(dst_file.as_ref().to_string());
498    tokio::spawn(async move {
499        envelope_arc
500            .unseal_decompress(src_file_arc, dst_file_arc)
501            .await
502    })
503    .await
504    .expect("failed spawn await")
505}