Skip to content

Commit e33e92f

Browse files
committed
m
1 parent 8784720 commit e33e92f

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod raw_rsa_keyring;
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::test_utils;
5+
6+
/*
7+
This example sets up DynamoDb Encryption for the AWS SDK client
8+
using the raw RSA Keyring. This keyring uses an RSA key pair to
9+
encrypt and decrypt records. This keyring accepts PEM encodings of
10+
the key pair as UTF-8 interpreted bytes. The client uses the public key
11+
to encrypt items it adds to the table and uses the private key to decrypt
12+
existing table items it retrieves.
13+
14+
This example loads a key pair from PEM files with paths defined in
15+
- EXAMPLE_RSA_PRIVATE_KEY_FILENAME
16+
- EXAMPLE_RSA_PUBLIC_KEY_FILENAME
17+
If you do not provide these files, running this example through this
18+
class' main method will generate these files for you. These files will
19+
be generated in the directory where the example is run.
20+
In practice, users of this library should not generate new key pairs
21+
like this, and should instead retrieve an existing key from a secure
22+
key management system (e.g. an HSM).
23+
You may also provide your own key pair by placing PEM files in the
24+
directory where the example is run or modifying the paths in the code
25+
below. These files must be valid PEM encodings of the key pair as UTF-8
26+
encoded bytes. If you do provide your own key pair, or if a key pair
27+
already exists, this class' main method will not generate a new key pair.
28+
29+
This example loads a key pair from disk, encrypts a test item, and puts the
30+
encrypted item to the provided DynamoDb table. Then, it gets the
31+
item from the table and decrypts it.
32+
33+
Running this example requires access to the DDB Table whose name
34+
is provided in CLI arguments.
35+
This table must be configured with the following
36+
primary key configuration:
37+
- Partition key is named "partition_key" with type (S)
38+
- Sort key is named "sort_key" with type (S)
39+
*/
40+
41+
42+
// const EXAMPLE_RSA_PRIVATE_KEY_FILENAME : &str = "RawRsaKeyringExamplePrivateKey.pem";
43+
// const EXAMPLE_RSA_PUBLIC_KEY_FILENAME : &str = "RawRsaKeyringExamplePublicKey.pem";
44+
45+
pub async fn put_item_get_item()
46+
{
47+
let _ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
48+
/*
49+
// You may provide your own RSA key pair in the files located at
50+
// - EXAMPLE_RSA_PRIVATE_KEY_FILENAME
51+
// - EXAMPLE_RSA_PUBLIC_KEY_FILENAME
52+
// If these files are not present, this will generate a pair for you
53+
if (RawRsaKeyringExample.ShouldGenerateNewRsaKeyPair())
54+
{
55+
RawRsaKeyringExample.GenerateRsaKeyPair();
56+
}
57+
58+
// 1. Load key pair from UTF-8 encoded PEM files.
59+
// You may provide your own PEM files to use here.
60+
// If you do not, the main method in this class will generate PEM
61+
// files for example use. Do not use these files for any other purpose.
62+
MemoryStream publicKeyUtf8EncodedByteBuffer;
63+
try
64+
{
65+
publicKeyUtf8EncodedByteBuffer = new MemoryStream(
66+
File.ReadAllBytes(EXAMPLE_RSA_PUBLIC_KEY_FILENAME));
67+
}
68+
catch (IOException e)
69+
{
70+
throw new IOException("Exception while reading public key from file", e);
71+
}
72+
73+
MemoryStream privateKeyUtf8EncodedByteBuffer;
74+
try
75+
{
76+
privateKeyUtf8EncodedByteBuffer = new MemoryStream(
77+
File.ReadAllBytes(EXAMPLE_RSA_PRIVATE_KEY_FILENAME));
78+
}
79+
catch (IOException e)
80+
{
81+
throw new IOException("Exception while reading private key from file", e);
82+
}
83+
84+
// 2. Create the keyring.
85+
// The DynamoDb encryption client uses this to encrypt and decrypt items.
86+
var keyringInput = new CreateRawRsaKeyringInput
87+
{
88+
KeyName = "my-rsa-key-name",
89+
KeyNamespace = "my-key-namespace",
90+
PaddingScheme = PaddingScheme.OAEP_SHA256_MGF1,
91+
PublicKey = publicKeyUtf8EncodedByteBuffer,
92+
PrivateKey = privateKeyUtf8EncodedByteBuffer
93+
};
94+
var matProv = new MaterialProviders(new MaterialProvidersConfig());
95+
var rawRsaKeyring = matProv.CreateRawRsaKeyring(keyringInput);
96+
97+
// 3. Configure which attributes are encrypted and/or signed when writing new items.
98+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
99+
// we must explicitly configure how they should be treated during item encryption:
100+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
101+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
102+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
103+
var attributeActionsOnEncrypt = new Dictionary<String, CryptoAction>
104+
{
105+
["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY
106+
["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY
107+
["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN
108+
};
109+
110+
// 4. Configure which attributes we expect to be included in the signature
111+
// when reading items. There are two options for configuring this:
112+
//
113+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
114+
// When defining your DynamoDb schema and deciding on attribute names,
115+
// choose a distinguishing prefix (such as ":") for all attributes that
116+
// you do not want to include in the signature.
117+
// This has two main benefits:
118+
// - It is easier to reason about the security and authenticity of data within your item
119+
// when all unauthenticated data is easily distinguishable by their attribute name.
120+
// - If you need to add new unauthenticated attributes in the future,
121+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
122+
// and immediately start writing to that new attribute, without
123+
// any other configuration update needed.
124+
// Once you configure this field, it is not safe to update it.
125+
//
126+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
127+
// a set of attributes that should be considered unauthenticated when encountered
128+
// on read. Be careful if you use this configuration. Do not remove an attribute
129+
// name from this configuration, even if you are no longer writing with that attribute,
130+
// as old items may still include this attribute, and our configuration needs to know
131+
// to continue to exclude this attribute from the signature scope.
132+
// If you add new attribute names to this field, you must first deploy the update to this
133+
// field to all readers in your host fleet before deploying the update to start writing
134+
// with that new attribute.
135+
//
136+
// For this example, we currently authenticate all attributes. To make it easier to
137+
// add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
138+
const String unsignAttrPrefix = ":";
139+
140+
// 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
141+
var tableConfigs = new Dictionary<String, DynamoDbTableEncryptionConfig>
142+
{
143+
[ddbTableName] = new DynamoDbTableEncryptionConfig
144+
{
145+
LogicalTableName = ddbTableName,
146+
PartitionKeyName = "partition_key",
147+
SortKeyName = "sort_key",
148+
AttributeActionsOnEncrypt = attributeActionsOnEncrypt,
149+
Keyring = rawRsaKeyring,
150+
AllowedUnsignedAttributePrefix = unsignAttrPrefix
151+
}
152+
};
153+
154+
// 6. Create a new AWS SDK DynamoDb client using the config above
155+
var ddb = new Client.DynamoDbClient(
156+
new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs });
157+
158+
// 8. Put an item into our table using the above client.
159+
// Before the item gets sent to DynamoDb, it will be encrypted
160+
// client-side, according to our configuration.
161+
var item = new Dictionary<String, AttributeValue>
162+
{
163+
["partition_key"] = new AttributeValue("rawRsaKeyringItem"),
164+
["sort_key"] = new AttributeValue { N = "0" },
165+
["sensitive_data"] = new AttributeValue("encrypt and sign me!")
166+
};
167+
168+
var putRequest = new PutItemRequest
169+
{
170+
TableName = ddbTableName,
171+
Item = item
172+
};
173+
174+
var putResponse = await ddb.PutItemAsync(putRequest);
175+
176+
// Demonstrate that PutItem succeeded
177+
Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK);
178+
179+
// 9. Get the item back from our table using the same client.
180+
// The client will decrypt the item client-side, and return
181+
// back the original item.
182+
var keyToGet = new Dictionary<String, AttributeValue>
183+
{
184+
["partition_key"] = new AttributeValue("rawRsaKeyringItem"),
185+
["sort_key"] = new AttributeValue { N = "0" }
186+
};
187+
188+
var getRequest = new GetItemRequest
189+
{
190+
Key = keyToGet,
191+
TableName = ddbTableName
192+
};
193+
194+
var getResponse = await ddb.GetItemAsync(getRequest);
195+
196+
// Demonstrate that GetItem succeeded and returned the decrypted item
197+
Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK);
198+
var returnedItem = getResponse.Item;
199+
Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!"));
200+
}
201+
202+
static bool ShouldGenerateNewRsaKeyPair()
203+
{
204+
// If a key pair already exists: do not overwrite existing key pair
205+
if (File.Exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && File.Exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))
206+
{
207+
return false;
208+
}
209+
210+
// If only one file is present: throw exception
211+
if (File.Exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && !File.Exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))
212+
{
213+
throw new ApplicationException("Missing public key file at " + EXAMPLE_RSA_PUBLIC_KEY_FILENAME);
214+
}
215+
216+
// If a key pair already exists: do not overwrite existing key pair
217+
if (File.Exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && !File.Exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))
218+
{
219+
throw new ApplicationException("Missing private key file at " + EXAMPLE_RSA_PRIVATE_KEY_FILENAME);
220+
}
221+
222+
// If neither file is present, generate a new key pair
223+
return true;
224+
}
225+
226+
static void GenerateRsaKeyPair()
227+
{
228+
// Safety check: Validate neither file is present
229+
if (File.Exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) || File.Exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))
230+
{
231+
throw new ApplicationException("generateRsaKeyPair will not overwrite existing PEM files");
232+
}
233+
234+
// This code will generate a new RSA key pair for example use.
235+
// The public and private key will be written to the files:
236+
// - public: EXAMPLE_RSA_PUBLIC_KEY_FILENAME
237+
// - private: EXAMPLE_RSA_PRIVATE_KEY_FILENAME
238+
// This example uses BouncyCastle's KeyPairGenerator to generate the key pair.
239+
// In practice, you should not generate this in your code, and should instead
240+
// retrieve this key from a secure key management system (e.g. HSM)
241+
// This key is created here for example purposes only.
242+
243+
var r = new RsaKeyPairGenerator();
244+
r.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
245+
var keys = r.GenerateKeyPair();
246+
247+
var privateKeyStringWriter = new StringWriter();
248+
var pemWriter = new PemWriter(privateKeyStringWriter);
249+
pemWriter.WriteObject(keys.Private);
250+
251+
var privateKeyUtf8EncodedByteBuffer = Encoding.UTF8.GetBytes(privateKeyStringWriter.ToString());
252+
var fc = new FileStream(EXAMPLE_RSA_PRIVATE_KEY_FILENAME, FileMode.Create, FileAccess.Write);
253+
fc.Write(privateKeyUtf8EncodedByteBuffer);
254+
fc.Close();
255+
256+
var publicKeyStringWriter = new StringWriter();
257+
var publicKeyPemWriter = new PemWriter(publicKeyStringWriter);
258+
publicKeyPemWriter.WriteObject(keys.Public);
259+
var publicKeyUtf8EncodedByteBuffer = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString());
260+
261+
fc = new FileStream(EXAMPLE_RSA_PUBLIC_KEY_FILENAME, FileMode.Create, FileAccess.Write);
262+
fc.Write(publicKeyUtf8EncodedByteBuffer);
263+
fc.Close();
264+
*/
265+
println!("put_item_get_item successful.");
266+
}

0 commit comments

Comments
 (0)