Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 02d7b0a

Browse files
committedSep 19, 2024··
m
1 parent 088a7dc commit 02d7b0a

File tree

5 files changed

+509
-0
lines changed

5 files changed

+509
-0
lines changed
 
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use std::collections::HashMap;
2+
use crate::test_utils;
3+
use aws_sdk_dynamodb::types::AttributeValue;
4+
5+
use db_esdk::deps::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig;
6+
use db_esdk::deps::aws_cryptography_materialProviders::client as mpl_client;
7+
use db_esdk::deps::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction;
8+
9+
use db_esdk::deps::aws_cryptography_dbEncryptionSdk_dynamoDb_itemEncryptor::types::dynamo_db_item_encryptor_config::DynamoDbItemEncryptorConfig;
10+
use db_esdk::deps::aws_cryptography_dbEncryptionSdk_dynamoDb_itemEncryptor::client as enc_client;
11+
use db_esdk::deps::aws_cryptography_materialProviders::types::DbeAlgorithmSuiteId;
12+
13+
/*
14+
This example sets up a DynamoDb Item Encryptor and uses
15+
the EncryptItem and DecryptItem APIs to directly encrypt and
16+
decrypt an existing DynamoDb item.
17+
You should use the DynamoDb Item Encryptor
18+
if you already have a DynamoDb Item to encrypt or decrypt,
19+
and do not need to make a Put or Get call to DynamoDb.
20+
For example, if you are using DynamoDb Streams,
21+
you may already be working with an encrypted item obtained from
22+
DynamoDb, and want to directly decrypt the item.
23+
24+
Running this example requires access to the DDB Table whose name
25+
is provided in CLI arguments.
26+
This table must be configured with the following
27+
primary key configuration:
28+
- Partition key is named "partition_key" with type (S)
29+
- Sort key is named "sort_key" with type (S)
30+
*/
31+
pub async fn encrypt_decrypt()
32+
{
33+
let kms_key_id = test_utils::TEST_KMS_KEY_ID;
34+
let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
35+
36+
37+
// 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
38+
// For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
39+
// We will use the `CreateMrkMultiKeyring` method to create this keyring,
40+
// as it will correctly handle both single region and Multi-Region KMS Keys.
41+
let provider_config = MaterialProvidersConfig::builder().build().unwrap();
42+
let mat_prov = mpl_client::Client::from_conf(provider_config).unwrap();
43+
let kms_keyring = mat_prov.create_aws_kms_mrk_multi_keyring().generator(kms_key_id).send().await.unwrap();
44+
45+
// 2. Configure which attributes are encrypted and/or signed when writing new items.
46+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
47+
// we must explicitly configure how they should be treated during item encryption:
48+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
49+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
50+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
51+
let attribute_actions_on_encrypt = HashMap::from([
52+
("partition_key".to_string(), CryptoAction::SignOnly),
53+
("sort_key".to_string(), CryptoAction::SignOnly),
54+
("attribute1".to_string(), CryptoAction::EncryptAndSign),
55+
("attribute2".to_string(), CryptoAction::SignOnly),
56+
(":attribute3".to_string(), CryptoAction::DoNothing),
57+
]);
58+
59+
// 3. Configure which attributes we expect to be included in the signature
60+
// when reading items. There are two options for configuring this:
61+
//
62+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
63+
// When defining your DynamoDb schema and deciding on attribute names,
64+
// choose a distinguishing prefix (such as ":") for all attributes that
65+
// you do not want to include in the signature.
66+
// This has two main benefits:
67+
// - It is easier to reason about the security and authenticity of data within your item
68+
// when all unauthenticated data is easily distinguishable by their attribute name.
69+
// - If you need to add new unauthenticated attributes in the future,
70+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
71+
// and immediately start writing to that new attribute, without
72+
// any other configuration update needed.
73+
// Once you configure this field, it is not safe to update it.
74+
//
75+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
76+
// a set of attributes that should be considered unauthenticated when encountered
77+
// on read. Be careful if you use this configuration. Do not remove an attribute
78+
// name from this configuration, even if you are no longer writing with that attribute,
79+
// as old items may still include this attribute, and our configuration needs to know
80+
// to continue to exclude this attribute from the signature scope.
81+
// If you add new attribute names to this field, you must first deploy the update to this
82+
// field to all readers in your host fleet before deploying the update to start writing
83+
// with that new attribute.
84+
//
85+
// For this example, we have designed our DynamoDb table such that any attribute name with
86+
// the ":" prefix should be considered unauthenticated.
87+
const UNSIGNED_ATTR_PREFIX : &str = ":";
88+
89+
// 4. Create the configuration for the DynamoDb Item Encryptor
90+
let config = DynamoDbItemEncryptorConfig::builder()
91+
.logical_table_name(ddb_table_name)
92+
.partition_key_name("partition_key")
93+
.sort_key_name("sort_key")
94+
.attribute_actions_on_encrypt(attribute_actions_on_encrypt)
95+
.keyring(kms_keyring)
96+
.allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
97+
// Specifying an algorithm suite is not required,
98+
// but is done here to demonstrate how to do so.
99+
// We suggest using the
100+
// `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
101+
// which includes AES-GCM with key derivation, signing, and key commitment.
102+
// This is also the default algorithm suite if one is not specified in this config.
103+
// For more information on supported algorithm suites, see:
104+
// https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
105+
.algorithm_suite_id(DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384)
106+
.build().unwrap();
107+
108+
// 5. Create the DynamoDb Item Encryptor
109+
let item_encryptor = enc_client::Client::from_conf(config).unwrap();
110+
111+
// 6. Directly encrypt a DynamoDb item using the DynamoDb Item Encryptor
112+
let original_item = HashMap::from([
113+
("partition_key".to_string(), AttributeValue::S("ItemEncryptDecryptExample".to_string())),
114+
("sort_key".to_string(), AttributeValue::N("0".to_string())),
115+
("attribute1".to_string(), AttributeValue::S("encrypt and sign me!".to_string())),
116+
("attribute2".to_string(), AttributeValue::S("sign me!".to_string())),
117+
(":attribute3".to_string(), AttributeValue::S("ignore me!".to_string())),
118+
]);
119+
120+
let encrypted_item = item_encryptor.encrypt_item()
121+
.plaintext_item(original_item.clone())
122+
.send()
123+
.await
124+
.unwrap()
125+
.encrypted_item.unwrap();
126+
127+
// Demonstrate that the item has been encrypted
128+
assert_eq!(encrypted_item["partition_key"], AttributeValue::S("ItemEncryptDecryptExample".to_string()));
129+
assert_eq!(encrypted_item["sort_key"], AttributeValue::N("0".to_string()));
130+
assert!(encrypted_item["attribute1"].is_b());
131+
assert!(!encrypted_item["attribute1"].is_s());
132+
133+
// 7. Directly decrypt the encrypted item using the DynamoDb Item Encryptor
134+
let decrypted_item = item_encryptor.decrypt_item()
135+
.encrypted_item(encrypted_item)
136+
.send()
137+
.await
138+
.unwrap()
139+
.plaintext_item.unwrap();
140+
141+
// Demonstrate that GetItem succeeded and returned the decrypted item
142+
assert_eq!(decrypted_item, original_item);
143+
println!("encrypt_decrypt successful.");
144+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod item_encrypt_decrypt;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,39 @@
11

22
pub mod basic_get_put_example;
33
pub mod test_utils;
4+
pub mod itemencryptor;
5+
pub mod searchableencryption;
46

57
#[tokio::main]
68
pub async fn main() {
79
basic_get_put_example::put_item_get_item().await;
10+
itemencryptor::item_encrypt_decrypt::encrypt_decrypt().await;
11+
/*
12+
13+
await ScanErrorExample.ScanError();
14+
await GetEncryptedDataKeyDescriptionExample.GetEncryptedDataKeyDescription();
15+
await MultiPutGetExample.MultiPutGet();
16+
await ClientSupplierExample.ClientSupplierPutItemGetItem();
17+
await MultiMrkKeyringExample.MultiMrkKeyringGetItemPutItem();
18+
await RawAesKeyringExample.RawAesKeyringGetItemPutItem();
19+
await MrkDiscoveryMultiKeyringExample.MultiMrkDiscoveryKeyringGetItemPutItem();
20+
await MultiKeyringExample.MultiKeyringGetItemPutItem();
21+
await RawRsaKeyringExample.RawRsaKeyringGetItemPutItem();
22+
await KmsRsaKeyringExample.KmsRsaKeyringGetItemPutItem();
23+
24+
var keyId = CreateKeyStoreKeyExample.KeyStoreCreateKey();
25+
var keyId2 = CreateKeyStoreKeyExample.KeyStoreCreateKey();
26+
// Key creation is eventually consistent, so wait 5 seconds to decrease the likelihood
27+
// our test fails due to eventual consistency issues.
28+
Thread.Sleep(5000);
29+
30+
await HierarchicalKeyringExample.HierarchicalKeyringGetItemPutItem(keyId, keyId2);
31+
32+
await BasicSearchableEncryptionExample.PutItemQueryItemWithBeacon(keyId);
33+
await CompoundBeaconSearchableEncryptionExample.PutItemQueryItemWithCompoundBeacon(keyId);
34+
await VirtualBeaconSearchableEncryptionExample.PutItemQueryItemWithVirtualBeacon(keyId);
35+
await BeaconStylesSearchableEncryptionExample.PutItemQueryItemWithBeaconStyles(keyId);
36+
await ComplexSearchableEncryptionExample.RunExample(keyId);
37+
Console.Write("All examples completed successfully.\n");
38+
*/
839
}
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
use std::collections::HashMap;
2+
use crate::test_utils;
3+
use aws_sdk_dynamodb::types::AttributeValue;
4+
5+
use db_esdk::deps::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig;
6+
use db_esdk::deps::aws_cryptography_materialProviders::client;
7+
use db_esdk::deps::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction;
8+
9+
use db_esdk::deps::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig;
10+
use db_esdk::deps::aws_cryptography_dbEncryptionSdk_dynamoDb::types::StandardBeacon;
11+
use db_esdk::deps::aws_cryptography_materialProviders::types::DbeAlgorithmSuiteId;
12+
use db_esdk::intercept::DbEsdkInterceptor;
13+
use db_esdk::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig;
14+
// use db_esdk::deps::aws_cryptography_keyStore::types::KeyStore;
15+
use db_esdk::deps::aws_cryptography_keyStore::client as keystore_client;
16+
use db_esdk::deps::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig;
17+
18+
/*
19+
This example demonstrates how to set up a beacon on an encrypted attribute,
20+
put an item with the beacon, and query against that beacon.
21+
This example follows a use case of a database that stores unit inspection information.
22+
23+
Running this example requires access to a DDB table with the
24+
following key configuration:
25+
- Partition key is named "work_id" with type (S)
26+
- Sort key is named "inspection_date" with type (S)
27+
This table must have a Global Secondary Index (GSI) configured named "last4-unit-index":
28+
- Partition key is named "aws_dbe_b_inspector_id_last4" with type (S)
29+
- Sort key is named "aws_dbe_b_unit" with type (S)
30+
31+
In this example for storing unit inspection information, this schema is utilized for the data:
32+
- "work_id" stores a unique identifier for a unit inspection work order (v4 UUID)
33+
- "inspection_date" stores an ISO 8601 date for the inspection (YYYY-MM-DD)
34+
- "inspector_id_last4" stores the last 4 digits of the ID of the inspector performing the work
35+
- "unit" stores a 12-digit serial number for the unit being inspected
36+
37+
The example requires the following ordered input command line parameters:
38+
1. DDB table name for table to put/query data from
39+
2. Branch key ID for a branch key that was previously created in your key store. See the
40+
CreateKeyStoreKeyExample.
41+
3. Branch key wrapping KMS key ARN for the KMS key used to create the branch key with ID
42+
provided in arg 2
43+
4. Branch key DDB table name for the DDB table representing the branch key store
44+
*/
45+
46+
const GSI_NAME : &str = "last4-unit-index";
47+
48+
pub async fn put_and_query_with_beacon(branch_key_id : &str)
49+
{
50+
let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME;
51+
let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN;
52+
let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME;
53+
54+
// 1. Configure Beacons.
55+
// The beacon name must be the name of a table attribute that will be encrypted.
56+
// The `length` parameter dictates how many bits are in the beacon attribute value.
57+
// The following link provides guidance on choosing a beacon length:
58+
// https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
59+
let mut standard_beacon_list = Vec::new();
60+
61+
// The configured DDB table has a GSI on the `aws_dbe_b_inspector_id_last4` AttributeName.
62+
// This field holds the last 4 digits of an inspector ID.
63+
// For our example, this field may range from 0 to 9,999 (10,000 possible values).
64+
// For our example, we assume a full inspector ID is an integer
65+
// ranging from 0 to 99,999,999. We do not assume that the full inspector ID's
66+
// values are uniformly distributed across its range of possible values.
67+
// In many use cases, the prefix of an identifier encodes some information
68+
// about that identifier (e.g. zipcode and SSN prefixes encode geographic
69+
// information), while the suffix does not and is more uniformly distributed.
70+
// We will assume that the inspector ID field matches a similar use case.
71+
// So for this example, we only store and use the last
72+
// 4 digits of the inspector ID, which we assume is uniformly distributed.
73+
// Since the full ID's range is divisible by the range of the last 4 digits,
74+
// then the last 4 digits of the inspector ID are uniformly distributed
75+
// over the range from 0 to 9,999.
76+
// See our documentation for why you should avoid creating beacons over non-uniform distributions
77+
// https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me
78+
// A single inspector ID suffix may be assigned to multiple `work_id`s.
79+
//
80+
// This link provides guidance for choosing a beacon length:
81+
// https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
82+
// We follow the guidance in the link above to determine reasonable bounds
83+
// for the length of a beacon on the last 4 digits of an inspector ID:
84+
// - min: log(sqrt(10,000))/log(2) ~= 6.6, round up to 7
85+
// - max: log((10,000/2))/log(2) ~= 12.3, round down to 12
86+
// You will somehow need to round results to a nearby integer.
87+
// We choose to round to the nearest integer; you might consider a different rounding approach.
88+
// Rounding up will return fewer expected "false positives" in queries,
89+
// leading to fewer decrypt calls and better performance,
90+
// but it is easier to identify which beacon values encode distinct plaintexts.
91+
// Rounding down will return more expected "false positives" in queries,
92+
// leading to more decrypt calls and worse performance,
93+
// but it is harder to identify which beacon values encode distinct plaintexts.
94+
// We can choose a beacon length between 7 and 12:
95+
// - Closer to 7, we expect more "false positives" to be returned,
96+
// making it harder to identify which beacon values encode distinct plaintexts,
97+
// but leading to more decrypt calls and worse performance
98+
// - Closer to 12, we expect fewer "false positives" returned in queries,
99+
// leading to fewer decrypt calls and better performance,
100+
// but it is easier to identify which beacon values encode distinct plaintexts.
101+
// As an example, we will choose 10.
102+
//
103+
// Values stored in aws_dbe_b_inspector_id_last4 will be 10 bits long (0x000 - 0x3ff)
104+
// There will be 2^10 = 1,024 possible HMAC values.
105+
// With a sufficiently large number of well-distributed inspector IDs,
106+
// for a particular beacon we expect (10,000/1,024) ~= 9.8 4-digit inspector ID suffixes
107+
// sharing that beacon value.
108+
let last4_beacon = StandardBeacon::builder().name("inspector_id_last4").length(10).build();
109+
standard_beacon_list.push(last4_beacon);
110+
111+
// The configured DDB table has a GSI on the `aws_dbe_b_unit` AttributeName.
112+
// This field holds a unit serial number.
113+
// For this example, this is a 12-digit integer from 0 to 999,999,999,999 (10^12 possible values).
114+
// We will assume values for this attribute are uniformly distributed across this range.
115+
// A single unit serial number may be assigned to multiple `work_id`s.
116+
//
117+
// This link provides guidance for choosing a beacon length:
118+
// https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
119+
// We follow the guidance in the link above to determine reasonable bounds
120+
// for the length of a beacon on a unit serial number:
121+
// - min: log(sqrt(999,999,999,999))/log(2) ~= 19.9, round up to 20
122+
// - max: log((999,999,999,999/2))/log(2) ~= 38.9, round up to 39
123+
// We can choose a beacon length between 20 and 39:
124+
// - Closer to 20, we expect more "false positives" to be returned,
125+
// making it harder to identify which beacon values encode distinct plaintexts,
126+
// but leading to more decrypt calls and worse performance
127+
// - Closer to 39, we expect fewer "false positives" returned in queries,
128+
// leading to fewer decrypt calls and better performance,
129+
// but it is easier to identify which beacon values encode distinct plaintexts.
130+
// As an example, we will choose 30.
131+
//
132+
// Values stored in aws_dbe_b_unit will be 30 bits long (0x00000000 - 0x3fffffff)
133+
// There will be 2^30 = 1,073,741,824 ~= 1.1B possible HMAC values.
134+
// With a sufficiently large number of well-distributed inspector IDs,
135+
// for a particular beacon we expect (10^12/2^30) ~= 931.3 unit serial numbers
136+
// sharing that beacon value.
137+
let unit_beacon = StandardBeacon::builder().name("unit").length(30).build();
138+
standard_beacon_list.push(unit_beacon);
139+
140+
// 2. Configure Keystore.
141+
// The keystore is a separate DDB table where the client stores encryption and decryption materials.
142+
// In order to configure beacons on the DDB client, you must configure a keystore.
143+
//
144+
// This example expects that you have already set up a KeyStore with a single branch key.
145+
// See the "Create KeyStore Table Example" and "Create KeyStore Key Example" for how to do this.
146+
// After you create a branch key, you should persist its ID for use in this example.
147+
let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
148+
let key_store_config = KeyStoreConfig::builder()
149+
.kms_client(aws_sdk_kms::Client::new(&sdk_config))
150+
.ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
151+
.ddb_table_name(branch_key_ddb_table_name)
152+
.logical_key_store_name(branch_key_ddb_table_name)
153+
.kms_configuration(KMSConfiguration.builder().kms_key_arn(branch_key_wrapping_kms_key_arn).build().unwrap())
154+
.build()
155+
.unwrap();
156+
157+
/*
158+
// 3. Create BeaconVersion.
159+
// The BeaconVersion inside the list holds the list of beacons on the table.
160+
// The BeaconVersion also stores information about the keystore.
161+
// BeaconVersion must be provided:
162+
// - keyStore: The keystore configured in step 2.
163+
// - keySource: A configuration for the key source.
164+
// For simple use cases, we can configure a 'singleKeySource' which
165+
// statically configures a single beaconKey. That is the approach this example takes.
166+
// For use cases where you want to use different beacon keys depending on the data
167+
// (for example if your table holds data for multiple tenants, and you want to use
168+
// a different beacon key per tenant), look into configuring a MultiKeyStore:
169+
// https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption-multitenant.html
170+
var beaconVersions = new List<BeaconVersion>
171+
{
172+
new BeaconVersion
173+
{
174+
StandardBeacons = standardBeaconList,
175+
Version = 1, // MUST be 1
176+
KeyStore = keyStore,
177+
KeySource = new BeaconKeySource
178+
{
179+
Single = new SingleKeyStore
180+
{
181+
// `keyId` references a beacon key.
182+
// For every branch key we create in the keystore,
183+
// we also create a beacon key.
184+
// This beacon key is not the same as the branch key,
185+
// but is created with the same ID as the branch key.
186+
KeyId = branchKeyId,
187+
CacheTTL = 6000
188+
}
189+
}
190+
}
191+
};
192+
193+
// 4. Create a Hierarchical Keyring
194+
// This is a KMS keyring that utilizes the keystore table.
195+
// This config defines how items are encrypted and decrypted.
196+
// NOTE: You should configure this to use the same keystore as your search config.
197+
var matProv = new MaterialProviders(new MaterialProvidersConfig());
198+
var keyringInput = new CreateAwsKmsHierarchicalKeyringInput
199+
{
200+
BranchKeyId = branchKeyId,
201+
KeyStore = keyStore,
202+
TtlSeconds = 6000L
203+
};
204+
var kmsKeyring = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput);
205+
206+
// 5. Configure which attributes are encrypted and/or signed when writing new items.
207+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
208+
// we must explicitly configure how they should be treated during item encryption:
209+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
210+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
211+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
212+
// Any attributes that will be used in beacons must be configured as ENCRYPT_AND_SIGN.
213+
var attributeActionsOnEncrypt = new Dictionary<String, CryptoAction>
214+
{
215+
["work_id"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY
216+
["inspection_date"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY
217+
["inspector_id_last4"] = CryptoAction.ENCRYPT_AND_SIGN, // Beaconized attributes must be encrypted
218+
["unit"] = CryptoAction.ENCRYPT_AND_SIGN // Beaconized attributes must be encrypted
219+
};
220+
221+
// 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
222+
// The beaconVersions are added to the search configuration.
223+
var tableConfigs = new Dictionary<String, DynamoDbTableEncryptionConfig>
224+
{
225+
[ddbTableName] = new DynamoDbTableEncryptionConfig
226+
{
227+
LogicalTableName = ddbTableName,
228+
PartitionKeyName = "work_id",
229+
SortKeyName = "inspection_date",
230+
AttributeActionsOnEncrypt = attributeActionsOnEncrypt,
231+
Keyring = kmsKeyring,
232+
Search = new SearchConfig
233+
{
234+
WriteVersion = 1, // MUST be 1
235+
Versions = beaconVersions
236+
}
237+
}
238+
};
239+
240+
// 7. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs
241+
var ddb = new Client.DynamoDbClient(
242+
new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs });
243+
244+
// 8. Put an item into our table using the above client.
245+
// Before the item gets sent to DynamoDb, it will be encrypted
246+
// client-side, according to our configuration.
247+
// Since our configuration includes beacons for `inspector_id_last4` and `unit`,
248+
// the client will add two additional attributes to the item. These attributes will have names
249+
// `aws_dbe_b_inspector_id_last4` and `aws_dbe_b_unit`. Their values will be HMACs
250+
// truncated to as many bits as the beacon's `length` parameter; e.g.
251+
// aws_dbe_b_inspector_id_last4 = truncate(HMAC("4321"), 10)
252+
// aws_dbe_b_unit = truncate(HMAC("123456789012"), 30)
253+
var item = new Dictionary<String, AttributeValue>
254+
{
255+
["work_id"] = new AttributeValue("1313ba89-5661-41eb-ba6c-cb1b4cb67b2d"),
256+
["inspection_date"] = new AttributeValue("2023-06-13"),
257+
["inspector_id_last4"] = new AttributeValue("4321"),
258+
["unit"] = new AttributeValue("123456789012")
259+
};
260+
261+
var putRequest = new PutItemRequest
262+
{
263+
TableName = ddbTableName,
264+
Item = item
265+
};
266+
267+
var putResponse = await ddb.PutItemAsync(putRequest);
268+
Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK);
269+
270+
// 10. Query for the item we just put.
271+
// Note that we are constructing the query as if we were querying on plaintext values.
272+
// However, the DDB encryption client will detect that this attribute name has a beacon configured.
273+
// The client will add the beaconized attribute name and attribute value to the query,
274+
// and transform the query to use the beaconized name and value.
275+
// Internally, the client will query for and receive all items with a matching HMAC value in the beacon field.
276+
// This may include a number of "false positives" with different ciphertext, but the same truncated HMAC.
277+
// e.g. if truncate(HMAC("123456789012"), 30)
278+
// == truncate(HMAC("098765432109"), 30),
279+
// the query will return both items.
280+
// The client will decrypt all returned items to determine which ones have the expected attribute values,
281+
// and only surface items with the correct plaintext to the user.
282+
// This procedure is internal to the client and is abstracted away from the user;
283+
// e.g. the user will only see "123456789012" and never
284+
// "098765432109", though the actual query returned both.
285+
var expressionAttributesNames = new Dictionary<String, String>
286+
{
287+
["#last4"] = "inspector_id_last4",
288+
["#unit"] = "unit"
289+
};
290+
291+
var expressionAttributeValues = new Dictionary<String, AttributeValue>
292+
{
293+
[":last4"] = new AttributeValue("4321"),
294+
[":unit"] = new AttributeValue("123456789012")
295+
};
296+
297+
var queryRequest = new QueryRequest
298+
{
299+
TableName = ddbTableName,
300+
IndexName = GSI_NAME,
301+
KeyConditionExpression = "#last4 = :last4 and #unit = :unit",
302+
ExpressionAttributeNames = expressionAttributesNames,
303+
ExpressionAttributeValues = expressionAttributeValues
304+
};
305+
306+
// GSIs do not update instantly
307+
// so if the results come back empty
308+
// we retry after a short sleep
309+
for (int i = 0; i < 10; ++i)
310+
{
311+
var queryResponse = await ddb.QueryAsync(queryRequest);
312+
var attributeValues = queryResponse.Items;
313+
// Validate query was returned successfully
314+
Debug.Assert(queryResponse.HttpStatusCode == HttpStatusCode.OK);
315+
316+
// if no results, sleep and try again
317+
if (attributeValues.Count == 0)
318+
{
319+
Thread.Sleep(20);
320+
continue;
321+
}
322+
323+
// Validate only 1 item was returned: the item we just put
324+
Debug.Assert(attributeValues.Count == 1);
325+
var returnedItem = attributeValues[0];
326+
// Validate the item has the expected attributes
327+
Debug.Assert(returnedItem["inspector_id_last4"].S.Equals("4321"));
328+
Debug.Assert(returnedItem["unit"].S.Equals("123456789012"));
329+
break;
330+
}
331+
*/
332+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod basic_searchable_encryption;

0 commit comments

Comments
 (0)
Please sign in to comment.