aws_manager/kms/
mod.rs

1pub mod envelope;
2
3use std::{
4    collections::HashMap,
5    fs::{self, File},
6    io::Write,
7};
8
9use crate::errors::{self, Error, Result};
10use aws_config::retry::ProvideErrorKind;
11use aws_sdk_kms::{
12    operation::{
13        create_grant::CreateGrantError,
14        create_key::CreateKeyError,
15        decrypt::DecryptError,
16        describe_key::{DescribeKeyError, DescribeKeyOutput},
17        encrypt::EncryptError,
18        generate_data_key::GenerateDataKeyError,
19        get_public_key::{GetPublicKeyError, GetPublicKeyOutput},
20        revoke_grant::RevokeGrantError,
21        schedule_key_deletion::ScheduleKeyDeletionError,
22        sign::SignError,
23    },
24    primitives::Blob,
25    types::{
26        DataKeySpec, EncryptionAlgorithmSpec, GrantOperation, KeySpec, KeyUsageType, MessageType,
27        SigningAlgorithmSpec, Tag,
28    },
29    Client,
30};
31use aws_smithy_client::SdkError;
32use aws_types::SdkConfig as AwsSdkConfig;
33
34/// Represents the data encryption key.
35#[derive(Debug)]
36pub struct DEK {
37    pub ciphertext: Vec<u8>,
38    pub plaintext: Vec<u8>,
39}
40
41impl DEK {
42    pub fn new(cipher: Vec<u8>, plain: Vec<u8>) -> Self {
43        // ref. <https://doc.rust-lang.org/1.0.0/style/ownership/constructors.html>
44        Self {
45            ciphertext: cipher,
46            plaintext: plain,
47        }
48    }
49}
50
51/// Implements AWS KMS manager.
52#[derive(Debug, Clone)]
53pub struct Manager {
54    pub region: String,
55    pub cli: Client,
56}
57
58impl Manager {
59    pub fn new(shared_config: &AwsSdkConfig) -> Self {
60        Self {
61            region: shared_config.region().unwrap().to_string(),
62            cli: Client::new(shared_config),
63        }
64    }
65
66    /// Creates an AWS KMS CMK.
67    /// Set the tag "Name" with the name value for more descriptive key creation.
68    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html>
69    pub async fn create_key(
70        &self,
71        key_spec: KeySpec,
72        key_usage: KeyUsageType,
73        tags: Option<HashMap<String, String>>,
74        multi_region: bool,
75    ) -> Result<Key> {
76        log::info!(
77            "creating KMS CMK key spec {:?}, key usage {:?}, multi-region '{multi_region}', region '{}'",
78            key_spec,
79            key_usage,
80            self.region
81        );
82        let mut req = self
83            .cli
84            .create_key()
85            // ref. https://docs.aws.amazon.com/kms/latest/developerguide/asymmetric-key-specs.html#key-spec-ecc
86            // ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html#API_CreateKey_RequestSyntax
87            .key_spec(key_spec)
88            // ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html#KMS-CreateKey-request-KeyUsage
89            .key_usage(key_usage);
90        if let Some(tags) = &tags {
91            for (k, v) in tags.iter() {
92                req = req.tags(Tag::builder().tag_key(k).tag_value(v).build());
93            }
94        }
95        if multi_region {
96            req = req.multi_region(true);
97        }
98
99        let resp = req.send().await.map_err(|e| Error::API {
100            message: format!("failed create_key {:?}", e),
101            retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_create_key(&e),
102        })?;
103
104        let meta = match resp.key_metadata() {
105            Some(v) => v,
106            None => {
107                return Err(Error::Other {
108                    message: String::from("unexpected empty key metadata"),
109                    retryable: false,
110                });
111            }
112        };
113
114        let key_id = meta.key_id().unwrap_or("");
115        let key_arn = meta.arn().unwrap_or("");
116        log::info!(
117            "successfully KMS CMK -- key Id '{}' and Arn '{}'",
118            key_id,
119            key_arn
120        );
121
122        Ok(Key::new(key_id, key_arn))
123    }
124
125    /// Creates a KMS grant for encrypt and decrypt.
126    /// And returns the grant Id and token.
127    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateGrant.html>
128    pub async fn create_grant_for_encrypt_decrypt(
129        &self,
130        key_id: &str,
131        grantee_principal: &str,
132    ) -> Result<(String, String)> {
133        log::info!("creating KMS grant for encrypt and decrypt for the key Id '{key_id}' on the grantee '{grantee_principal}' in region '{}'", self.region);
134
135        let out = self
136            .cli
137            .create_grant()
138            .key_id(key_id)
139            .grantee_principal(grantee_principal)
140            .operations(GrantOperation::Encrypt)
141            .operations(GrantOperation::Decrypt)
142            .operations(GrantOperation::DescribeKey)
143            .send()
144            .await
145            .map_err(|e| Error::API {
146                message: format!("failed create_grant {:?}", e),
147                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_create_grant(&e),
148            })?;
149
150        let grant_id = out.grant_id().unwrap().to_string();
151        let grant_token = out.grant_token().unwrap().to_string();
152        log::info!("created grant Id {grant_id} and token {grant_token}");
153
154        Ok((grant_id, grant_token))
155    }
156
157    /// Creates a KMS grant for Sign, DescribeKey, and GetPublicKey operations.
158    /// And returns the grant Id and token.
159    /// Note that "GetPublicKey" are not supported when creating a grant for a symmetric KMS.
160    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateGrant.html>
161    pub async fn create_grant_for_sign_reads(
162        &self,
163        key_id: &str,
164        grantee_principal: &str,
165    ) -> Result<(String, String)> {
166        log::info!("creating KMS grant for Sign, DescribeKey, GetPublicKey for the key '{key_id}' on the grantee '{grantee_principal}' in region '{}'", self.region);
167
168        let out = self
169            .cli
170            .create_grant()
171            .key_id(key_id)
172            .grantee_principal(grantee_principal)
173            .operations(GrantOperation::Sign)
174            .operations(GrantOperation::DescribeKey)
175            .operations(GrantOperation::GetPublicKey)
176            .send()
177            .await
178            .map_err(|e| Error::API {
179                message: format!("failed create_grant {:?}", e),
180                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_create_grant(&e),
181            })?;
182
183        let grant_id = out.grant_id().unwrap().to_string();
184        let grant_token = out.grant_token().unwrap().to_string();
185        log::info!(
186            "created grant Id '{grant_id}' and token '{grant_token}' in region '{}'",
187            self.region
188        );
189
190        Ok((grant_id, grant_token))
191    }
192
193    /// Revokes a KMS grant.
194    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_RevokeGrant.html>
195    pub async fn revoke_grant(&self, key_id: &str, grant_id: &str) -> Result<()> {
196        log::info!(
197            "revoking KMS grant '{grant_id}' for the key Id '{key_id}' in region '{}'",
198            self.region
199        );
200
201        self.cli
202            .revoke_grant()
203            .key_id(key_id)
204            .grant_id(grant_id)
205            .send()
206            .await
207            .map_err(|e| Error::API {
208                message: format!("failed revoke_grant {:?}", e),
209                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_revoke_grant(&e),
210            })?;
211
212        Ok(())
213    }
214
215    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_DescribeKey.html>
216    pub async fn describe_key(&self, key_arn: &str) -> Result<(String, DescribeKeyOutput)> {
217        log::info!("describing KMS ARN '{key_arn}' in region '{}'", self.region);
218        let desc = self
219            .cli
220            .describe_key()
221            .key_id(key_arn)
222            .send()
223            .await
224            .map_err(|e| Error::API {
225                message: format!("failed describe_key {:?}", e),
226                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_describe_key(&e),
227            })?;
228
229        let key_id = desc.key_metadata().unwrap().key_id().unwrap().to_string();
230        log::info!(
231            "successfully described KMS CMK -- key Id '{}' and Arn '{}'",
232            key_id,
233            key_arn
234        );
235
236        Ok((key_id, desc))
237    }
238
239    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html>
240    pub async fn get_public_key(&self, key_arn: &str) -> Result<GetPublicKeyOutput> {
241        log::info!(
242            "getting public key for KMS ARN '{key_arn}' in region '{}'",
243            self.region
244        );
245        let out = self
246            .cli
247            .get_public_key()
248            .key_id(key_arn)
249            .send()
250            .await
251            .map_err(|e| Error::API {
252                message: format!("failed get_public_key {:?}", e),
253                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_get_public_key(&e),
254            })?;
255
256        Ok(out)
257    }
258
259    /// Creates a default symmetric AWS KMS CMK.
260    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html
261    pub async fn create_symmetric_default_key(
262        &self,
263        name: &str,
264        multi_region: bool,
265    ) -> Result<Key> {
266        let mut tags = HashMap::new();
267        tags.insert(String::from("Name"), name.to_string());
268        self.create_key(
269            KeySpec::SymmetricDefault,
270            KeyUsageType::EncryptDecrypt,
271            Some(tags),
272            multi_region,
273        )
274        .await
275    }
276
277    /// Signs the 32-byte SHA256 output message with the ECDSA private key and the recoverable code.
278    /// Make sure to use key ARN in case sign happens cross-account with a grant token.
279    /// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html>
280    pub async fn sign_digest_secp256k1_ecdsa_sha256(
281        &self,
282        key_arn: &str,
283        digest: &[u8],
284        grant_token: Option<String>,
285    ) -> Result<Vec<u8>> {
286        log::info!(
287            "secp256k1 signing {}-byte digest message with key Arn '{key_arn}' (grant token exists '{}', region '{}')",
288            digest.len(),
289            grant_token.is_some(),
290            self.region
291        );
292
293        // DO NOT DO THIS -- fails with "Digest is invalid length for algorithm ECDSA_SHA_256"
294        // ref. https://github.com/awslabs/aws-sdk-rust/discussions/571
295        // let msg = aws_smithy_types::base64::encode(digest);
296
297        let mut builder = self
298            .cli
299            .sign()
300            .key_id(key_arn)
301            .message(Blob::new(digest)) // ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#KMS-Sign-request-Message
302            .message_type(MessageType::Digest) // ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#KMS-Sign-request-MessageType
303            .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256);
304        if let Some(grant_token) = grant_token {
305            builder = builder.grant_tokens(grant_token);
306        }
307
308        // ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html
309        let sign_output = builder.send().await.map_err(|e| {
310            let retryable = errors::is_sdk_err_retryable(&e) || is_err_retryable_sign(&e);
311            if !retryable {
312                log::warn!("non-retryable sign error {}", explain_err_sign(&e));
313            } else {
314                log::warn!("retryable sign error {}", explain_err_sign(&e));
315            }
316            Error::API {
317                message: e.to_string(),
318                retryable,
319            }
320        })?;
321
322        if let Some(blob) = sign_output.signature() {
323            let sig = blob.as_ref();
324            log::debug!(
325                "DER-encoded signature output from KMS is {}-byte",
326                sig.len()
327            );
328            return Ok(Vec::from(sig));
329        }
330
331        return Err(Error::API {
332            message: String::from("signature blob not found"),
333            retryable: false,
334        });
335    }
336
337    /// Schedules to delete a KMS CMK.
338    /// Pass either CMK Id or Arn.
339    /// The minimum pending window days are 7.
340    pub async fn schedule_to_delete(
341        &self,
342        key_arn: &str,
343        pending_window_in_days: i32,
344    ) -> Result<()> {
345        log::info!("scheduling to delete KMS key '{key_arn}' in {pending_window_in_days} days, in region '{}'", self.region);
346        let ret = self
347            .cli
348            .schedule_key_deletion()
349            .key_id(key_arn)
350            .pending_window_in_days(pending_window_in_days)
351            .send()
352            .await;
353
354        let deleted = match ret {
355            Ok(_) => true,
356            Err(e) => {
357                let mut ignore_err: bool = false;
358                if is_err_does_not_exist_schedule_key_deletion(&e) {
359                    log::warn!("KMS key '{key_arn}' does not exist");
360                    ignore_err = true
361                }
362                if is_err_schedule_key_deletion_already_scheduled(&e) {
363                    log::warn!("KMS key '{key_arn}' already scheduled for deletion");
364                    ignore_err = true
365                }
366                if !ignore_err {
367                    return Err(Error::API {
368                        message: format!("failed schedule_key_deletion {:?}", e),
369                        retryable: errors::is_sdk_err_retryable(&e),
370                    });
371                }
372                false
373            }
374        };
375        if deleted {
376            log::info!("successfully scheduled to delete KMS CMK '{key_arn}'");
377        };
378
379        Ok(())
380    }
381
382    /// Encrypts data. The maximum size of the data KMS can encrypt is 4096 bytes for
383    /// "SYMMETRIC_DEFAULT" encryption algorithm. To specify a KMS key, use its key ID,
384    /// key ARN, alias name, or alias ARN.
385    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html
386    pub async fn encrypt(
387        &self,
388        key_id: &str,
389        spec: Option<EncryptionAlgorithmSpec>,
390        plaintext: Vec<u8>,
391    ) -> Result<Vec<u8>> {
392        // default to "SYMMETRIC_DEFAULT"
393        let key_spec = spec.unwrap_or(EncryptionAlgorithmSpec::SymmetricDefault);
394        log::info!(
395            "encrypting data (plaintext size {}) in region '{}'",
396            human_readable::bytes(plaintext.len() as f64),
397            self.region
398        );
399
400        let resp = self
401            .cli
402            .encrypt()
403            .key_id(key_id)
404            .plaintext(Blob::new(plaintext))
405            .encryption_algorithm(key_spec)
406            .send()
407            .await
408            .map_err(|e| Error::API {
409                message: format!("failed encrypt {:?}", e),
410                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_encrypt(&e),
411            })?;
412
413        let ciphertext = match resp.ciphertext_blob() {
414            Some(v) => v,
415            None => {
416                return Err(Error::API {
417                    message: String::from("EncryptOutput.ciphertext_blob not found"),
418                    retryable: false,
419                });
420            }
421        };
422        let ciphertext = ciphertext.clone().into_inner();
423
424        log::info!(
425            "successfully encrypted data (ciphertext size {})",
426            human_readable::bytes(ciphertext.len() as f64),
427        );
428        Ok(ciphertext)
429    }
430
431    /// Decrypts data.
432    /// The maximum length of "ciphertext" is 6144 bytes.
433    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html
434    pub async fn decrypt(
435        &self,
436        key_id: &str,
437        spec: Option<EncryptionAlgorithmSpec>,
438        ciphertext: Vec<u8>,
439    ) -> Result<Vec<u8>> {
440        // default to "SYMMETRIC_DEFAULT"
441        let key_spec = spec.unwrap_or(EncryptionAlgorithmSpec::SymmetricDefault);
442        log::info!(
443            "decrypting data (ciphertext size {}) in region '{}'",
444            human_readable::bytes(ciphertext.len() as f64),
445            self.region
446        );
447
448        let resp = self
449            .cli
450            .decrypt()
451            .key_id(key_id)
452            .ciphertext_blob(Blob::new(ciphertext))
453            .encryption_algorithm(key_spec)
454            .send()
455            .await
456            .map_err(|e| Error::API {
457                message: format!("failed decrypt {:?}", e),
458                retryable: errors::is_sdk_err_retryable(&e) || is_err_retryable_decrypt(&e),
459            })?;
460
461        let plaintext = match resp.plaintext() {
462            Some(v) => v,
463            None => {
464                return Err(Error::API {
465                    message: String::from("DecryptOutput.plaintext not found"),
466                    retryable: false,
467                });
468            }
469        };
470        let plaintext = plaintext.clone().into_inner();
471
472        log::info!(
473            "successfully decrypted data (plaintext size {})",
474            human_readable::bytes(plaintext.len() as f64),
475        );
476        Ok(plaintext)
477    }
478
479    /// Encrypts data from a file and save the ciphertext to the other file.
480    pub async fn encrypt_file(
481        &self,
482        key_id: &str,
483        spec: Option<EncryptionAlgorithmSpec>,
484        src_file: &str,
485        dst_file: &str,
486    ) -> Result<()> {
487        log::info!("encrypting file {} to {}", src_file, dst_file);
488        let d = fs::read(src_file).map_err(|e| Error::Other {
489            message: format!("failed read {:?}", e),
490            retryable: false,
491        })?;
492        let ciphertext = self.encrypt(key_id, spec, d).await?;
493        let mut f = File::create(dst_file).map_err(|e| Error::Other {
494            message: format!("failed File::create {:?}", e),
495            retryable: false,
496        })?;
497        f.write_all(&ciphertext).map_err(|e| Error::Other {
498            message: format!("failed File::write_all {:?}", e),
499            retryable: false,
500        })
501    }
502
503    /// Decrypts data from a file and save the plaintext to the other file.
504    pub async fn decrypt_file(
505        &self,
506        key_id: &str,
507        spec: Option<EncryptionAlgorithmSpec>,
508        src_file: &str,
509        dst_file: &str,
510    ) -> Result<()> {
511        log::info!("decrypting file {} to {}", src_file, dst_file);
512        let d = fs::read(src_file).map_err(|e| Error::Other {
513            message: format!("failed read {:?}", e),
514            retryable: false,
515        })?;
516        let plaintext = self.decrypt(key_id, spec, d).await?;
517        let mut f = File::create(dst_file).map_err(|e| Error::Other {
518            message: format!("failed File::create {:?}", e),
519            retryable: false,
520        })?;
521        f.write_all(&plaintext).map_err(|e| Error::Other {
522            message: format!("failed File::write_all {:?}", e),
523            retryable: false,
524        })
525    }
526
527    /// Generates a data-encryption key.
528    /// The default key spec is AES_256 generate a 256-bit symmetric key.
529    /// ref. https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html
530    pub async fn generate_data_key(&self, key_id: &str, spec: Option<DataKeySpec>) -> Result<DEK> {
531        // default to "AES_256" for generate 256-bit symmetric key (32-byte)
532        let dek_spec = spec.unwrap_or(DataKeySpec::Aes256);
533        log::info!(
534            "generating KMS data key for '{}' with key spec {:?}",
535            key_id,
536            dek_spec
537        );
538        let resp = self
539            .cli
540            .generate_data_key()
541            .key_id(key_id)
542            .key_spec(dek_spec)
543            .send()
544            .await
545            .map_err(|e| Error::API {
546                message: format!("failed generate_data_key {:?}", e),
547                retryable: errors::is_sdk_err_retryable(&e)
548                    || is_err_retryable_generate_data_key(&e),
549            })?;
550
551        let cipher = resp.ciphertext_blob().unwrap();
552        let plain = resp.plaintext().unwrap();
553        Ok(DEK::new(
554            cipher.clone().into_inner(),
555            plain.clone().into_inner(),
556        ))
557    }
558}
559
560/// Represents the KMS CMK.
561#[derive(Debug)]
562pub struct Key {
563    pub id: String,
564    pub arn: String,
565}
566
567impl Key {
568    pub fn new(id: &str, arn: &str) -> Self {
569        // ref. <https://doc.rust-lang.org/1.0.0/style/ownership/constructors.html>
570        Self {
571            id: String::from(id),
572            arn: String::from(arn),
573        }
574    }
575}
576
577#[inline]
578pub fn is_err_retryable_create_key(
579    e: &SdkError<CreateKeyError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
580) -> bool {
581    match e {
582        SdkError::ServiceError(err) => {
583            err.err().is_dependency_timeout_exception() || err.err().is_kms_internal_exception()
584        }
585        _ => false,
586    }
587}
588
589#[inline]
590pub fn is_err_retryable_create_grant(
591    e: &SdkError<CreateGrantError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
592) -> bool {
593    match e {
594        SdkError::ServiceError(err) => {
595            err.err().is_dependency_timeout_exception() || err.err().is_kms_internal_exception()
596        }
597        _ => false,
598    }
599}
600
601#[inline]
602pub fn is_err_retryable_revoke_grant(
603    e: &SdkError<RevokeGrantError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
604) -> bool {
605    match e {
606        SdkError::ServiceError(err) => {
607            err.err().is_dependency_timeout_exception() || err.err().is_kms_internal_exception()
608        }
609        _ => false,
610    }
611}
612
613#[inline]
614pub fn is_err_retryable_describe_key(
615    e: &SdkError<DescribeKeyError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
616) -> bool {
617    match e {
618        SdkError::ServiceError(err) => {
619            err.err().is_dependency_timeout_exception() || err.err().is_kms_internal_exception()
620        }
621        _ => false,
622    }
623}
624
625/// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html>
626#[inline]
627pub fn is_err_retryable_get_public_key(
628    e: &SdkError<GetPublicKeyError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
629) -> bool {
630    match e {
631        SdkError::ServiceError(err) => {
632            err.err().is_dependency_timeout_exception()
633                || err.err().is_key_unavailable_exception()
634                || err.err().is_kms_internal_exception()
635        }
636        _ => false,
637    }
638}
639
640#[inline]
641pub fn is_err_retryable_generate_data_key(
642    e: &SdkError<GenerateDataKeyError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
643) -> bool {
644    match e {
645        SdkError::ServiceError(err) => {
646            err.err().is_dependency_timeout_exception()
647                || err.err().is_key_unavailable_exception()
648                || err.err().is_kms_internal_exception()
649        }
650        _ => false,
651    }
652}
653
654#[inline]
655fn is_err_retryable_encrypt(
656    e: &SdkError<EncryptError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
657) -> bool {
658    match e {
659        SdkError::ServiceError(err) => {
660            err.err().is_dependency_timeout_exception()
661                || err.err().is_key_unavailable_exception()
662                || err.err().is_kms_internal_exception()
663        }
664        _ => false,
665    }
666}
667
668#[inline]
669fn is_err_retryable_decrypt(
670    e: &SdkError<DecryptError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
671) -> bool {
672    match e {
673        SdkError::ServiceError(err) => {
674            err.err().is_dependency_timeout_exception()
675                || err.err().is_key_unavailable_exception()
676                || err.err().is_kms_internal_exception()
677        }
678        _ => false,
679    }
680}
681
682#[inline]
683fn is_err_does_not_exist_schedule_key_deletion(
684    e: &SdkError<
685        ScheduleKeyDeletionError,
686        aws_smithy_runtime_api::client::orchestrator::HttpResponse,
687    >,
688) -> bool {
689    match e {
690        SdkError::ServiceError(err) => err.err().is_not_found_exception(),
691        _ => false,
692    }
693}
694
695#[inline]
696fn is_err_schedule_key_deletion_already_scheduled(
697    e: &SdkError<
698        ScheduleKeyDeletionError,
699        aws_smithy_runtime_api::client::orchestrator::HttpResponse,
700    >,
701) -> bool {
702    match e {
703        SdkError::ServiceError(err) => {
704            let msg = format!("{:?}", err);
705            msg.contains("pending deletion")
706        }
707        _ => false,
708    }
709}
710
711/// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#KMS-Sign-request-SigningAlgorithm>
712#[inline]
713pub fn is_err_retryable_sign(
714    e: &SdkError<SignError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
715) -> bool {
716    match e {
717        SdkError::ServiceError(err) => {
718            err.err().is_dependency_timeout_exception()
719                || err.err().is_key_unavailable_exception()
720                || err.err().is_kms_internal_exception()
721        }
722        _ => false,
723    }
724}
725
726/// ref. <https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html#KMS-Sign-request-SigningAlgorithm>
727#[inline]
728pub fn explain_err_sign(
729    e: &SdkError<SignError, aws_smithy_runtime_api::client::orchestrator::HttpResponse>,
730) -> String {
731    match e {
732        SdkError::ServiceError(err) => format!(
733            "sign service error [code '{:?}', kind '{:?}', meta '{:?}']",
734            err.err().code(),
735            err.err().retryable_error_kind(),
736            err.err().meta(),
737        ),
738        _ => e.to_string(),
739    }
740}