Skip to content

Commit ed92899

Browse files
auto commit
1 parent af51040 commit ed92899

File tree

4 files changed

+325
-29
lines changed

4 files changed

+325
-29
lines changed

Examples/runtimes/go/keyring/awskmsrsakeyring.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func KmsRsaKeyringExample(ddbTableName, rsaKeyArn, rsaPublicKeyFilename string)
5757
// You may provide your own RSA public key at rsaPublicKeyFilename.
5858
// This must be the public key for the RSA key represented at rsaKeyArn.
5959
// If this file is not present, this will write a UTF-8 encoded PEM file for you.
60-
if shouldGetNewPublicKey(rsaPublicKeyFilename) {
60+
if utils.FileExists(rsaPublicKeyFilename) {
6161
writePublicKeyPemForRsaKey(rsaKeyArn, rsaPublicKeyFilename)
6262
}
6363

@@ -213,22 +213,7 @@ func KmsRsaKeyringExample(ddbTableName, rsaKeyArn, rsaPublicKeyFilename string)
213213
fmt.Println("AWS KMS RSA Keyring Example successful.")
214214
}
215215

216-
func shouldGetNewPublicKey(rsaPublicKeyFilename string) bool {
217-
// Check if a public key file already exists
218-
if _, err := os.Stat(rsaPublicKeyFilename); err == nil {
219-
// If a public key file already exists: do not overwrite existing file
220-
return false
221-
}
222-
// If file is not present, generate a new key pair
223-
return true
224-
}
225-
226216
func writePublicKeyPemForRsaKey(rsaKeyArn, rsaPublicKeyFilename string) {
227-
// Safety check: Validate file is not present
228-
if _, err := os.Stat(rsaPublicKeyFilename); err == nil {
229-
panic("writePublicKeyPemForRsaKey will not overwrite existing PEM files")
230-
}
231-
232217
// This code will call KMS to get the public key for the KMS RSA key.
233218
// You must have kms:GetPublicKey permissions on the key for this to succeed.
234219
// The public key will be written to the file rsaPublicKeyFilename.
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package keyring
5+
6+
import (
7+
"context"
8+
"crypto/rand"
9+
"crypto/rsa"
10+
"crypto/x509"
11+
"encoding/pem"
12+
"fmt"
13+
"os"
14+
"reflect"
15+
16+
mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated"
17+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
18+
dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes"
19+
dbesdkstructuredencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkstructuredencryptionsmithygeneratedtypes"
20+
"github.com/aws/aws-database-encryption-sdk-dynamodb/dbesdkmiddleware"
21+
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/utils"
22+
"github.com/aws/aws-sdk-go-v2/aws"
23+
"github.com/aws/aws-sdk-go-v2/config"
24+
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
25+
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
26+
)
27+
28+
/*
29+
This example sets up DynamoDb Encryption for the AWS SDK client
30+
using the raw RSA Keyring. This keyring uses an RSA key pair to
31+
encrypt and decrypt records. This keyring accepts PEM encodings of
32+
the key pair as UTF-8 interpreted bytes. The client uses the public key
33+
to encrypt items it adds to the table and uses the private key to decrypt
34+
existing table items it retrieves.
35+
36+
This example loads a key pair from PEM files with paths defined in
37+
- exampleRsaPrivateKeyFilename
38+
- exampleRsaPublicKeyFilename
39+
If you do not provide these files, running this example through this
40+
class' main method will generate these files for you. These files will
41+
be generated in the directory where the example is run.
42+
In practice, users of this library should not generate new key pairs
43+
like this, and should instead retrieve an existing key from a secure
44+
key management system (e.g. an HSM).
45+
You may also provide your own key pair by placing PEM files in the
46+
directory where the example is run or modifying the paths in the code
47+
below. These files must be valid PEM encodings of the key pair as UTF-8
48+
encoded bytes. If you do provide your own key pair, or if a key pair
49+
already exists, this class' main method will not generate a new key pair.
50+
51+
This example loads a key pair from disk, encrypts a test item, and puts the
52+
encrypted item to the provided DynamoDb table. Then, it gets the
53+
item from the table and decrypts it.
54+
55+
Running this example requires access to the DDB Table whose name
56+
is provided in CLI arguments.
57+
This table must be configured with the following
58+
primary key configuration:
59+
- Partition key is named "partition_key" with type (S)
60+
- Sort key is named "sort_key" with type (S)
61+
*/
62+
63+
func RawRsaKeyringExample(ddbTableName, exampleRsaPublicKeyFilename, exampleRsaPrivateKeyFilename string) {
64+
// You may provide your own RSA key pair in the files located at
65+
// - exampleRsaPrivateKeyFilename
66+
// - exampleRsaPublicKeyFilename
67+
// If these files are not present, this will generate a pair for you
68+
if shouldGenerateNewRsaKeyPair(exampleRsaPublicKeyFilename, exampleRsaPrivateKeyFilename) {
69+
generateRsaKeyPair(exampleRsaPublicKeyFilename, exampleRsaPrivateKeyFilename)
70+
}
71+
72+
// 1. Load key pair from UTF-8 encoded PEM files.
73+
// You may provide your own PEM files to use here.
74+
// If you do not, the main method in this class will generate PEM
75+
// files for example use. Do not use these files for any other purpose.
76+
publicKeyUtf8EncodedBytes, err := os.ReadFile(exampleRsaPublicKeyFilename)
77+
utils.HandleError(err)
78+
79+
privateKeyUtf8EncodedBytes, err := os.ReadFile(exampleRsaPrivateKeyFilename)
80+
utils.HandleError(err)
81+
82+
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{})
83+
utils.HandleError(err)
84+
85+
// 2. Create the keyring.
86+
// The DynamoDb encryption client uses this to encrypt and decrypt items.
87+
keyringInput := mpltypes.CreateRawRsaKeyringInput{
88+
KeyName: "my-rsa-key-name",
89+
KeyNamespace: "my-key-namespace",
90+
PaddingScheme: mpltypes.PaddingSchemeOaepSha256Mgf1,
91+
PublicKey: publicKeyUtf8EncodedBytes,
92+
PrivateKey: privateKeyUtf8EncodedBytes,
93+
}
94+
rawRsaKeyring, err := matProv.CreateRawRsaKeyring(context.Background(), keyringInput)
95+
utils.HandleError(err)
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+
attributeActionsOnEncrypt := map[string]dbesdkstructuredencryptiontypes.CryptoAction{
104+
"partition_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our partition attribute must be SIGN_ONLY
105+
"sort_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our sort attribute must be SIGN_ONLY
106+
"sensitive_data": dbesdkstructuredencryptiontypes.CryptoActionEncryptAndSign,
107+
}
108+
109+
// 4. Configure which attributes we expect to be included in the signature
110+
// when reading items. There are two options for configuring this:
111+
//
112+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
113+
// When defining your DynamoDb schema and deciding on attribute names,
114+
// choose a distinguishing prefix (such as ":") for all attributes that
115+
// you do not want to include in the signature.
116+
// This has two main benefits:
117+
// - It is easier to reason about the security and authenticity of data within your item
118+
// when all unauthenticated data is easily distinguishable by their attribute name.
119+
// - If you need to add new unauthenticated attributes in the future,
120+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
121+
// and immediately start writing to that new attribute, without
122+
// any other configuration update needed.
123+
// Once you configure this field, it is not safe to update it.
124+
//
125+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
126+
// a set of attributes that should be considered unauthenticated when encountered
127+
// on read. Be careful if you use this configuration. Do not remove an attribute
128+
// name from this configuration, even if you are no longer writing with that attribute,
129+
// as old items may still include this attribute, and our configuration needs to know
130+
// to continue to exclude this attribute from the signature scope.
131+
// If you add new attribute names to this field, you must first deploy the update to this
132+
// field to all readers in your host fleet before deploying the update to start writing
133+
// with that new attribute.
134+
//
135+
// For this example, we currently authenticate all attributes. To make it easier to
136+
// add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
137+
unsignedAttrPrefix := ":"
138+
139+
// 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
140+
partitionKey := "partition_key"
141+
sortKeyName := "sort_key"
142+
tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
143+
LogicalTableName: ddbTableName,
144+
PartitionKeyName: partitionKey,
145+
SortKeyName: &sortKeyName,
146+
AttributeActionsOnEncrypt: attributeActionsOnEncrypt,
147+
Keyring: rawRsaKeyring,
148+
AllowedUnsignedAttributePrefix: &unsignedAttrPrefix,
149+
}
150+
tableConfigsMap := make(map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig)
151+
tableConfigsMap[ddbTableName] = tableConfig
152+
listOfTableConfigs := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
153+
TableEncryptionConfigs: tableConfigsMap,
154+
}
155+
156+
// 6. Create a new AWS SDK DynamoDb client using the Config above
157+
158+
// Create DBESDK middleware
159+
dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(listOfTableConfigs)
160+
utils.HandleError(err)
161+
// Create aws config
162+
cfg, err := config.LoadDefaultConfig(context.TODO())
163+
utils.HandleError(err)
164+
ddb := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware())
165+
166+
// 7. Put an item into our table using the above client.
167+
// Before the item gets sent to DynamoDb, it will be encrypted
168+
// client-side, according to our configuration.
169+
item := map[string]types.AttributeValue{
170+
"partition_key": &types.AttributeValueMemberS{Value: "rawRsaKeyringItem"},
171+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
172+
"sensitive_data": &types.AttributeValueMemberS{Value: "encrypt and sign me!"},
173+
}
174+
putInput := &dynamodb.PutItemInput{
175+
TableName: aws.String(ddbTableName),
176+
Item: item,
177+
}
178+
_, err = ddb.PutItem(context.TODO(), putInput)
179+
utils.HandleError(err)
180+
181+
// 8. Get the item back from our table using the same client.
182+
// The client will decrypt the item client-side, and return
183+
// back the original item.
184+
key := map[string]types.AttributeValue{
185+
"partition_key": &types.AttributeValueMemberS{Value: "rawRsaKeyringItem"},
186+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
187+
}
188+
getInput := &dynamodb.GetItemInput{
189+
TableName: aws.String(ddbTableName),
190+
Key: key,
191+
// In this example we configure a strongly consistent read
192+
// because we perform a read immediately after a write (for demonstrative purposes).
193+
// By default, reads are only eventually consistent.
194+
// Read our docs to determine which read consistency to use for your application:
195+
// https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html
196+
ConsistentRead: aws.Bool(true),
197+
}
198+
result, err := ddb.GetItem(context.TODO(), getInput)
199+
utils.HandleError(err)
200+
201+
// Demonstrate that GetItem succeeded and returned the decrypted item
202+
if !reflect.DeepEqual(item, result.Item) {
203+
panic("Decrypted item does not match original item")
204+
}
205+
fmt.Println("Raw RSA Keyring Example successful.")
206+
}
207+
208+
func shouldGenerateNewRsaKeyPair(exampleRsaPublicKeyFilename, exampleRsaPrivateKeyFilename string) bool {
209+
// Check if a key pair already exists
210+
privateKeyExists := utils.FileExists(exampleRsaPrivateKeyFilename)
211+
publicKeyExists := utils.FileExists(exampleRsaPublicKeyFilename)
212+
213+
// If a key pair already exists: do not overwrite existing key pair
214+
if privateKeyExists && publicKeyExists {
215+
return false
216+
}
217+
218+
// If only one file is present: throw exception
219+
if privateKeyExists && !publicKeyExists {
220+
panic("Missing public key file at " + exampleRsaPublicKeyFilename)
221+
}
222+
if !privateKeyExists && publicKeyExists {
223+
panic("Missing private key file at " + exampleRsaPrivateKeyFilename)
224+
}
225+
226+
// If neither file is present, generate a new key pair
227+
return true
228+
}
229+
230+
func generateRsaKeyPair(exampleRsaPublicKeyFilename, exampleRsaPrivateKeyFilename string) {
231+
// Safety check: Validate neither file is present
232+
if utils.FileExists(exampleRsaPrivateKeyFilename) || utils.FileExists(exampleRsaPublicKeyFilename) {
233+
panic("generateRsaKeyPair will not overwrite existing PEM files")
234+
}
235+
236+
// This code will generate a new RSA key pair for example use.
237+
// The public and private key will be written to the files:
238+
// - public: exampleRsaPublicKeyFilename
239+
// - private: exampleRsaPrivateKeyFilename
240+
// This example uses Go's crypto/rsa package to generate the key pair.
241+
// In practice, you should not generate this in your code, and should instead
242+
// retrieve this key from a secure key management system (e.g. HSM)
243+
// This key is created here for example purposes only.
244+
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
245+
utils.HandleError(err)
246+
247+
// Extract public key from the private key
248+
publicKey := &privateKey.PublicKey
249+
250+
// Encode private key to PKCS8 DER format
251+
privateKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey)
252+
utils.HandleError(err)
253+
254+
// Encode public key to PKIX DER format
255+
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
256+
utils.HandleError(err)
257+
258+
// Create PEM blocks
259+
privateKeyBlock := &pem.Block{
260+
Type: "PRIVATE KEY",
261+
Bytes: privateKeyDER,
262+
}
263+
264+
publicKeyBlock := &pem.Block{
265+
Type: "PUBLIC KEY",
266+
Bytes: publicKeyDER,
267+
}
268+
269+
// Write private key to file
270+
privateKeyFile, err := os.Create(exampleRsaPrivateKeyFilename)
271+
utils.HandleError(err)
272+
defer privateKeyFile.Close()
273+
274+
err = pem.Encode(privateKeyFile, privateKeyBlock)
275+
utils.HandleError(err)
276+
277+
// Write public key to file
278+
publicKeyFile, err := os.Create(exampleRsaPublicKeyFilename)
279+
utils.HandleError(err)
280+
defer publicKeyFile.Close()
281+
282+
err = pem.Encode(publicKeyFile, publicKeyBlock)
283+
utils.HandleError(err)
284+
}

Examples/runtimes/go/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ func main() {
4747
utils.DdbTableName(),
4848
utils.TestKmsRsaKeyID(),
4949
utils.DefaultRsaPublicKeyFilename())
50+
keyring.RawRsaKeyringExample(
51+
utils.DdbTableName(),
52+
utils.ExampleRsaPublicKeyFilename(),
53+
utils.ExampleRsaPrivateKeyFilename())
54+
keyring.MrkDiscoveryMultiKeyringExample(
55+
utils.KmsKeyID(),
56+
utils.DdbTableName(),
57+
utils.DefaultKMSKeyAccountID(),
58+
utils.DefaultKmsKeyRegion())
5059

5160
// item encryptor example
5261
itemencryptor.ItemEncryptDecryptExample(

Examples/runtimes/go/utils/exampleUtils.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,36 @@
33

44
package utils
55

6-
import "crypto/rand"
6+
import (
7+
"crypto/rand"
8+
"os"
9+
)
710

811
const (
9-
kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"
10-
ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS"
11-
keyNamespace = "my-key-namespace"
12-
keyName = "my-key-name"
13-
aesKeyBytes = 32 // 256 bits = 32 bytes
14-
testKeystoreName = "KeyStoreDdbTable"
15-
testLogicalKeystoreName = "KeyStoreDdbTable"
16-
testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"
17-
defaultRsaPublicKeyFilename = "KmsRsaKeyringPublicKey.pem"
18-
testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"
19-
defaultKMSKeyAccountID = "658956600833"
20-
defaultKmsKeyRegion = "us-west-2"
12+
kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"
13+
ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS"
14+
keyNamespace = "my-key-namespace"
15+
keyName = "my-key-name"
16+
aesKeyBytes = 32 // 256 bits = 32 bytes
17+
testKeystoreName = "KeyStoreDdbTable"
18+
testLogicalKeystoreName = "KeyStoreDdbTable"
19+
testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"
20+
defaultRsaPublicKeyFilename = "KmsRsaKeyringPublicKey.pem"
21+
testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"
22+
defaultKMSKeyAccountID = "658956600833"
23+
defaultKmsKeyRegion = "us-west-2"
24+
exampleRsaPrivateKeyFilename = "RawRsaKeyringExamplePrivateKey.pem"
25+
exampleRsaPublicKeyFilename = "RawRsaKeyringExamplePublicKey.pem"
2126
)
2227

28+
func ExampleRsaPublicKeyFilename() string {
29+
return exampleRsaPublicKeyFilename
30+
}
31+
32+
func ExampleRsaPrivateKeyFilename() string {
33+
return exampleRsaPrivateKeyFilename
34+
}
35+
2336
func DefaultKMSKeyAccountID() []string {
2437
return []string{defaultKMSKeyAccountID}
2538
}
@@ -94,3 +107,8 @@ func GenerateAes256KeyBytes() []byte {
94107
HandleError(err)
95108
return key
96109
}
110+
111+
func FileExists(filename string) bool {
112+
_, err := os.Stat(filename)
113+
return !os.IsNotExist(err)
114+
}

0 commit comments

Comments
 (0)