aws_sdk_manager/kms/
mod.rs

1pub mod envelope;
2
3use std::{
4    fs::{self, File},
5    io::Write,
6    string::String,
7};
8
9use aws_sdk_kms::{
10    error::{
11        CreateKeyError, CreateKeyErrorKind, DecryptError, DecryptErrorKind, EncryptError,
12        EncryptErrorKind, GenerateDataKeyError, GenerateDataKeyErrorKind, ScheduleKeyDeletionError,
13        ScheduleKeyDeletionErrorKind,
14    },
15    model::{DataKeySpec, EncryptionAlgorithmSpec, Tag},
16    types::{Blob, SdkError},
17    Client,
18};
19use aws_types::SdkConfig as AwsSdkConfig;
20use log::{info, warn};
21
22use crate::errors::{
23    Error::{Other, API},
24    Result,
25};
26
27/// Represents the data encryption key.
28#[derive(Debug)]
29pub struct DEK {
30    pub ciphertext: Vec<u8>,
31    pub plaintext: Vec<u8>,
32}
33
34impl DEK {
35    pub fn new(cipher: Vec<u8>, plain: Vec<u8>) -> Self {
36        // ref. https://doc.rust-lang.org/1.0.0/style/ownership/constructors.html
37        Self {
38            ciphertext: cipher,
39            plaintext: plain,
40        }
41    }
42}
43
44/// Implements AWS KMS manager.
45#[derive(Debug, Clone)]
46pub struct Manager {
47    #[allow(dead_code)]
48    shared_config: AwsSdkConfig,
49    cli: Client,
50}
51
52impl Manager {
53    pub fn new(shared_config: &AwsSdkConfig) -> Self {
54        let cloned = shared_config.clone();
55        let cli = Client::new(shared_config);
56        Self {
57            shared_config: cloned,
58            cli,
59        }
60    }
61
62    /// Creates an AWS KMS CMK.
63    pub async fn create_key(&self, key_desc: &str) -> Result<Key> {
64        info!("creating KMS CMK '{}'", key_desc);
65        let ret = self
66            .cli
67            .create_key()
68            .description(key_desc)
69            .tags(Tag::builder().tag_key("Name").tag_value(key_desc).build())
70            .tags(
71                Tag::builder()
72                    .tag_key("KIND")
73                    .tag_value("avalanche-ops")
74                    .build(),
75            )
76            .send()
77            .await;
78        let resp = match ret {
79            Ok(v) => v,
80            Err(e) => {
81                return Err(API {
82                    message: format!("failed create_key {:?}", e),
83                    is_retryable: is_error_retryable_create_key(&e),
84                });
85            }
86        };
87
88        let meta = match resp.key_metadata() {
89            Some(v) => v,
90            None => {
91                return Err(Other {
92                    message: String::from("unexpected empty key metadata"),
93                    is_retryable: false,
94                });
95            }
96        };
97        let key_id = meta.key_id().unwrap_or("");
98        let key_arn = meta.arn().unwrap_or("");
99
100        info!("created KMS CMK id '{}' and arn '{}'", key_id, key_arn);
101        Ok(Key::new(key_id, key_arn))
102    }
103
104    /// Schedules to delete a KMS CMK.
105    pub async fn schedule_to_delete(&self, key_id: &str) -> Result<()> {
106        info!("deleting KMS CMK '{}'", key_id);
107        let ret = self
108            .cli
109            .schedule_key_deletion()
110            .key_id(key_id)
111            .pending_window_in_days(7)
112            .send()
113            .await;
114
115        let deleted = match ret {
116            Ok(_) => true,
117            Err(e) => {
118                let mut ignore_err: bool = false;
119                if is_error_schedule_key_deletion_does_not_exist(&e) {
120                    warn!("KMS CMK '{}' does not exist", key_id);
121                    ignore_err = true
122                }
123                if is_error_schedule_key_deletion_already_scheduled(&e) {
124                    warn!("KMS CMK '{}' already scheduled for deletion", key_id);
125                    ignore_err = true
126                }
127                if !ignore_err {
128                    return Err(API {
129                        message: format!("failed schedule_key_deletion {:?}", e),
130                        is_retryable: is_error_retryable(&e),
131                    });
132                }
133                false
134            }
135        };
136        if deleted {
137            info!("scheduled to delete KMS CMK '{}'", key_id);
138        };
139
140        Ok(())
141    }
142
143    /// Encrypts data. The maximum size of the data KMS can encrypt is 4096 bytes for
144    /// "SYMMETRIC_DEFAULT" encryption algorithm. To specify a KMS key, use its key ID,
145    /// key ARN, alias name, or alias ARN.
146    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html
147    pub async fn encrypt(
148        &self,
149        key_id: &str,
150        spec: Option<EncryptionAlgorithmSpec>,
151        plaintext: Vec<u8>,
152    ) -> Result<Vec<u8>> {
153        // default to "SYMMETRIC_DEFAULT"
154        let key_spec = spec.unwrap_or(EncryptionAlgorithmSpec::SymmetricDefault);
155        info!(
156            "encrypting data (plaintext size {})",
157            human_readable::bytes(plaintext.len() as f64),
158        );
159
160        let ret = self
161            .cli
162            .encrypt()
163            .key_id(key_id)
164            .plaintext(Blob::new(plaintext))
165            .encryption_algorithm(key_spec)
166            .send()
167            .await;
168        let resp = match ret {
169            Ok(v) => v,
170            Err(e) => {
171                return Err(API {
172                    message: format!("failed encrypt {:?}", e),
173                    is_retryable: is_error_retryable_encrypt(&e),
174                });
175            }
176        };
177
178        let ciphertext = match resp.ciphertext_blob() {
179            Some(v) => v,
180            None => {
181                return Err(API {
182                    message: String::from("EncryptOutput.ciphertext_blob not foun"),
183                    is_retryable: false,
184                });
185            }
186        };
187        let ciphertext = ciphertext.clone().into_inner();
188
189        info!(
190            "encrypted data (ciphertext size {})",
191            human_readable::bytes(ciphertext.len() as f64),
192        );
193        Ok(ciphertext)
194    }
195
196    /// Decrypts data.
197    /// The maximum length of "ciphertext" is 6144 bytes.
198    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html
199    pub async fn decrypt(
200        &self,
201        key_id: &str,
202        spec: Option<EncryptionAlgorithmSpec>,
203        ciphertext: Vec<u8>,
204    ) -> Result<Vec<u8>> {
205        // default to "SYMMETRIC_DEFAULT"
206        let key_spec = spec.unwrap_or(EncryptionAlgorithmSpec::SymmetricDefault);
207        info!(
208            "decrypting data (ciphertext size {})",
209            human_readable::bytes(ciphertext.len() as f64),
210        );
211
212        let ret = self
213            .cli
214            .decrypt()
215            .key_id(key_id)
216            .ciphertext_blob(Blob::new(ciphertext))
217            .encryption_algorithm(key_spec)
218            .send()
219            .await;
220        let resp = match ret {
221            Ok(v) => v,
222            Err(e) => {
223                return Err(API {
224                    message: format!("failed decrypt {:?}", e),
225                    is_retryable: is_error_retryable_decrypt(&e),
226                });
227            }
228        };
229
230        let plaintext = match resp.plaintext() {
231            Some(v) => v,
232            None => {
233                return Err(API {
234                    message: String::from("DecryptOutput.plaintext not foun"),
235                    is_retryable: false,
236                });
237            }
238        };
239        let plaintext = plaintext.clone().into_inner();
240
241        info!(
242            "decrypted data (plaintext size {})",
243            human_readable::bytes(plaintext.len() as f64),
244        );
245        Ok(plaintext)
246    }
247
248    /// Encrypts data from a file and save the ciphertext to the other file.
249    pub async fn encrypt_file(
250        &self,
251        key_id: &str,
252        spec: Option<EncryptionAlgorithmSpec>,
253        src_file: &str,
254        dst_file: &str,
255    ) -> Result<()> {
256        info!("encrypting file {} to {}", src_file, dst_file);
257        let d = match fs::read(src_file) {
258            Ok(d) => d,
259            Err(e) => {
260                return Err(Other {
261                    message: format!("failed read {:?}", e),
262                    is_retryable: false,
263                });
264            }
265        };
266
267        let ciphertext = match self.encrypt(key_id, spec, d).await {
268            Ok(d) => d,
269            Err(e) => {
270                return Err(e);
271            }
272        };
273
274        let mut f = match File::create(dst_file) {
275            Ok(f) => f,
276            Err(e) => {
277                return Err(Other {
278                    message: format!("failed File::create {:?}", e),
279                    is_retryable: false,
280                });
281            }
282        };
283        match f.write_all(&ciphertext) {
284            Ok(_) => {}
285            Err(e) => {
286                return Err(Other {
287                    message: format!("failed File::write_all {:?}", e),
288                    is_retryable: false,
289                });
290            }
291        };
292
293        Ok(())
294    }
295
296    /// Decrypts data from a file and save the plaintext to the other file.
297    pub async fn decrypt_file(
298        &self,
299        key_id: &str,
300        spec: Option<EncryptionAlgorithmSpec>,
301        src_file: &str,
302        dst_file: &str,
303    ) -> Result<()> {
304        info!("decrypting file {} to {}", src_file, dst_file);
305        let d = match fs::read(src_file) {
306            Ok(d) => d,
307            Err(e) => {
308                return Err(Other {
309                    message: format!("failed read {:?}", e),
310                    is_retryable: false,
311                });
312            }
313        };
314
315        let plaintext = match self.decrypt(key_id, spec, d).await {
316            Ok(d) => d,
317            Err(e) => {
318                return Err(e);
319            }
320        };
321
322        let mut f = match File::create(dst_file) {
323            Ok(f) => f,
324            Err(e) => {
325                return Err(Other {
326                    message: format!("failed File::create {:?}", e),
327                    is_retryable: false,
328                });
329            }
330        };
331        match f.write_all(&plaintext) {
332            Ok(_) => {}
333            Err(e) => {
334                return Err(Other {
335                    message: format!("failed File::write_all {:?}", e),
336                    is_retryable: false,
337                });
338            }
339        };
340
341        Ok(())
342    }
343
344    /// Generates a data-encryption key.
345    /// The default key spec is AES_256 generate a 256-bit symmetric key.
346    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html
347    pub async fn generate_data_key(&self, key_id: &str, spec: Option<DataKeySpec>) -> Result<DEK> {
348        // default to "AES_256" for generate 256-bit symmetric key (32-byte)
349        let dek_spec = spec.unwrap_or(DataKeySpec::Aes256);
350        info!(
351            "generating KMS data key for '{}' with key spec {:?}",
352            key_id, dek_spec
353        );
354        let ret = self
355            .cli
356            .generate_data_key()
357            .key_id(key_id)
358            .key_spec(dek_spec)
359            .send()
360            .await;
361        let resp = match ret {
362            Ok(v) => v,
363            Err(e) => {
364                return Err(API {
365                    message: format!("failed generate_data_key {:?}", e),
366                    is_retryable: is_error_retryable_generate_data_key(&e),
367                });
368            }
369        };
370
371        let cipher = resp.ciphertext_blob().unwrap();
372        let plain = resp.plaintext().unwrap();
373        Ok(DEK::new(
374            cipher.clone().into_inner(),
375            plain.clone().into_inner(),
376        ))
377    }
378}
379
380/// Represents the KMS CMK.
381#[derive(Debug)]
382pub struct Key {
383    pub id: String,
384    pub arn: String,
385}
386
387impl Key {
388    pub fn new(id: &str, arn: &str) -> Self {
389        // ref. https://doc.rust-lang.org/1.0.0/style/ownership/constructors.html
390        Self {
391            id: String::from(id),
392            arn: String::from(arn),
393        }
394    }
395}
396
397#[inline]
398pub fn is_error_retryable<E>(e: &SdkError<E>) -> bool {
399    match e {
400        SdkError::TimeoutError(_) | SdkError::ResponseError { .. } => true,
401        SdkError::DispatchFailure(e) => e.is_timeout() || e.is_io(),
402        _ => false,
403    }
404}
405
406#[inline]
407pub fn is_error_retryable_create_key(e: &SdkError<CreateKeyError>) -> bool {
408    match e {
409        SdkError::ServiceError { err, .. } => {
410            matches!(
411                err.kind,
412                CreateKeyErrorKind::DependencyTimeoutException(_)
413                    | CreateKeyErrorKind::KmsInternalException(_)
414            )
415        }
416        _ => false,
417    }
418}
419
420#[inline]
421pub fn is_error_retryable_generate_data_key(e: &SdkError<GenerateDataKeyError>) -> bool {
422    match e {
423        SdkError::ServiceError { err, .. } => {
424            matches!(
425                err.kind,
426                GenerateDataKeyErrorKind::DependencyTimeoutException(_)
427                    | GenerateDataKeyErrorKind::KmsInternalException(_)
428                    | GenerateDataKeyErrorKind::KeyUnavailableException(_)
429            )
430        }
431        _ => false,
432    }
433}
434
435#[inline]
436pub fn is_error_retryable_encrypt(e: &SdkError<EncryptError>) -> bool {
437    match e {
438        SdkError::ServiceError { err, .. } => {
439            matches!(
440                err.kind,
441                EncryptErrorKind::DependencyTimeoutException(_)
442                    | EncryptErrorKind::KmsInternalException(_)
443                    | EncryptErrorKind::KeyUnavailableException(_)
444            )
445        }
446        _ => false,
447    }
448}
449
450#[inline]
451pub fn is_error_retryable_decrypt(e: &SdkError<DecryptError>) -> bool {
452    match e {
453        SdkError::ServiceError { err, .. } => {
454            matches!(
455                err.kind,
456                DecryptErrorKind::DependencyTimeoutException(_)
457                    | DecryptErrorKind::KmsInternalException(_)
458                    | DecryptErrorKind::KeyUnavailableException(_)
459            )
460        }
461        _ => false,
462    }
463}
464
465#[inline]
466fn is_error_schedule_key_deletion_does_not_exist(e: &SdkError<ScheduleKeyDeletionError>) -> bool {
467    match e {
468        SdkError::ServiceError { err, .. } => {
469            matches!(err.kind, ScheduleKeyDeletionErrorKind::NotFoundException(_))
470        }
471        _ => false,
472    }
473}
474
475#[inline]
476fn is_error_schedule_key_deletion_already_scheduled(
477    e: &SdkError<ScheduleKeyDeletionError>,
478) -> bool {
479    match e {
480        SdkError::ServiceError { err, .. } => {
481            let msg = format!("{:?}", err);
482            msg.contains("pending deletion")
483        }
484        _ => false,
485    }
486}