|
| 1 | +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +/* |
| 5 | + This example sets up an MRK multi-keyring and an MRK discovery |
| 6 | + multi-keyring using a custom client supplier. |
| 7 | + A custom client supplier grants users access to more granular |
| 8 | + configuration aspects of their authentication details and KMS |
| 9 | + client. In this example, we create a simple custom client supplier |
| 10 | + that authenticates with a different IAM role based on the |
| 11 | + region of the KMS key. |
| 12 | +
|
| 13 | + This example creates a MRK multi-keyring configured with a custom |
| 14 | + client supplier using a single MRK and encrypts the example_data with it. |
| 15 | + Then, it creates a MRK discovery multi-keyring to decrypt the ciphertext. |
| 16 | +*/ |
| 17 | + |
| 18 | +use super::regional_role_client_supplier::RegionalRoleClientSupplier; |
| 19 | +use aws_esdk::client as esdk_client; |
| 20 | +use aws_esdk::material_providers::client as mpl_client; |
| 21 | +use aws_esdk::material_providers::types::DiscoveryFilter; |
| 22 | +use aws_esdk::material_providers::types::error::Error::AwsCryptographicMaterialProvidersException; |
| 23 | +use aws_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig; |
| 24 | +use aws_esdk::types::*; |
| 25 | + |
| 26 | +pub async fn encrypt_and_decrypt_with_keyring( |
| 27 | + example_data: &str, |
| 28 | + mrk_key_id_encrypt: &str, |
| 29 | + aws_account_id: &str, |
| 30 | + aws_regions: Vec<String>, |
| 31 | +) -> Result<(), crate::BoxError> { |
| 32 | + // 1. Instantiate the encryption SDK client. |
| 33 | + // This builds the default client with the RequireEncryptRequireDecrypt commitment policy, |
| 34 | + // which enforces that this client only encrypts using committing algorithm suites and enforces |
| 35 | + // that this client will only decrypt encrypted messages that were created with a committing |
| 36 | + // algorithm suite. |
| 37 | + let esdk_config = AwsEncryptionSdkConfig::default(); |
| 38 | + let esdk_client = esdk_client::Client::from_conf(esdk_config)?; |
| 39 | + |
| 40 | + // 2. Create encryption context. |
| 41 | + // Remember that your encryption context is NOT SECRET. |
| 42 | + // For more information, see |
| 43 | + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context |
| 44 | + let encryption_context = EncryptionContext::from([ |
| 45 | + ("encryption".to_string(), "context".to_string()), |
| 46 | + ("is not".to_string(), "secret".to_string()), |
| 47 | + ("but adds".to_string(), "useful metadata".to_string()), |
| 48 | + ( |
| 49 | + "that can help you".to_string(), |
| 50 | + "be confident that".to_string(), |
| 51 | + ), |
| 52 | + ( |
| 53 | + "the data you are handling".to_string(), |
| 54 | + "is what you think it is".to_string(), |
| 55 | + ), |
| 56 | + ]); |
| 57 | + |
| 58 | + // 3. Create a single MRK multi-keyring. |
| 59 | + // This can be either a single-region KMS key or an MRK. |
| 60 | + // For this example to succeed, the key's region must either |
| 61 | + // 1) be in the regions list, or |
| 62 | + // 2) the key must be an MRK with a replica defined |
| 63 | + // in a region in the regions list, and the client |
| 64 | + // must have the correct permissions to access the replica. |
| 65 | + let mpl_config = MaterialProvidersConfig::builder().build()?; |
| 66 | + let mpl = mpl_client::Client::from_conf(mpl_config)?; |
| 67 | + |
| 68 | + // Create the multi-keyring using our custom client supplier |
| 69 | + // defined in the RegionalRoleClientSupplier class in this directory. |
| 70 | + // Note: RegionalRoleClientSupplier will internally use the key_arn's region |
| 71 | + // to retrieve the correct IAM role. |
| 72 | + let mrk_keyring_with_client_supplier = mpl |
| 73 | + .create_aws_kms_mrk_multi_keyring() |
| 74 | + .client_supplier(RegionalRoleClientSupplier {}) |
| 75 | + .generator(mrk_key_id_encrypt) |
| 76 | + .send() |
| 77 | + .await?; |
| 78 | + |
| 79 | + // 4. Encrypt the data with the encryption_context using the encrypt_keyring. |
| 80 | + let plaintext = example_data.as_bytes(); |
| 81 | + |
| 82 | + let encrypt_input = EncryptInputBuilder::default() |
| 83 | + .plaintext(plaintext) |
| 84 | + .keyring(mrk_keyring_with_client_supplier) |
| 85 | + .encryption_context(&encryption_context) |
| 86 | + .build()?; |
| 87 | + let encryption_response = esdk_client.encrypt(&encrypt_input).await?; |
| 88 | + |
| 89 | + let ciphertext = encryption_response.ciphertext; |
| 90 | + |
| 91 | + // 5. Demonstrate that the ciphertext and plaintext are different. |
| 92 | + // (This is an example for demonstration; you do not need to do this in your own code.) |
| 93 | + assert_ne!( |
| 94 | + ciphertext, plaintext, |
| 95 | + "Ciphertext and plaintext data are the same. Invalid encryption" |
| 96 | + ); |
| 97 | + |
| 98 | + // 6. Create a MRK discovery multi-keyring with a custom client supplier. |
| 99 | + // A discovery MRK multi-keyring will be composed of |
| 100 | + // multiple discovery MRK keyrings, one for each region. |
| 101 | + // Each component keyring has its own KMS client in a particular region. |
| 102 | + // When we provide a client supplier to the multi-keyring, all component |
| 103 | + // keyrings will use that client supplier configuration. |
| 104 | + // In our tests, we make `mrk_key_id_encrypt` an MRK with a replica, and |
| 105 | + // provide only the replica region in our discovery filter. |
| 106 | + let discovery_filter = DiscoveryFilter::builder() |
| 107 | + .account_ids(vec![aws_account_id.to_string()]) |
| 108 | + .partition("aws".to_string()) |
| 109 | + .build()?; |
| 110 | + |
| 111 | + let mrk_discovery_client_supplier_keyring = mpl |
| 112 | + .create_aws_kms_mrk_discovery_multi_keyring() |
| 113 | + .client_supplier(RegionalRoleClientSupplier {}) |
| 114 | + .discovery_filter(discovery_filter.clone()) |
| 115 | + .regions(aws_regions) |
| 116 | + .send() |
| 117 | + .await?; |
| 118 | + |
| 119 | + // 7. Decrypt your encrypted data using the discovery multi keyring. |
| 120 | + // On Decrypt, the header of the encrypted message (ciphertext) will be parsed. |
| 121 | + // The header contains the Encrypted Data Keys (EDKs), which, if the EDK |
| 122 | + // was encrypted by a KMS Keyring, includes the KMS Key ARN. |
| 123 | + // For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption |
| 124 | + // is successful. |
| 125 | + // Since every member of the Multi Keyring is a Discovery Keyring: |
| 126 | + // Each Keyring will filter the EDKs by the Discovery Filter and the Keyring's region. |
| 127 | + // For each filtered EDK, the keyring will attempt decryption with the keyring's client. |
| 128 | + // All of this is done serially, until a success occurs or all keyrings have failed |
| 129 | + // all (filtered) EDKs. KMS MRK Discovery Keyrings will attempt to decrypt |
| 130 | + // Multi Region Keys (MRKs) and regular KMS Keys. |
| 131 | + let decrypt_input = DecryptInputBuilder::default() |
| 132 | + .ciphertext(&ciphertext) |
| 133 | + .keyring(mrk_discovery_client_supplier_keyring) |
| 134 | + // Provide the encryption context that was supplied to the encrypt method |
| 135 | + .encryption_context(&encryption_context) |
| 136 | + .build()?; |
| 137 | + let decryption_response = esdk_client.decrypt(&decrypt_input).await?; |
| 138 | + |
| 139 | + let decrypted_plaintext = decryption_response.plaintext; |
| 140 | + |
| 141 | + // 8. Demonstrate that the decrypted plaintext is identical to the original plaintext. |
| 142 | + // (This is an example for demonstration; you do not need to do this in your own code.) |
| 143 | + assert_eq!( |
| 144 | + decrypted_plaintext, plaintext, |
| 145 | + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" |
| 146 | + ); |
| 147 | + |
| 148 | + // 9. Test the Missing Region Exception |
| 149 | + // (This is an example for demonstration; you do not need to do this in your own code.) |
| 150 | + let mrk_discovery_client_supplier_keyring_missing_region = mpl |
| 151 | + .create_aws_kms_mrk_discovery_multi_keyring() |
| 152 | + .client_supplier(RegionalRoleClientSupplier {}) |
| 153 | + .discovery_filter(discovery_filter) |
| 154 | + .regions(vec!["fake-region".to_string()]) |
| 155 | + .send() |
| 156 | + .await; |
| 157 | + |
| 158 | + // Swallow the exception |
| 159 | + // (This is an example for demonstration; you do not need to do this in your own code.) |
| 160 | + match mrk_discovery_client_supplier_keyring_missing_region { |
| 161 | + Ok(_) => panic!( |
| 162 | + "Decryption using discovery keyring with missing region MUST \ |
| 163 | + raise AwsCryptographicMaterialProvidersException" |
| 164 | + ), |
| 165 | + Err(AwsCryptographicMaterialProvidersException { message: _e }) => (), |
| 166 | + _ => panic!("Unexpected error type"), |
| 167 | + } |
| 168 | + |
| 169 | + println!("Client Supplier Example Completed Successfully"); |
| 170 | + |
| 171 | + Ok(()) |
| 172 | +} |
| 173 | + |
| 174 | +#[tokio::test(flavor = "multi_thread")] |
| 175 | +pub async fn test_encrypt_and_decrypt_with_keyring() -> Result<(), crate::BoxError2> { |
| 176 | + // Test function for encrypt and decrypt using the Client Supplier example |
| 177 | + use crate::example_utils::utils; |
| 178 | + |
| 179 | + // Note that we pass in an MRK in us-east-1. The RegionalRoleClientSupplier |
| 180 | + // will internally use the key_arn's region (us-east-1) |
| 181 | + // to retrieve the correct IAM role. |
| 182 | + // and access its replica in eu-west-1 |
| 183 | + let aws_regions: Vec<String> = vec!["eu-west-1".to_string()]; |
| 184 | + |
| 185 | + encrypt_and_decrypt_with_keyring( |
| 186 | + utils::TEST_EXAMPLE_DATA, |
| 187 | + utils::TEST_MRK_KEY_ID_US_EAST_1, |
| 188 | + utils::TEST_DEFAULT_KMS_KEY_ACCOUNT_ID, |
| 189 | + aws_regions, |
| 190 | + ) |
| 191 | + .await?; |
| 192 | + |
| 193 | + Ok(()) |
| 194 | +} |
0 commit comments