Skip to content

Commit 246aea1

Browse files
chore: add KMS Keyring example in an multithreaded environment for Go (#735)
1 parent c3ba5e3 commit 246aea1

File tree

4 files changed

+217
-2
lines changed

4 files changed

+217
-2
lines changed

AwsEncryptionSDK/runtimes/go/examples/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1
2121
github.com/aws/aws-sdk-go-v2/service/kms v1.36.0
2222
github.com/aws/aws-sdk-go-v2/service/sts v1.31.1
23+
github.com/google/uuid v1.6.0
2324
)
2425

2526
require (
@@ -38,6 +39,5 @@ require (
3839
github.com/aws/smithy-go v1.21.0 // indirect
3940
github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.1 // indirect
4041
github.com/dafny-lang/DafnyStandardLibGo v0.0.0 // indirect
41-
github.com/google/uuid v1.6.0 // indirect
4242
github.com/jmespath/go-jmespath v0.4.0 // indirect
4343
)

AwsEncryptionSDK/runtimes/go/examples/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@ import (
2323
"github.com/aws/aws-encryption-sdk/examples/keyring/rawaeskeyring"
2424
"github.com/aws/aws-encryption-sdk/examples/keyring/rawrsakeyring"
2525
"github.com/aws/aws-encryption-sdk/examples/misc"
26+
"github.com/aws/aws-encryption-sdk/examples/multithreading"
2627
"github.com/aws/aws-encryption-sdk/examples/utils"
2728
)
2829

2930
func main() {
3031
const stringToEncrypt = "Text To encrypt"
32+
const numOfString = 10000
3133
clientsupplier.ClientSupplierExample(
3234
stringToEncrypt,
3335
utils.DefaultRegionMrkKeyArn(),
3436
utils.DefaultKMSKeyAccountID(),
35-
[]string{"eu-west-1"})
37+
[]string{utils.AlternateRegionMrkKeyRegion()})
3638
misc.CommitmentPolicyExample(
3739
stringToEncrypt,
3840
utils.DefaultKMSKeyId(),
@@ -158,4 +160,9 @@ func main() {
158160
utils.DefaultKMSKeyId(),
159161
utils.DefaultKmsKeyRegion(),
160162
)
163+
// Example with multithreading
164+
multithreading.AWSKMSMultiThreadTest(
165+
utils.GenerateUUIDTestData(numOfString),
166+
utils.DefaultKMSKeyId(),
167+
utils.DefaultKmsKeyRegion())
161168
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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 the AWS KMS Keyring in an multithreaded environment.
6+
The AWS KMS keyring uses symmetric encryption KMS keys to generate, encrypt and
7+
decrypt data keys. This example creates a KMS Keyring and then encrypts a custom input exampleText
8+
with an encryption context. This example also includes some sanity checks for demonstration:
9+
1. Ciphertext and plaintext data are not the same
10+
2. Decrypted plaintext value matches exampleText
11+
These sanity checks are for demonstration in the example only. You do not need these in your code.
12+
AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings
13+
of the same or a different type.
14+
For more information on how to use KMS keyrings, see
15+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html
16+
For more information on KMS Key identifiers, see
17+
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
18+
*/
19+
20+
package multithreading
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"sync"
26+
27+
mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated"
28+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
29+
client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated"
30+
esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes"
31+
"github.com/aws/aws-sdk-go-v2/config"
32+
"github.com/aws/aws-sdk-go-v2/service/kms"
33+
)
34+
35+
// Function to handle encryption
36+
func encryptData(
37+
ctx context.Context,
38+
encryptionClient *client.Client,
39+
plaintext string,
40+
encryptionContext map[string]string,
41+
keyring mpltypes.IKeyring) (*esdktypes.EncryptOutput, error) {
42+
res, err := encryptionClient.Encrypt(ctx, esdktypes.EncryptInput{
43+
Plaintext: []byte(plaintext),
44+
EncryptionContext: encryptionContext,
45+
Keyring: keyring,
46+
})
47+
return res, err
48+
}
49+
50+
// Function to handle decryption
51+
func decryptData(
52+
ctx context.Context,
53+
encryptionClient *client.Client,
54+
ciphertext []byte,
55+
encryptionContext map[string]string,
56+
keyring mpltypes.IKeyring) (*esdktypes.DecryptOutput, error) {
57+
res, err := encryptionClient.Decrypt(ctx, esdktypes.DecryptInput{
58+
EncryptionContext: encryptionContext,
59+
Keyring: keyring,
60+
Ciphertext: ciphertext,
61+
})
62+
return res, err
63+
}
64+
65+
func processEncryptionWorker(
66+
ctx context.Context,
67+
wg *sync.WaitGroup,
68+
jobs <-chan string,
69+
encryptionClient *client.Client,
70+
awsKmsKeyring mpltypes.IKeyring,
71+
encryptionContext map[string]string,
72+
) {
73+
defer wg.Done()
74+
for plaintext := range jobs {
75+
// Perform encryption
76+
encryptResult, err := encryptData(
77+
ctx,
78+
encryptionClient,
79+
plaintext,
80+
encryptionContext,
81+
awsKmsKeyring)
82+
if err != nil {
83+
panic(err)
84+
}
85+
// Verify ciphertext is different from plaintext
86+
if string(encryptResult.Ciphertext) == plaintext {
87+
panic("Ciphertext and Plaintext before encryption are the same")
88+
}
89+
// Perform decryption
90+
decryptResult, err := decryptData(
91+
ctx,
92+
encryptionClient,
93+
encryptResult.Ciphertext,
94+
encryptionContext,
95+
awsKmsKeyring,
96+
)
97+
if err != nil {
98+
panic(err)
99+
}
100+
// If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches.
101+
// The encryption context was specified on decrypt; we are validating the encryption context for demonstration only.
102+
// Before your application uses plaintext data, verify that the encryption context that
103+
// you used to encrypt the message is included in the encryption context that was used to
104+
// decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match.
105+
if err := validateEncryptionContext(encryptionContext, decryptResult.EncryptionContext); err != nil {
106+
panic(err)
107+
}
108+
if string(decryptResult.Plaintext) != plaintext {
109+
panic("Plaintext after decryption and Plaintext before encryption are NOT the same")
110+
}
111+
}
112+
}
113+
114+
func AWSKMSMultiThreadTest(texts []string, defaultKmsKeyID, defaultKmsKeyRegion string) {
115+
// Create the AWS KMS client
116+
cfg, err := config.LoadDefaultConfig(context.TODO())
117+
if err != nil {
118+
panic(err)
119+
}
120+
kmsClient := kms.NewFromConfig(cfg, func(o *kms.Options) {
121+
o.Region = defaultKmsKeyRegion
122+
})
123+
// Initialize the mpl client
124+
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{})
125+
if err != nil {
126+
panic(err)
127+
}
128+
// Create the keyring
129+
ctx := context.Background()
130+
awsKmsKeyringInput := mpltypes.CreateAwsKmsKeyringInput{
131+
KmsClient: kmsClient,
132+
KmsKeyId: defaultKmsKeyID,
133+
}
134+
awsKmsKeyring, err := matProv.CreateAwsKmsKeyring(ctx, awsKmsKeyringInput)
135+
if err != nil {
136+
panic(err)
137+
}
138+
// Instantiate the encryption SDK client.
139+
// This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
140+
// which enforces that this client only encrypts using committing algorithm suites and enforces
141+
// that this client will only decrypt encrypted messages that were created with a committing
142+
// algorithm suite.
143+
encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{})
144+
if err != nil {
145+
panic(err)
146+
}
147+
// Create your encryption context (Optional).
148+
// Remember that your encryption context is NOT SECRET.
149+
// https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
150+
encryptionContext := map[string]string{
151+
"encryption": "context",
152+
"is not": "secret",
153+
"but adds": "useful metadata",
154+
"that can help you": "be confident that",
155+
"the data you are handling": "is what you think it is",
156+
}
157+
// Create buffered channels to handle multiple operations
158+
// As an example, we will have 10 workers, adjust this number as needed.
159+
numWorkers := 10
160+
161+
// Create a wait group to track all goroutines
162+
var wg sync.WaitGroup
163+
164+
// Create a channel to send a plaintext
165+
jobs := make(chan string, len(texts))
166+
167+
// Start worker pool
168+
for range numWorkers {
169+
wg.Add(1)
170+
go processEncryptionWorker(ctx, &wg, jobs, encryptionClient, awsKmsKeyring, encryptionContext)
171+
}
172+
173+
// Send jobs to workers
174+
for _, text := range texts {
175+
jobs <- text
176+
}
177+
close(jobs)
178+
// Wait for all workers to complete
179+
wg.Wait()
180+
fmt.Println("AWS KMS Keyring example in multithreaded environment completed successfully.")
181+
}
182+
183+
// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match.
184+
func validateEncryptionContext(expected, actual map[string]string) error {
185+
for expectedKey, expectedValue := range expected {
186+
actualValue, exists := actual[expectedKey]
187+
if !exists || actualValue != expectedValue {
188+
return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'",
189+
expectedKey, expectedValue)
190+
}
191+
}
192+
return nil
193+
}

AwsEncryptionSDK/runtimes/go/examples/utils/exampleUtils.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
package utils
25

36
import (
@@ -13,6 +16,7 @@ import (
1316

1417
"github.com/aws/aws-cryptographic-material-providers-library/primitives/awscryptographyprimitivessmithygeneratedtypes"
1518
"github.com/aws/aws-sdk-go-v2/service/kms"
19+
"github.com/google/uuid"
1620
)
1721

1822
const (
@@ -319,3 +323,14 @@ func GenerateKmsEccPublicKey(eccKeyArn string, kmsClient *kms.Client) ([]byte, e
319323
}
320324
return response.PublicKey, nil
321325
}
326+
327+
// GenerateUUIDTestData creates an array of random UUID strings
328+
func GenerateUUIDTestData(count int) []string {
329+
testData := make([]string, count)
330+
for i := 0; i < count; i++ {
331+
// Generate a random UUID
332+
uuid := uuid.New()
333+
testData[i] = uuid.String()
334+
}
335+
return testData
336+
}

0 commit comments

Comments
 (0)