trelent-hyok 0.1.12

A Rust library implementing Hold Your Own Key (HYOK) encryption patterns with support for multiple cloud providers
Documentation
//! AWS Key Management Service (KMS) integration.
//!
//! This module provides integration with AWS KMS for key management operations.
//! It supports:
//!
//! - Key encryption/decryption
//! - Multiple encryption algorithms
//! - AWS KMS key aliases and ARNs
//! - Error handling for AWS operations

#![cfg(feature = "aws")]

use crate::cmk::CMKTrait;
use crate::error::cmk::CMKError;
use async_trait::async_trait;
use aws_sdk_kms::primitives::Blob;
use aws_sdk_kms::types::EncryptionAlgorithmSpec;
use aws_sdk_kms::Client;
use std::sync::Arc;

/// AWS Customer Managed Key implementation using AWS KMS.
///
/// This CMK implementation:
/// - Uses AWS KMS for cryptographic operations
/// - Supports multiple encryption algorithms
/// - Handles key aliases and ARNs
/// - Provides proper error handling
///
/// # Security
///
/// This implementation:
/// - Uses AWS KMS for key protection
/// - Supports key rotation
/// - Provides audit logging
/// - Enables access control via IAM
///
/// # Costs
///
/// Be aware that AWS KMS operations incur costs based on:
/// - Number of API calls
/// - Key storage duration
/// - Key usage volume
///
/// # Example
/// ```no_run
/// use std::sync::Arc;
/// use aws_sdk_kms::{Client, types::EncryptionAlgorithmSpec};
/// use hyokashi::AwsCMK;
///
/// async {
///     let client = Arc::new(Client::new(&aws_config::load_from_env().await));
///     let cmk = AwsCMK::new(
///         client,
///         "alias/my-key".to_string(),
///         EncryptionAlgorithmSpec::RsaesOaepSha256,
///     );
///
///     // Encrypt some data
///     let data = vec![1, 2, 3];
///     let encrypted = cmk.encrypt(data).await?;
///
///     // Decrypt the data
///     let decrypted = cmk.decrypt(encrypted).await?;
///     # Ok::<(), Box<dyn std::error::Error>>(())
/// };
/// ```
pub struct AwsCMK {
    pub(crate) client: Arc<Client>,
    pub(crate) key_name: String,
    pub(crate) encryption_algorithm: EncryptionAlgorithmSpec,
}

impl AwsCMK {
    /// Creates a new `AwsCMK` instance from an AWS KMS client, a key name, and an encryption algorithm.
    ///
    /// # Arguments
    ///
    /// * `client` - An `Arc` wrapping the AWS KMS client.
    /// * `key_name` - The AWS KMS key alias or ARN used to identify the key.
    /// * `encryption_algorithm` - The desired encryption algorithm.
    ///
    /// # Returns
    ///
    /// A new `AwsCMK` configured with the specified client, key, and algorithm.
    pub fn new(
        client: Arc<Client>,
        key_name: String,
        encryption_algorithm: EncryptionAlgorithmSpec
    ) -> Self {
        Self {
            client,
            key_name,
            encryption_algorithm,
        }
    }
}

#[async_trait]
impl CMKTrait for AwsCMK {
    /// Encrypts the specified plaintext data using the AWS CMK.
    ///
    /// # Arguments
    ///
    /// * `plaintext` - The plaintext bytes to be encrypted.
    ///
    /// # Errors
    ///
    /// Returns a `CMKError` if encryption fails in the AWS KMS service.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// // let cmk = AwsCMK::new(/* ... */);
    /// // let encrypted = cmk.encrypt(vec![1, 2, 3]).await?;
    /// ```
    async fn encrypt(&self, plaintext: Vec<u8>) -> Result<Vec<u8>, CMKError> {
        let blob = Blob::new(plaintext);

        let encrypted = self.client
            .encrypt()
            .key_id(&self.key_name)
            .encryption_algorithm(self.encryption_algorithm.clone())
            .plaintext(blob)
            .send();
        match encrypted.await {
            Ok(encrypted) =>
                match encrypted.ciphertext_blob() {
                    Some(ciphertext) => Ok(ciphertext.clone().into_inner()),
                    None => Err(CMKError("CMK returned no ciphertext".to_owned())),
                }
            Err(e) => Err(CMKError(format!("Could not encrypt plaintext, Error: {:?}", e))),
        }
    }

    /// Decrypts the specified ciphertext data using the AWS CMK.
    ///
    /// # Arguments
    ///
    /// * `ciphertext` - The ciphertext bytes to be decrypted.
    ///
    /// # Errors
    ///
    /// Returns a `CMKError` if decryption fails in the AWS KMS service.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// // let cmk = AwsCMK::new(/* ... */);
    /// // let decrypted = cmk.decrypt(some_encrypted_data).await?;
    /// ```
    async fn decrypt(&self, ciphertext: Vec<u8>) -> Result<Vec<u8>, CMKError> {
        let blob = Blob::new(ciphertext);

        let decrypted = self.client
            .decrypt()
            .key_id(&self.key_name)
            .encryption_algorithm(self.encryption_algorithm.clone())
            .ciphertext_blob(blob)
            .send();
        match decrypted.await {
            Ok(decrypted) =>
                match decrypted.plaintext() {
                    Some(plaintext) => Ok(plaintext.clone().into_inner()),
                    None => Err(CMKError("CMK returned no plaintext".to_string())),
                }
            Err(e) => Err(CMKError(format!("Could not decrypt ciphertext, Error: {:?}", e))),
        }
    }
}