VYPR
Moderate severityNVD Advisory· Published Dec 27, 2022· Updated Apr 11, 2025

Exposure of unencrypted plaintext hash in github.com/aws/aws-sdk-go

CVE-2022-2582

Description

The AWS S3 Crypto SDK sends an unencrypted hash of the plaintext alongside the ciphertext as a metadata field. This hash can be used to brute force the plaintext, if the hash is readable to the attacker. AWS now blocks this metadata field, but older SDK versions still send it.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

The AWS S3 Crypto SDK for Go (v1) leaks an unencrypted MD5 hash of plaintext in object metadata, enabling offline brute-force recovery of low-entropy plaintext by attackers with read access to S3.

Vulnerability

Details

The AWS S3 Crypto SDK for Go (v1) writes an unencrypted MD5 hash of the plaintext as a metadata field (X-Amz-Meta-X-Amz-Unencrypted-Content-Md5) in S3 objects. This is a design flaw: the hash is stored alongside the ciphertext without encryption, directly violating the principle that cryptographic metadata must not leak information about the plaintext [4].

Exploitation

An attacker who has read access to the S3 bucket (e.g., an insider or via misconfigured IAM policies) can retrieve this metadata field. The attack is an offline brute-force: the attacker guesses potential plaintexts, computes their MD5, and compares them to the stored hash. This is feasible when the plaintext has low entropy (short strings, predictable values) or when the attacker can narrow the candidate space [4]. No decryption key is needed.

Impact

Successful exploitation allows the attacker to recover the plaintext without ever accessing the encryption keys, defeating the confidentiality guarantees of server-side or client-side encryption. AWS acknowledges this as a circumvention of KMS controls and a serious insider risk [4].

Mitigation

AWS has fully mitigated this server-side as of August 5, 2022, by blocking the metadata field. No existing S3 objects are currently affected. However, older versions of the AWS SDK for Go v1 (which is now end-of-support) may still send this field if used to upload new objects. Users are strongly recommended to migrate to AWS SDK for Go v2 [1][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/aws/aws-sdk-goGo
< 1.34.01.34.0

Affected products

12

Patches

1
35fa6ddf45c0

service/s3/s3crypto: V2 Client Release (#3403)

https://github.com/aws/aws-sdk-goSean McGrailJul 1, 2020via ghsa
28 files changed · +1480 263
  • CHANGELOG_PENDING.md+4 0 modified
    @@ -1,4 +1,8 @@
     ### SDK Features
    +* `service/s3/s3crypto`: Introduces `EncryptionClientV2` and `DecryptionClientV2` encryption and decryption clients which support
    +a new key wrapping algorithm `kms+context`. ([#3403](https://github.com/aws/aws-sdk-go/pull/3403))
    +  * `DecryptionClientV2` maintains the ability to decrypt objects encrypted using the `EncryptionClient`.
    +  * Please see `s3crypto` documentation for migration details.
     
     ### SDK Enhancements
     
    
  • internal/sync/singleflight/singleflight_test.go+4 0 modified
    @@ -14,6 +14,7 @@ import (
     )
     
     func TestDo(t *testing.T) {
    +	t.Skip("singleflight tests not stable")
     	var g Group
     	v, err, _ := g.Do("key", func() (interface{}, error) {
     		return "bar", nil
    @@ -27,6 +28,7 @@ func TestDo(t *testing.T) {
     }
     
     func TestDoErr(t *testing.T) {
    +	t.Skip("singleflight tests not stable")
     	var g Group
     	someErr := errors.New("Some error")
     	v, err, _ := g.Do("key", func() (interface{}, error) {
    @@ -41,6 +43,7 @@ func TestDoErr(t *testing.T) {
     }
     
     func TestDoDupSuppress(t *testing.T) {
    +	t.Skip("singleflight tests not stable")
     	var g Group
     	var wg1, wg2 sync.WaitGroup
     	c := make(chan string, 1)
    @@ -89,6 +92,7 @@ func TestDoDupSuppress(t *testing.T) {
     // Test that singleflight behaves correctly after Forget called.
     // See https://github.com/golang/go/issues/31420
     func TestForget(t *testing.T) {
    +	t.Skip("singleflight tests not stable")
     	var g Group
     
     	var firstStarted, firstFinished sync.WaitGroup
    
  • service/s3/s3crypto/aes_cbc_content_cipher.go+6 0 modified
    @@ -15,8 +15,14 @@ type cbcContentCipherBuilder struct {
     	padder    Padder
     }
     
    +func (cbcContentCipherBuilder) isUsingDeprecatedFeatures() error {
    +	return errDeprecatedCipherBuilder
    +}
    +
     // AESCBCContentCipherBuilder returns a new encryption only mode structure with a specific cipher
     // for the master key
    +//
    +// deprecated: This content cipher builder has been deprecated. Users should migrate to AESGCMContentCipherBuilder
     func AESCBCContentCipherBuilder(generator CipherDataGenerator, padder Padder) ContentCipherBuilder {
     	return cbcContentCipherBuilder{generator: generator, padder: padder}
     }
    
  • service/s3/s3crypto/aes_gcm_content_cipher.go+14 2 modified
    @@ -15,6 +15,13 @@ type gcmContentCipherBuilder struct {
     	generator CipherDataGenerator
     }
     
    +func (builder gcmContentCipherBuilder) isUsingDeprecatedFeatures() error {
    +	if feature, ok := builder.generator.(deprecatedFeatures); ok {
    +		return feature.isUsingDeprecatedFeatures()
    +	}
    +	return nil
    +}
    +
     // AESGCMContentCipherBuilder returns a new encryption only mode structure with a specific cipher
     // for the master key
     func AESGCMContentCipherBuilder(generator CipherDataGenerator) ContentCipherBuilder {
    @@ -29,9 +36,14 @@ func (builder gcmContentCipherBuilder) ContentCipherWithContext(ctx aws.Context)
     	var cd CipherData
     	var err error
     
    -	if v, ok := builder.generator.(CipherDataGeneratorWithContext); ok {
    +	switch v := builder.generator.(type) {
    +	case CipherDataGeneratorWithCEKAlgWithContext:
    +		cd, err = v.GenerateCipherDataWithCEKAlgWithContext(ctx, gcmKeySize, gcmNonceSize, AESGCMNoPadding)
    +	case CipherDataGeneratorWithCEKAlg:
    +		cd, err = v.GenerateCipherDataWithCEKAlg(gcmKeySize, gcmNonceSize, AESGCMNoPadding)
    +	case CipherDataGeneratorWithContext:
     		cd, err = v.GenerateCipherDataWithContext(ctx, gcmKeySize, gcmNonceSize)
    -	} else {
    +	default:
     		cd, err = builder.generator.GenerateCipherData(gcmKeySize, gcmNonceSize)
     	}
     	if err != nil {
    
  • service/s3/s3crypto/aes_gcm_content_cipher_test.go+5 0 modified
    @@ -3,6 +3,7 @@ package s3crypto_test
     import (
     	"testing"
     
    +	"github.com/aws/aws-sdk-go/service/kms/kmsiface"
     	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
     )
     
    @@ -26,3 +27,7 @@ func TestAESGCMContentCipherNewEncryptor(t *testing.T) {
     		t.Errorf("expected non-nil vaue")
     	}
     }
    +
    +type mockKMS struct {
    +	kmsiface.KMSAPI
    +}
    
  • service/s3/s3crypto/aes_gcm_test.go+62 6 modified
    @@ -1,8 +1,11 @@
    +// +build go1.9
    +
     package s3crypto
     
     import (
     	"bytes"
     	"encoding/hex"
    +	"encoding/json"
     	"fmt"
     	"io"
     	"io/ioutil"
    @@ -15,7 +18,7 @@ func TestAES_GCM_NIST_gcmEncryptExtIV256_PTLen_128_Test_0(t *testing.T) {
     	iv, _ := hex.DecodeString("0d18e06c7c725ac9e362e1ce")
     	key, _ := hex.DecodeString("31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22")
     	plaintext, _ := hex.DecodeString("2db5168e932556f8089a0622981d017d")
    -	expected, _ := hex.DecodeString("fa4362189661d163fcd6a56d8bf0405ad636ac1bbedd5cc3ee727dc2ab4a9489")
    +	expected, _ := hex.DecodeString("fa4362189661d163fcd6a56d8bf0405a")
     	tag, _ := hex.DecodeString("d636ac1bbedd5cc3ee727dc2ab4a9489")
     	aesgcmTest(t, iv, key, plaintext, expected, tag)
     }
    @@ -24,7 +27,7 @@ func TestAES_GCM_NIST_gcmEncryptExtIV256_PTLen_104_Test_3(t *testing.T) {
     	iv, _ := hex.DecodeString("4742357c335913153ff0eb0f")
     	key, _ := hex.DecodeString("e5a0eb92cc2b064e1bc80891faf1fab5e9a17a9c3a984e25416720e30e6c2b21")
     	plaintext, _ := hex.DecodeString("8499893e16b0ba8b007d54665a")
    -	expected, _ := hex.DecodeString("eb8e6175f1fe38eb1acf95fd5188a8b74bb74fda553e91020a23deed45")
    +	expected, _ := hex.DecodeString("eb8e6175f1fe38eb1acf95fd51")
     	tag, _ := hex.DecodeString("88a8b74bb74fda553e91020a23deed45")
     	aesgcmTest(t, iv, key, plaintext, expected, tag)
     }
    @@ -33,7 +36,7 @@ func TestAES_GCM_NIST_gcmEncryptExtIV256_PTLen_256_Test_6(t *testing.T) {
     	iv, _ := hex.DecodeString("a291484c3de8bec6b47f525f")
     	key, _ := hex.DecodeString("37f39137416bafde6f75022a7a527cc593b6000a83ff51ec04871a0ff5360e4e")
     	plaintext, _ := hex.DecodeString("fafd94cede8b5a0730394bec68a8e77dba288d6ccaa8e1563a81d6e7ccc7fc97")
    -	expected, _ := hex.DecodeString("44dc868006b21d49284016565ffb3979cc4271d967628bf7cdaf86db888e92e501a2b578aa2f41ec6379a44a31cc019c")
    +	expected, _ := hex.DecodeString("44dc868006b21d49284016565ffb3979cc4271d967628bf7cdaf86db888e92e5")
     	tag, _ := hex.DecodeString("01a2b578aa2f41ec6379a44a31cc019c")
     	aesgcmTest(t, iv, key, plaintext, expected, tag)
     }
    @@ -42,11 +45,62 @@ func TestAES_GCM_NIST_gcmEncryptExtIV256_PTLen_408_Test_8(t *testing.T) {
     	iv, _ := hex.DecodeString("92f258071d79af3e63672285")
     	key, _ := hex.DecodeString("595f259c55abe00ae07535ca5d9b09d6efb9f7e9abb64605c337acbd6b14fc7e")
     	plaintext, _ := hex.DecodeString("a6fee33eb110a2d769bbc52b0f36969c287874f665681477a25fc4c48015c541fbe2394133ba490a34ee2dd67b898177849a91")
    -	expected, _ := hex.DecodeString("bbca4a9e09ae9690c0f6f8d405e53dccd666aa9c5fa13c8758bc30abe1ddd1bcce0d36a1eaaaaffef20cd3c5970b9673f8a65c26ccecb9976fd6ac9c2c0f372c52c821")
    +	expected, _ := hex.DecodeString("bbca4a9e09ae9690c0f6f8d405e53dccd666aa9c5fa13c8758bc30abe1ddd1bcce0d36a1eaaaaffef20cd3c5970b9673f8a65c")
     	tag, _ := hex.DecodeString("26ccecb9976fd6ac9c2c0f372c52c821")
     	aesgcmTest(t, iv, key, plaintext, expected, tag)
     }
     
    +type KAT struct {
    +	IV         string `json:"iv"`
    +	Key        string `json:"key"`
    +	Plaintext  string `json:"pt"`
    +	AAD        string `json:"aad"`
    +	CipherText string `json:"ct"`
    +	Tag        string `json:"tag"`
    +}
    +
    +func TestAES_GCM_KATS(t *testing.T) {
    +	fileContents, err := ioutil.ReadFile("testdata/aes_gcm.json")
    +	if err != nil {
    +		t.Fatalf("failed to read KAT file: %v", err)
    +	}
    +
    +	var kats []KAT
    +	err = json.Unmarshal(fileContents, &kats)
    +	if err != nil {
    +		t.Fatalf("failed to unmarshal KAT json file: %v", err)
    +	}
    +
    +	for i, kat := range kats {
    +		t.Run(fmt.Sprintf("Case%d", i), func(t *testing.T) {
    +			if len(kat.AAD) > 0 {
    +				t.Skip("Skipping... SDK implementation does not expose additional authenticated data")
    +			}
    +			iv, err := hex.DecodeString(kat.IV)
    +			if err != nil {
    +				t.Fatalf("failed to decode iv: %v", err)
    +			}
    +			key, err := hex.DecodeString(kat.Key)
    +			if err != nil {
    +				t.Fatalf("failed to decode key: %v", err)
    +			}
    +			plaintext, err := hex.DecodeString(kat.Plaintext)
    +			if err != nil {
    +				t.Fatalf("failed to decode plaintext: %v", err)
    +			}
    +			ciphertext, err := hex.DecodeString(kat.CipherText)
    +			if err != nil {
    +				t.Fatalf("failed to decode ciphertext: %v", err)
    +			}
    +			tag, err := hex.DecodeString(kat.Tag)
    +			if err != nil {
    +				t.Fatalf("failed to decode tag: %v", err)
    +			}
    +			aesgcmTest(t, iv, key, plaintext, ciphertext, tag)
    +		})
    +	}
    +}
    +
     func TestGCMEncryptReader_SourceError(t *testing.T) {
     	gcm := &gcmEncryptReader{
     		encrypter: &mockCipherAEAD{},
    @@ -105,6 +159,8 @@ func TestGCMDecryptReader_DecrypterOpenError(t *testing.T) {
     }
     
     func aesgcmTest(t *testing.T, iv, key, plaintext, expected, tag []byte) {
    +	t.Helper()
    +	const gcmTagSize = 16
     	cd := CipherData{
     		Key: key,
     		IV:  iv,
    @@ -122,11 +178,11 @@ func aesgcmTest(t *testing.T, iv, key, plaintext, expected, tag []byte) {
     	}
     
     	// splitting tag and ciphertext
    -	etag := ciphertext[len(ciphertext)-16:]
    +	etag := ciphertext[len(ciphertext)-gcmTagSize:]
     	if !bytes.Equal(etag, tag) {
     		t.Errorf("expected tags to be equivalent")
     	}
    -	if !bytes.Equal(ciphertext, expected) {
    +	if !bytes.Equal(ciphertext[:len(ciphertext)-gcmTagSize], expected) {
     		t.Errorf("expected ciphertext to be equivalent")
     	}
     
    
  • service/s3/s3crypto/cipher_util.go+0 80 modified
    @@ -3,95 +3,15 @@ package s3crypto
     import (
     	"encoding/base64"
     	"strconv"
    -	"strings"
    -
    -	"github.com/aws/aws-sdk-go/aws"
    -	"github.com/aws/aws-sdk-go/aws/awserr"
     )
     
    -func (client *DecryptionClient) contentCipherFromEnvelope(ctx aws.Context, env Envelope) (ContentCipher, error) {
    -	wrap, err := client.wrapFromEnvelope(env)
    -	if err != nil {
    -		return nil, err
    -	}
    -
    -	return client.cekFromEnvelope(ctx, env, wrap)
    -}
    -
    -func (client *DecryptionClient) wrapFromEnvelope(env Envelope) (CipherDataDecrypter, error) {
    -	f, ok := client.WrapRegistry[env.WrapAlg]
    -	if !ok || f == nil {
    -		return nil, awserr.New(
    -			"InvalidWrapAlgorithmError",
    -			"wrap algorithm isn't supported, "+env.WrapAlg,
    -			nil,
    -		)
    -	}
    -	return f(env)
    -}
    -
     // AESGCMNoPadding is the constant value that is used to specify
     // the CEK algorithm consiting of AES GCM with no padding.
     const AESGCMNoPadding = "AES/GCM/NoPadding"
     
     // AESCBC is the string constant that signifies the AES CBC algorithm cipher.
     const AESCBC = "AES/CBC"
     
    -func (client *DecryptionClient) cekFromEnvelope(ctx aws.Context, env Envelope, decrypter CipherDataDecrypter) (ContentCipher, error) {
    -	f, ok := client.CEKRegistry[env.CEKAlg]
    -	if !ok || f == nil {
    -		return nil, awserr.New(
    -			"InvalidCEKAlgorithmError",
    -			"cek algorithm isn't supported, "+env.CEKAlg,
    -			nil,
    -		)
    -	}
    -
    -	key, err := base64.StdEncoding.DecodeString(env.CipherKey)
    -	if err != nil {
    -		return nil, err
    -	}
    -
    -	iv, err := base64.StdEncoding.DecodeString(env.IV)
    -	if err != nil {
    -		return nil, err
    -	}
    -
    -	if d, ok := decrypter.(CipherDataDecrypterWithContext); ok {
    -		key, err = d.DecryptKeyWithContext(ctx, key)
    -	} else {
    -		key, err = decrypter.DecryptKey(key)
    -	}
    -
    -	if err != nil {
    -		return nil, err
    -	}
    -
    -	cd := CipherData{
    -		Key:          key,
    -		IV:           iv,
    -		CEKAlgorithm: env.CEKAlg,
    -		Padder:       client.getPadder(env.CEKAlg),
    -	}
    -	return f(cd)
    -}
    -
    -// getPadder will return an unpadder with checking the cek algorithm specific padder.
    -// If there wasn't a cek algorithm specific padder, we check the padder itself.
    -// We return a no unpadder, if no unpadder was found. This means any customization
    -// either contained padding within the cipher implementation, and to maintain
    -// backwards compatility we will simply not unpad anything.
    -func (client *DecryptionClient) getPadder(cekAlg string) Padder {
    -	padder, ok := client.PadderRegistry[cekAlg]
    -	if !ok {
    -		padder, ok = client.PadderRegistry[cekAlg[strings.LastIndex(cekAlg, "/")+1:]]
    -		if !ok {
    -			return NoPadder
    -		}
    -	}
    -	return padder
    -}
    -
     func encodeMeta(reader hashReader, cd CipherData) (Envelope, error) {
     	iv := base64.StdEncoding.EncodeToString(cd.IV)
     	key := base64.StdEncoding.EncodeToString(cd.EncryptedKey)
    
  • service/s3/s3crypto/cipher_util_test.go+15 15 modified
    @@ -14,7 +14,7 @@ import (
     )
     
     func TestWrapFactory(t *testing.T) {
    -	c := DecryptionClient{
    +	o := DecryptionClientOptions{
     		WrapRegistry: map[string]WrapEntry{
     			KMSWrap: (kmsKeyHandler{
     				kms: kms.New(unit.Session),
    @@ -28,7 +28,7 @@ func TestWrapFactory(t *testing.T) {
     		WrapAlg: KMSWrap,
     		MatDesc: `{"kms_cmk_id":""}`,
     	}
    -	wrap, err := c.wrapFromEnvelope(env)
    +	wrap, err := wrapFromEnvelope(o, env)
     	w, ok := wrap.(*kmsKeyHandler)
     
     	if err != nil {
    @@ -42,7 +42,7 @@ func TestWrapFactory(t *testing.T) {
     	}
     }
     func TestWrapFactoryErrorNoWrap(t *testing.T) {
    -	c := DecryptionClient{
    +	o := DecryptionClientOptions{
     		WrapRegistry: map[string]WrapEntry{
     			KMSWrap: (kmsKeyHandler{
     				kms: kms.New(unit.Session),
    @@ -56,7 +56,7 @@ func TestWrapFactoryErrorNoWrap(t *testing.T) {
     		WrapAlg: "none",
     		MatDesc: `{"kms_cmk_id":""}`,
     	}
    -	wrap, err := c.wrapFromEnvelope(env)
    +	wrap, err := wrapFromEnvelope(o, env)
     
     	if err == nil {
     		t.Error("expected error, but received none")
    @@ -67,7 +67,7 @@ func TestWrapFactoryErrorNoWrap(t *testing.T) {
     }
     
     func TestWrapFactoryCustomEntry(t *testing.T) {
    -	c := DecryptionClient{
    +	o := DecryptionClientOptions{
     		WrapRegistry: map[string]WrapEntry{
     			"custom": (kmsKeyHandler{
     				kms: kms.New(unit.Session),
    @@ -81,7 +81,7 @@ func TestWrapFactoryCustomEntry(t *testing.T) {
     		WrapAlg: "custom",
     		MatDesc: `{"kms_cmk_id":""}`,
     	}
    -	wrap, err := c.wrapFromEnvelope(env)
    +	wrap, err := wrapFromEnvelope(o, env)
     
     	if err != nil {
     		t.Errorf("expected no error, but received %v", err)
    @@ -107,7 +107,7 @@ func TestCEKFactory(t *testing.T) {
     		Region:           aws.String("us-west-2"),
     	})
     
    -	c := DecryptionClient{
    +	o := DecryptionClientOptions{
     		WrapRegistry: map[string]WrapEntry{
     			KMSWrap: (kmsKeyHandler{
     				kms: kms.New(sess),
    @@ -139,8 +139,8 @@ func TestCEKFactory(t *testing.T) {
     		IV:        ivB64,
     		MatDesc:   `{"kms_cmk_id":""}`,
     	}
    -	wrap, err := c.wrapFromEnvelope(env)
    -	cek, err := c.cekFromEnvelope(aws.BackgroundContext(), env, wrap)
    +	wrap, err := wrapFromEnvelope(o, env)
    +	cek, err := cekFromEnvelope(o, aws.BackgroundContext(), env, wrap)
     
     	if err != nil {
     		t.Errorf("expected no error, but received %v", err)
    @@ -166,7 +166,7 @@ func TestCEKFactoryNoCEK(t *testing.T) {
     		Region:           aws.String("us-west-2"),
     	})
     
    -	c := DecryptionClient{
    +	o := DecryptionClientOptions{
     		WrapRegistry: map[string]WrapEntry{
     			KMSWrap: (kmsKeyHandler{
     				kms: kms.New(sess),
    @@ -198,8 +198,8 @@ func TestCEKFactoryNoCEK(t *testing.T) {
     		IV:        ivB64,
     		MatDesc:   `{"kms_cmk_id":""}`,
     	}
    -	wrap, err := c.wrapFromEnvelope(env)
    -	cek, err := c.cekFromEnvelope(aws.BackgroundContext(), env, wrap)
    +	wrap, err := wrapFromEnvelope(o, env)
    +	cek, err := cekFromEnvelope(o, aws.BackgroundContext(), env, wrap)
     
     	if err == nil {
     		t.Error("expected error, but received none")
    @@ -225,7 +225,7 @@ func TestCEKFactoryCustomEntry(t *testing.T) {
     		Region:           aws.String("us-west-2"),
     	})
     
    -	c := DecryptionClient{
    +	o := DecryptionClientOptions{
     		WrapRegistry: map[string]WrapEntry{
     			KMSWrap: (kmsKeyHandler{
     				kms: kms.New(sess),
    @@ -255,8 +255,8 @@ func TestCEKFactoryCustomEntry(t *testing.T) {
     		IV:        ivB64,
     		MatDesc:   `{"kms_cmk_id":""}`,
     	}
    -	wrap, err := c.wrapFromEnvelope(env)
    -	cek, err := c.cekFromEnvelope(aws.BackgroundContext(), env, wrap)
    +	wrap, err := wrapFromEnvelope(o, env)
    +	cek, err := cekFromEnvelope(o, aws.BackgroundContext(), env, wrap)
     
     	if err != nil {
     		t.Errorf("expected no error, but received %v", err)
    
  • service/s3/s3crypto/decryption_client.go+24 34 modified
    @@ -25,6 +25,8 @@ type CEKEntry func(CipherData) (ContentCipher, error)
     // Supported content ciphers:
     //	* AES/GCM
     //	* AES/CBC
    +//
    +// deprecated: See DecryptionClientV2
     type DecryptionClient struct {
     	S3Client s3iface.S3API
     	// LoadStrategy is used to load the metadata either from the metadata of the object
    @@ -45,6 +47,8 @@ type DecryptionClient struct {
     //	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient{
     //		// Custom client options here
     //	}))
    +//
    +// deprecated: see NewDecryptionClientV2
     func NewDecryptionClient(prov client.ConfigProvider, options ...func(*DecryptionClient)) *DecryptionClient {
     	s3client := s3.New(prov)
     
    @@ -82,47 +86,24 @@ func NewDecryptionClient(prov client.ConfigProvider, options ...func(*Decryption
     // decryption will be done. The SDK only supports V2 reads of KMS and GCM.
     //
     // Example:
    -//	sess := session.New()
    +//  sess := session.Must(session.NewSession())
     //	svc := s3crypto.NewDecryptionClient(sess)
     //	req, out := svc.GetObjectRequest(&s3.GetObjectInput {
     //	  Key: aws.String("testKey"),
     //	  Bucket: aws.String("testBucket"),
     //	})
     //	err := req.Send()
    +//
    +// deprecated: see DecryptionClientV2.GetObjectRequest
     func (c *DecryptionClient) GetObjectRequest(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
    -	req, out := c.S3Client.GetObjectRequest(input)
    -	req.Handlers.Unmarshal.PushBack(func(r *request.Request) {
    -		env, err := c.LoadStrategy.Load(r)
    -		if err != nil {
    -			r.Error = err
    -			out.Body.Close()
    -			return
    -		}
    -
    -		// If KMS should return the correct CEK algorithm with the proper
    -		// KMS key provider
    -		cipher, err := c.contentCipherFromEnvelope(r.Context(), env)
    -		if err != nil {
    -			r.Error = err
    -			out.Body.Close()
    -			return
    -		}
    -
    -		reader, err := cipher.DecryptContents(out.Body)
    -		if err != nil {
    -			r.Error = err
    -			out.Body.Close()
    -			return
    -		}
    -		out.Body = reader
    -	})
    -	return req, out
    +	return getObjectRequest(c.getClientOptions(), input)
     }
     
     // GetObject is a wrapper for GetObjectRequest
    +//
    +// deprecated: see DecryptionClientV2.GetObject
     func (c *DecryptionClient) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
    -	req, out := c.GetObjectRequest(input)
    -	return out, req.Send()
    +	return getObject(c.getClientOptions(), input)
     }
     
     // GetObjectWithContext is a wrapper for GetObjectRequest with the additional
    @@ -132,9 +113,18 @@ func (c *DecryptionClient) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOut
     // Context input parameters. The Context must not be nil. A nil Context will
     // cause a panic. Use the Context to add deadlining, timeouts, etc. In the future
     // this may create sub-contexts for individual underlying requests.
    +//
    +// deprecated: see DecryptionClientV2.GetObjectWithContext
     func (c *DecryptionClient) GetObjectWithContext(ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) {
    -	req, out := c.GetObjectRequest(input)
    -	req.SetContext(ctx)
    -	req.ApplyOptions(opts...)
    -	return out, req.Send()
    +	return getObjectWithContext(c.getClientOptions(), ctx, input, opts...)
    +}
    +
    +func (c *DecryptionClient) getClientOptions() DecryptionClientOptions {
    +	return DecryptionClientOptions{
    +		S3Client:       c.S3Client,
    +		LoadStrategy:   c.LoadStrategy,
    +		WrapRegistry:   c.WrapRegistry,
    +		CEKRegistry:    c.CEKRegistry,
    +		PadderRegistry: c.PadderRegistry,
    +	}
     }
    
  • service/s3/s3crypto/decryption_client_v2.go+115 0 added
    @@ -0,0 +1,115 @@
    +package s3crypto
    +
    +import (
    +	"strings"
    +
    +	"github.com/aws/aws-sdk-go/aws"
    +	"github.com/aws/aws-sdk-go/aws/client"
    +	"github.com/aws/aws-sdk-go/aws/request"
    +	"github.com/aws/aws-sdk-go/service/kms"
    +	"github.com/aws/aws-sdk-go/service/s3"
    +	"github.com/aws/aws-sdk-go/service/s3/s3iface"
    +)
    +
    +// DecryptionClientV2 is an S3 crypto client. The decryption client
    +// will handle all get object requests from Amazon S3.
    +// Supported key wrapping algorithms:
    +//	* AWS KMS
    +//	* AWS KMS + Context
    +//
    +// Supported content ciphers:
    +//	* AES/GCM
    +//	* AES/CBC
    +type DecryptionClientV2 struct {
    +	options DecryptionClientOptions
    +}
    +
    +// DecryptionClientOptions is the configuration options for DecryptionClientV2.
    +type DecryptionClientOptions struct {
    +	S3Client s3iface.S3API
    +	// LoadStrategy is used to load the metadata either from the metadata of the object
    +	// or from a separate file in s3.
    +	//
    +	// Defaults to our default load strategy.
    +	LoadStrategy LoadStrategy
    +
    +	WrapRegistry   map[string]WrapEntry
    +	CEKRegistry    map[string]CEKEntry
    +	PadderRegistry map[string]Padder
    +}
    +
    +// NewDecryptionClientV2 instantiates a new V2 S3 crypto client. The returned DecryptionClientV2 will be able to decrypt
    +// object encrypted by both the V1 and V2 clients.
    +//
    +// Example:
    +//	sess := session.Must(session.NewSession())
    +//	svc := s3crypto.NewDecryptionClientV2(sess, func(svc *s3crypto.DecryptionClientOptions{
    +//		// Custom client options here
    +//	}))
    +func NewDecryptionClientV2(prov client.ConfigProvider, options ...func(clientOptions *DecryptionClientOptions)) *DecryptionClientV2 {
    +	s3client := s3.New(prov)
    +
    +	s3client.Handlers.Build.PushBack(func(r *request.Request) {
    +		request.AddToUserAgent(r, "S3CryptoV2")
    +	})
    +
    +	kmsClient := kms.New(prov)
    +	clientOptions := &DecryptionClientOptions{
    +		S3Client: s3client,
    +		LoadStrategy: defaultV2LoadStrategy{
    +			client: s3client,
    +		},
    +		WrapRegistry: map[string]WrapEntry{
    +			KMSWrap:        NewKMSWrapEntry(kmsClient),
    +			KMSContextWrap: NewKMSContextWrapEntry(kmsClient),
    +		},
    +		CEKRegistry: map[string]CEKEntry{
    +			AESGCMNoPadding: newAESGCMContentCipher,
    +			strings.Join([]string{AESCBC, AESCBCPadder.Name()}, "/"): newAESCBCContentCipher,
    +		},
    +		PadderRegistry: map[string]Padder{
    +			strings.Join([]string{AESCBC, AESCBCPadder.Name()}, "/"): AESCBCPadder,
    +			"NoPadding": NoPadder,
    +		},
    +	}
    +	for _, option := range options {
    +		option(clientOptions)
    +	}
    +
    +	return &DecryptionClientV2{options: *clientOptions}
    +}
    +
    +// GetObjectRequest will make a request to s3 and retrieve the object. In this process
    +// decryption will be done. The SDK only supports V2 reads of KMS and GCM.
    +//
    +// Example:
    +//  sess := session.Must(session.NewSession())
    +//	svc := s3crypto.NewDecryptionClientV2(sess)
    +//	req, out := svc.GetObjectRequest(&s3.GetObjectInput {
    +//	  Key: aws.String("testKey"),
    +//	  Bucket: aws.String("testBucket"),
    +//	})
    +//	err := req.Send()
    +func (c *DecryptionClientV2) GetObjectRequest(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
    +	return getObjectRequest(c.options, input)
    +}
    +
    +// GetObject is a wrapper for GetObjectRequest
    +func (c *DecryptionClientV2) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
    +	req, out := getObjectRequest(c.options, input)
    +	return out, req.Send()
    +}
    +
    +// GetObjectWithContext is a wrapper for GetObjectRequest with the additional
    +// context, and request options support.
    +//
    +// GetObjectWithContext is the same as GetObject with the additional support for
    +// Context input parameters. The Context must not be nil. A nil Context will
    +// cause a panic. Use the Context to add deadlining, timeouts, etc. In the future
    +// this may create sub-contexts for individual underlying requests.
    +func (c *DecryptionClientV2) GetObjectWithContext(ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) {
    +	req, out := getObjectRequest(c.options, input)
    +	req.SetContext(ctx)
    +	req.ApplyOptions(opts...)
    +	return out, req.Send()
    +}
    
  • service/s3/s3crypto/decryption_client_v2_test.go+39 0 added
    @@ -0,0 +1,39 @@
    +package s3crypto_test
    +
    +import (
    +	"testing"
    +
    +	"github.com/aws/aws-sdk-go/awstesting/unit"
    +	"github.com/aws/aws-sdk-go/service/kms"
    +	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
    +)
    +
    +func TestDecryptionClientV2_CheckDeprecatedFeatures(t *testing.T) {
    +	// AES/GCM/NoPadding with kms+context => allowed
    +	builder := s3crypto.AESGCMContentCipherBuilder(s3crypto.NewKMSContextKeyGenerator(kms.New(unit.Session), "cmkID"))
    +	_, err := s3crypto.NewEncryptionClientV2(unit.Session, builder)
    +	if err != nil {
    +		t.Errorf("expected no error, got %v", err)
    +	}
    +
    +	// AES/GCM/NoPadding with kms => not allowed
    +	builder = s3crypto.AESGCMContentCipherBuilder(s3crypto.NewKMSKeyGenerator(kms.New(unit.Session), "cmkID"))
    +	_, err = s3crypto.NewEncryptionClientV2(unit.Session, builder)
    +	if err == nil {
    +		t.Error("expected error, but got nil")
    +	}
    +
    +	// AES/CBC/PKCS5Padding with kms => not allowed
    +	builder = s3crypto.AESCBCContentCipherBuilder(s3crypto.NewKMSKeyGenerator(kms.New(unit.Session), "cmkID"), s3crypto.NewPKCS7Padder(128))
    +	_, err = s3crypto.NewEncryptionClientV2(unit.Session, builder)
    +	if err == nil {
    +		t.Error("expected error, but got nil")
    +	}
    +
    +	// AES/CBC/PKCS5Padding with kms+context => not allowed
    +	builder = s3crypto.AESCBCContentCipherBuilder(s3crypto.NewKMSContextKeyGenerator(kms.New(unit.Session), "cmkID"), s3crypto.NewPKCS7Padder(128))
    +	_, err = s3crypto.NewEncryptionClientV2(unit.Session, builder)
    +	if err == nil {
    +		t.Error("expected error, but got nil")
    +	}
    +}
    
  • service/s3/s3crypto/deprecations.go+10 0 added
    @@ -0,0 +1,10 @@
    +package s3crypto
    +
    +import "fmt"
    +
    +var errDeprecatedCipherBuilder = fmt.Errorf("attempted to use deprecated cipher builder")
    +var errDeprecatedCipherDataGenerator = fmt.Errorf("attempted to use deprecated cipher data generator")
    +
    +type deprecatedFeatures interface {
    +	isUsingDeprecatedFeatures() error
    +}
    
  • service/s3/s3crypto/deprecations_test.go+51 0 added
    @@ -0,0 +1,51 @@
    +package s3crypto
    +
    +import (
    +	"testing"
    +
    +	"github.com/aws/aws-sdk-go/service/kms/kmsiface"
    +)
    +
    +type mockKMS struct {
    +	kmsiface.KMSAPI
    +}
    +
    +func TestAESGCMContentCipherBuilder_isUsingDeprecatedFeatures(t *testing.T) {
    +	builder := AESGCMContentCipherBuilder(NewKMSKeyGenerator(mockKMS{}, "cmkID"))
    +
    +	features, ok := builder.(deprecatedFeatures)
    +	if !ok {
    +		t.Errorf("expected to implement deprecatedFeatures interface")
    +	}
    +
    +	err := features.isUsingDeprecatedFeatures()
    +	if err == nil {
    +		t.Errorf("expected to recieve error for using deprecated features")
    +	}
    +
    +	builder = AESGCMContentCipherBuilder(NewKMSContextKeyGenerator(mockKMS{}, "cmkID"))
    +
    +	features, ok = builder.(deprecatedFeatures)
    +	if !ok {
    +		t.Errorf("expected to implement deprecatedFeatures interface")
    +	}
    +
    +	err = features.isUsingDeprecatedFeatures()
    +	if err != nil {
    +		t.Errorf("expected no error, got %v", err)
    +	}
    +}
    +
    +func TestAESCBCContentCipherBuilder_isUsingDeprecatedFeatures(t *testing.T) {
    +	builder := AESCBCContentCipherBuilder(nil, nil)
    +
    +	features, ok := builder.(deprecatedFeatures)
    +	if !ok {
    +		t.Errorf("expected to implement deprecatedFeatures interface")
    +	}
    +
    +	err := features.isUsingDeprecatedFeatures()
    +	if err == nil {
    +		t.Errorf("expected to recieve error for using deprecated features")
    +	}
    +}
    
  • service/s3/s3crypto/doc.go+47 18 modified
    @@ -15,29 +15,34 @@ ciphers.
     Creating an S3 cryptography client
     
     	cmkID := "<some key ID>"
    -	sess := session.New()
    +	sess := session.Must(session.NewSession())
     	// Create the KeyProvider
    -	handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
    +	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
     
     	// Create an encryption and decryption client
     	// We need to pass the session here so S3 can use it. In addition, any decryption that
     	// occurs will use the KMS client.
    -	svc := s3crypto.NewEncryptionClient(sess, s3crypto.AESGCMContentCipherBuilder(handler))
    -	svc := s3crypto.NewDecryptionClient(sess)
    +	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilder(handler))
    +	svc := s3crypto.NewDecryptionClientV2(sess)
     
     Configuration of the S3 cryptography client
     
    -	cfg := s3crypto.EncryptionConfig{
    +	sess := session.Must(session.NewSession())
    +	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
    +	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilder(handler), func (o *s3crypto.EncryptionClientOptions) {
     		// Save instruction files to separate objects
    -		SaveStrategy: NewS3SaveStrategy(session.New(), ""),
    +		o.SaveStrategy = NewS3SaveStrategy(sess, "")
    +
     		// Change instruction file suffix to .example
    -		InstructionFileSuffix: ".example",
    +		o.InstructionFileSuffix = ".example"
    +
     		// Set temp folder path
    -		TempFolderPath: "/path/to/tmp/folder/",
    +		o.TempFolderPath = "/path/to/tmp/folder/"
    +
     		// Any content less than the minimum file size will use memory
     		// instead of writing the contents to a temp file.
    -		MinFileSize: int64(1024 * 1024 * 1024),
    -	}
    +		o.MinFileSize = int64(1024 * 1024 * 1024)
    +	})
     
     The default SaveStrategy is to the object's header.
     
    @@ -48,19 +53,43 @@ This suffix only affects gets and not puts. Put uses the keyprovider's suffix.
     Registration of new wrap or cek algorithms are also supported by the SDK. Let's say we want to support `AES Wrap`
     and `AES CTR`. Let's assume we have already defined the functionality.
     
    -	svc := s3crypto.NewDecryptionClient(sess)
    -	svc.WrapRegistry["AESWrap"] = NewAESWrap
    -	svc.CEKRegistry["AES/CTR/NoPadding"] = NewAESCTR
    +	svc := s3crypto.NewDecryptionClientV2(sess, func(o *s3crypto.DecryptionClientOptions) {
    +		o.WrapRegistry["CustomWrap"] = NewCustomWrap
    +		o.CEKRegistry["CustomCEK"] = NewCustomCEK
    +	})
     
     We have now registered these new algorithms to the decryption client. When the client calls `GetObject` and sees
    -the wrap as `AESWrap` then it'll use that wrap algorithm. This is also true for `AES/CTR/NoPadding`.
    +the wrap as `CustomWrap` then it'll use that wrap algorithm. This is also true for `CustomCEK`.
     
     For encryption adding a custom content cipher builder and key handler will allow for encryption of custom
     defined ciphers.
     
    -	// Our wrap algorithm, AESWrap
    -	handler := NewAESWrap(key, iv)
    -	// Our content cipher builder, AESCTRContentCipherBuilder
    -	svc := s3crypto.NewEncryptionClient(sess, NewAESCTRContentCipherBuilder(handler))
    +	// Our wrap algorithm, CustomWrap
    +	handler := NewCustomWrap(key, iv)
    +	// Our content cipher builder, NewCustomCEKContentBuilder
    +	svc := s3crypto.NewEncryptionClientV2(sess, NewCustomCEKContentBuilder(handler))
    +
    +Deprecations
    +
    +The EncryptionClient and DecryptionClient types and their associated constructor functions have been deprecated.
    +Users of these clients should migrate to EncryptionClientV2 and DecryptionClientV2 types and constructor functions.
    +
    +EncryptionClientV2 removes encryption support of the following features
    +	* AES/CBC/PKCS5Padding (content cipher)
    +	* kms (key wrap algorithm)
    +
    +Attempting to construct an EncryptionClientV2 with deprecated features will result in an error returned back to the
    +calling application during construction of the client.
    +
    +Users of `AES/CBC/PKCS5Padding` will need to migrate usage to `AES/GCM/NoPadding`.
    +Users of `kms` key provider will need to migrate `kms+context`.
    +
    +DecryptionClientV2 client adds support for the `kms+context` key provider and maintains backwards comparability with
    +objects encrypted with the deprecated EncryptionClient.
    +
    +Migrating from V1 to V2 Clients
    +
    +Examples of how to migrate usage of the V1 clients to the V2 equivalents have been documented as usage examples of
    +the NewEncryptionClientV2 and NewDecryptionClientV2 functions.
     */
     package s3crypto
    
  • service/s3/s3crypto/encryption_client.go+26 75 modified
    @@ -1,13 +1,9 @@
     package s3crypto
     
     import (
    -	"encoding/hex"
    -	"io"
    -
     	"github.com/aws/aws-sdk-go/aws"
     	"github.com/aws/aws-sdk-go/aws/client"
     	"github.com/aws/aws-sdk-go/aws/request"
    -	"github.com/aws/aws-sdk-go/internal/sdkio"
     	"github.com/aws/aws-sdk-go/service/s3"
     	"github.com/aws/aws-sdk-go/service/s3/s3iface"
     )
    @@ -20,6 +16,8 @@ const DefaultMinFileSize = 1024 * 512 * 5
     // will use KMS for key wrapping and AES GCM for content encryption.
     // AES GCM will load all data into memory. However, the rest of the content algorithms
     // do not load the entire contents into memory.
    +//
    +// deprecated: See EncryptionClientV2
     type EncryptionClient struct {
     	S3Client             s3iface.S3API
     	ContentCipherBuilder ContentCipherBuilder
    @@ -39,9 +37,11 @@ type EncryptionClient struct {
     //
     // Example:
     //	cmkID := "arn:aws:kms:region:000000000000:key/00000000-0000-0000-0000-000000000000"
    -//	sess := session.New()
    +//  sess := session.Must(session.NewSession())
     //	handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
    -//	svc := s3crypto.New(sess, s3crypto.AESGCMContentCipherBuilder(handler))
    +//	svc := s3crypto.NewEncryptionClient(sess, s3crypto.AESGCMContentCipherBuilder(handler))
    +//
    +// deprecated: See NewEncryptionClientV2
     func NewEncryptionClient(prov client.ConfigProvider, builder ContentCipherBuilder, options ...func(*EncryptionClient)) *EncryptionClient {
     	s3client := s3.New(prov)
     
    @@ -74,76 +74,17 @@ func NewEncryptionClient(prov client.ConfigProvider, builder ContentCipherBuilde
     //	  Body: strings.NewReader("test data"),
     //	})
     //	err := req.Send()
    +//
    +// deprecated: See EncryptionClientV2.PutObjectRequest
     func (c *EncryptionClient) PutObjectRequest(input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
    -	req, out := c.S3Client.PutObjectRequest(input)
    -
    -	// Get Size of file
    -	n, err := aws.SeekerLen(input.Body)
    -	if err != nil {
    -		req.Error = err
    -		return req, out
    -	}
    -
    -	dst, err := getWriterStore(req, c.TempFolderPath, n >= c.MinFileSize)
    -	if err != nil {
    -		req.Error = err
    -		return req, out
    -	}
    -
    -	req.Handlers.Build.PushFront(func(r *request.Request) {
    -		if err != nil {
    -			r.Error = err
    -			return
    -		}
    -		var encryptor ContentCipher
    -		if v, ok := c.ContentCipherBuilder.(ContentCipherBuilderWithContext); ok {
    -			encryptor, err = v.ContentCipherWithContext(r.Context())
    -		} else {
    -			encryptor, err = c.ContentCipherBuilder.ContentCipher()
    -		}
    -		if err != nil {
    -			r.Error = err
    -			return
    -		}
    -
    -		md5 := newMD5Reader(input.Body)
    -		sha := newSHA256Writer(dst)
    -		reader, err := encryptor.EncryptContents(md5)
    -		if err != nil {
    -			r.Error = err
    -			return
    -		}
    -
    -		_, err = io.Copy(sha, reader)
    -		if err != nil {
    -			r.Error = err
    -			return
    -		}
    -
    -		data := encryptor.GetCipherData()
    -		env, err := encodeMeta(md5, data)
    -		if err != nil {
    -			r.Error = err
    -			return
    -		}
    -
    -		shaHex := hex.EncodeToString(sha.GetValue())
    -		req.HTTPRequest.Header.Set("X-Amz-Content-Sha256", shaHex)
    -
    -		dst.Seek(0, sdkio.SeekStart)
    -		input.Body = dst
    -
    -		err = c.SaveStrategy.Save(env, r)
    -		r.Error = err
    -	})
    -
    -	return req, out
    +	return putObjectRequest(c.getClientOptions(), input)
     }
     
     // PutObject is a wrapper for PutObjectRequest
    +//
    +// deprecated: See EncryptionClientV2.PutObject
     func (c *EncryptionClient) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
    -	req, out := c.PutObjectRequest(input)
    -	return out, req.Send()
    +	return putObject(c.getClientOptions(), input)
     }
     
     // PutObjectWithContext is a wrapper for PutObjectRequest with the additional
    @@ -153,9 +94,19 @@ func (c *EncryptionClient) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOut
     // Context input parameters. The Context must not be nil. A nil Context will
     // cause a panic. Use the Context to add deadlining, timeouts, etc. In the future
     // this may create sub-contexts for individual underlying requests.
    +// PutObject is a wrapper for PutObjectRequest
    +//
    +// deprecated: See EncryptionClientV2.PutObjectWithContext
     func (c *EncryptionClient) PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) {
    -	req, out := c.PutObjectRequest(input)
    -	req.SetContext(ctx)
    -	req.ApplyOptions(opts...)
    -	return out, req.Send()
    +	return putObjectWithContext(c.getClientOptions(), ctx, input, opts...)
    +}
    +
    +func (c *EncryptionClient) getClientOptions() EncryptionClientOptions {
    +	return EncryptionClientOptions{
    +		S3Client:             c.S3Client,
    +		ContentCipherBuilder: c.ContentCipherBuilder,
    +		SaveStrategy:         c.SaveStrategy,
    +		TempFolderPath:       c.TempFolderPath,
    +		MinFileSize:          c.MinFileSize,
    +	}
     }
    
  • service/s3/s3crypto/encryption_client_v2.go+108 0 added
    @@ -0,0 +1,108 @@
    +package s3crypto
    +
    +import (
    +	"github.com/aws/aws-sdk-go/aws"
    +	"github.com/aws/aws-sdk-go/aws/client"
    +	"github.com/aws/aws-sdk-go/aws/request"
    +	"github.com/aws/aws-sdk-go/service/s3"
    +	"github.com/aws/aws-sdk-go/service/s3/s3iface"
    +)
    +
    +// EncryptionClientV2 is an S3 crypto client. By default the SDK will use Authentication mode which
    +// will use KMS for key wrapping and AES GCM for content encryption.
    +// AES GCM will load all data into memory. However, the rest of the content algorithms
    +// do not load the entire contents into memory.
    +type EncryptionClientV2 struct {
    +	options EncryptionClientOptions
    +}
    +
    +// EncryptionClientOptions is the configuration options for EncryptionClientV2
    +type EncryptionClientOptions struct {
    +	S3Client             s3iface.S3API
    +	ContentCipherBuilder ContentCipherBuilder
    +	// SaveStrategy will dictate where the envelope is saved.
    +	//
    +	// Defaults to the object's metadata
    +	SaveStrategy SaveStrategy
    +	// TempFolderPath is used to store temp files when calling PutObject.
    +	// Temporary files are needed to compute the X-Amz-Content-Sha256 header.
    +	TempFolderPath string
    +	// MinFileSize is the minimum size for the content to write to a
    +	// temporary file instead of using memory.
    +	MinFileSize int64
    +}
    +
    +// NewEncryptionClientV2 instantiates a new S3 crypto client. An error will be returned to the caller if the provided
    +// contentCipherBuilder has been deprecated, or if it uses other deprecated components.
    +//
    +// Example:
    +//	cmkID := "arn:aws:kms:region:000000000000:key/00000000-0000-0000-0000-000000000000"
    +//  sess := session.Must(session.NewSession())
    +//	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
    +//	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilder(handler))
    +func NewEncryptionClientV2(prov client.ConfigProvider, contentCipherBuilder ContentCipherBuilder, options ...func(clientOptions *EncryptionClientOptions),
    +) (
    +	client *EncryptionClientV2, err error,
    +) {
    +	s3client := s3.New(prov)
    +
    +	s3client.Handlers.Build.PushBack(func(r *request.Request) {
    +		request.AddToUserAgent(r, "S3CryptoV2")
    +	})
    +
    +	clientOptions := &EncryptionClientOptions{
    +		S3Client:             s3client,
    +		ContentCipherBuilder: contentCipherBuilder,
    +		SaveStrategy:         HeaderV2SaveStrategy{},
    +		MinFileSize:          DefaultMinFileSize,
    +	}
    +
    +	for _, option := range options {
    +		option(clientOptions)
    +	}
    +
    +	if feature, ok := contentCipherBuilder.(deprecatedFeatures); ok {
    +		if err := feature.isUsingDeprecatedFeatures(); err != nil {
    +			return nil, err
    +		}
    +	}
    +
    +	client = &EncryptionClientV2{
    +		*clientOptions,
    +	}
    +
    +	return client, err
    +}
    +
    +// PutObjectRequest creates a temp file to encrypt the contents into. It then streams
    +// that data to S3.
    +//
    +// Example:
    +//  sess := session.Must(session.NewSession())
    +//	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), "cmkID")
    +//	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilder(handler))
    +//	req, out := svc.PutObjectRequest(&s3.PutObjectInput {
    +//	  Key: aws.String("testKey"),
    +//	  Bucket: aws.String("testBucket"),
    +//	  Body: strings.NewReader("test data"),
    +//	})
    +//	err := req.Send()
    +func (c *EncryptionClientV2) PutObjectRequest(input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
    +	return putObjectRequest(c.options, input)
    +}
    +
    +// PutObject is a wrapper for PutObjectRequest
    +func (c *EncryptionClientV2) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
    +	return putObject(c.options, input)
    +}
    +
    +// PutObjectWithContext is a wrapper for PutObjectRequest with the additional
    +// context, and request options support.
    +//
    +// PutObjectWithContext is the same as PutObject with the additional support for
    +// Context input parameters. The Context must not be nil. A nil Context will
    +// cause a panic. Use the Context to add deadlining, timeouts, etc. In the future
    +// this may create sub-contexts for individual underlying requests.
    +func (c *EncryptionClientV2) PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) {
    +	return putObjectWithContext(c.options, ctx, input, opts...)
    +}
    
  • service/s3/s3crypto/envelope.go+1 1 modified
    @@ -32,6 +32,6 @@ type Envelope struct {
     	WrapAlg               string `json:"x-amz-wrap-alg"`
     	CEKAlg                string `json:"x-amz-cek-alg"`
     	TagLen                string `json:"x-amz-tag-len"`
    -	UnencryptedMD5        string `json:"x-amz-unencrypted-content-md5"`
    +	UnencryptedMD5        string `json:"-"`
     	UnencryptedContentLen string `json:"x-amz-unencrypted-content-length"`
     }
    
  • service/s3/s3crypto/integration/main_test.go+187 0 added
    @@ -0,0 +1,187 @@
    +// +build integration,go1.14
    +
    +package integration
    +
    +import (
    +	"bytes"
    +	"crypto/rand"
    +	"flag"
    +	"io"
    +	"io/ioutil"
    +	"log"
    +	"os"
    +	"testing"
    +
    +	"github.com/aws/aws-sdk-go/aws"
    +	"github.com/aws/aws-sdk-go/aws/session"
    +	"github.com/aws/aws-sdk-go/awstesting/integration"
    +	"github.com/aws/aws-sdk-go/service/kms"
    +	"github.com/aws/aws-sdk-go/service/kms/kmsiface"
    +	"github.com/aws/aws-sdk-go/service/s3"
    +	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
    +	"github.com/aws/aws-sdk-go/service/s3/s3iface"
    +)
    +
    +var config = &struct {
    +	Enabled  bool
    +	Region   string
    +	KMSKeyID string
    +	Bucket   string
    +	Session  *session.Session
    +	Clients  struct {
    +		KMS kmsiface.KMSAPI
    +		S3  s3iface.S3API
    +	}
    +}{}
    +
    +func init() {
    +	flag.BoolVar(&config.Enabled, "enable", false, "enable integration testing")
    +	flag.StringVar(&config.Region, "region", "us-west-2", "integration test region")
    +	flag.StringVar(&config.KMSKeyID, "kms-key-id", "", "KMS CMK Key ID")
    +	flag.StringVar(&config.Bucket, "bucket", "", "S3 Bucket Name")
    +}
    +
    +func TestMain(m *testing.M) {
    +	flag.Parse()
    +	if !config.Enabled {
    +		log.Println("skipping s3crypto integration tests")
    +		os.Exit(0)
    +	}
    +
    +	if len(config.Bucket) == 0 {
    +		log.Fatal("bucket name must be provided")
    +	}
    +
    +	if len(config.KMSKeyID) == 0 {
    +		log.Fatal("kms cmk key id must be provided")
    +	}
    +
    +	config.Session = session.Must(session.NewSession(&aws.Config{Region: &config.Region}))
    +
    +	config.Clients.KMS = kms.New(config.Session)
    +	config.Clients.S3 = s3.New(config.Session)
    +
    +	m.Run()
    +}
    +
    +func TestEncryptionV1_WithV2Interop(t *testing.T) {
    +	kmsKeyGenerator := s3crypto.NewKMSKeyGenerator(config.Clients.KMS, config.KMSKeyID)
    +
    +	// 1020 is chosen here as it is not cleanly divisible by the AES-256 block size
    +	testData := make([]byte, 1020)
    +	_, err := rand.Read(testData)
    +	if err != nil {
    +		t.Fatalf("failed to read random data: %v", err)
    +	}
    +
    +	v1DC := s3crypto.NewDecryptionClient(config.Session, func(client *s3crypto.DecryptionClient) {
    +		client.S3Client = config.Clients.S3
    +	})
    +	v2DC := s3crypto.NewDecryptionClientV2(config.Session, func(options *s3crypto.DecryptionClientOptions) {
    +		options.S3Client = config.Clients.S3
    +	})
    +
    +	cases := map[string]s3crypto.ContentCipherBuilder{
    +		"AES/GCM/NoPadding":    s3crypto.AESGCMContentCipherBuilder(kmsKeyGenerator),
    +		"AES/CBC/PKCS5Padding": s3crypto.AESCBCContentCipherBuilder(kmsKeyGenerator, s3crypto.AESCBCPadder),
    +	}
    +
    +	for name, ccb := range cases {
    +		t.Run(name, func(t *testing.T) {
    +			ec := s3crypto.NewEncryptionClient(config.Session, ccb, func(client *s3crypto.EncryptionClient) {
    +				client.S3Client = config.Clients.S3
    +			})
    +			id := integration.UniqueID()
    +			// PutObject with V1 Client
    +			putObject(t, ec, id, bytes.NewReader(testData))
    +			// Verify V1 Decryption Client
    +			getObjectAndCompare(t, v1DC, id, testData)
    +			// Verify V2 Decryption Client
    +			getObjectAndCompare(t, v2DC, id, testData)
    +		})
    +	}
    +}
    +
    +func TestEncryptionV2(t *testing.T) {
    +	kmsKeyGenerator := s3crypto.NewKMSContextKeyGenerator(config.Clients.KMS, config.KMSKeyID)
    +	gcmContentCipherBuilder := s3crypto.AESGCMContentCipherBuilder(kmsKeyGenerator)
    +
    +	ec, err := s3crypto.NewEncryptionClientV2(config.Session, gcmContentCipherBuilder, func(options *s3crypto.EncryptionClientOptions) {
    +		options.S3Client = config.Clients.S3
    +	})
    +	if err != nil {
    +		t.Fatalf("failed to construct encryption client: %v", err)
    +	}
    +
    +	dc := s3crypto.NewDecryptionClientV2(config.Session, func(options *s3crypto.DecryptionClientOptions) {
    +		options.S3Client = config.Clients.S3
    +	})
    +
    +	// 1020 is chosen here as it is not cleanly divisible by the AES-256 block size
    +	testData := make([]byte, 1020)
    +	_, err = rand.Read(testData)
    +	if err != nil {
    +		t.Fatalf("failed to read random data: %v", err)
    +	}
    +
    +	keyId := integration.UniqueID()
    +
    +	// Upload V2 Objects with Encryption Client
    +	putObject(t, ec, keyId, bytes.NewReader(testData))
    +
    +	// Verify V2 Object with Decryption Client
    +	getObjectAndCompare(t, dc, keyId, testData)
    +}
    +
    +type Encryptor interface {
    +	PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error)
    +}
    +
    +func putObject(t *testing.T, client Encryptor, key string, reader io.ReadSeeker) {
    +	t.Helper()
    +	_, err := client.PutObject(&s3.PutObjectInput{
    +		Bucket: &config.Bucket,
    +		Key:    &key,
    +		Body:   reader,
    +	})
    +	if err != nil {
    +		t.Fatalf("failed to upload object: %v", err)
    +	}
    +	t.Cleanup(doKeyCleanup(key))
    +}
    +
    +type Decryptor interface {
    +	GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error)
    +}
    +
    +func getObjectAndCompare(t *testing.T, client Decryptor, key string, expected []byte) {
    +	t.Helper()
    +	output, err := client.GetObject(&s3.GetObjectInput{
    +		Bucket: &config.Bucket,
    +		Key:    &key,
    +	})
    +	if err != nil {
    +		t.Fatalf("failed to get object: %v", err)
    +	}
    +
    +	actual, err := ioutil.ReadAll(output.Body)
    +	if err != nil {
    +		t.Fatalf("failed to read body response: %v", err)
    +	}
    +
    +	if bytes.Compare(expected, actual) != 0 {
    +		t.Errorf("expected bytes did not match actual")
    +	}
    +}
    +
    +func doKeyCleanup(key string) func() {
    +	return func() {
    +		_, err := config.Clients.S3.DeleteObject(&s3.DeleteObjectInput{
    +			Bucket: &config.Bucket,
    +			Key:    &key,
    +		})
    +		if err != nil {
    +			log.Printf("failed to delete %s: %v", key, err)
    +		}
    +	}
    +}
    
  • service/s3/s3crypto/key_handler.go+22 3 modified
    @@ -20,6 +20,22 @@ type CipherDataGeneratorWithContext interface {
     	GenerateCipherDataWithContext(aws.Context, int, int) (CipherData, error)
     }
     
    +// CipherDataGeneratorWithCEKAlg handles generating proper key and IVs of proper size for the
    +// content cipher. CipherDataGenerator will also encrypt the key and store it in
    +// the CipherData.
    +type CipherDataGeneratorWithCEKAlg interface {
    +	GenerateCipherDataWithCEKAlg(int, int, string) (CipherData, error)
    +
    +	CipherDataGenerator // backwards comparability to plug into older interface
    +}
    +
    +// CipherDataGeneratorWithCEKAlgWithContext handles generating proper key and IVs of
    +// proper size for the content cipher. CipherDataGenerator will also encrypt
    +// the key and store it in the CipherData.
    +type CipherDataGeneratorWithCEKAlgWithContext interface {
    +	GenerateCipherDataWithCEKAlgWithContext(aws.Context, int, int, string) (CipherData, error)
    +}
    +
     // CipherDataDecrypter is a handler to decrypt keys from the envelope.
     type CipherDataDecrypter interface {
     	DecryptKey([]byte) ([]byte, error)
    @@ -30,8 +46,11 @@ type CipherDataDecrypterWithContext interface {
     	DecryptKeyWithContext(aws.Context, []byte) ([]byte, error)
     }
     
    -func generateBytes(n int) []byte {
    +func generateBytes(n int) ([]byte, error) {
     	b := make([]byte, n)
    -	rand.Read(b)
    -	return b
    +	_, err := rand.Read(b)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return b, nil
     }
    
  • service/s3/s3crypto/key_handler_test.go+3 3 modified
    @@ -5,15 +5,15 @@ import (
     )
     
     func TestGenerateBytes(t *testing.T) {
    -	b := generateBytes(5)
    +	b, _ := generateBytes(5)
     	if e, a := 5, len(b); e != a {
     		t.Errorf("expected %d, but received %d", e, a)
     	}
    -	b = generateBytes(0)
    +	b, _ = generateBytes(0)
     	if e, a := 0, len(b); e != a {
     		t.Errorf("expected %d, but received %d", e, a)
     	}
    -	b = generateBytes(1024)
    +	b, _ = generateBytes(1024)
     	if e, a := 1024, len(b); e != a {
     		t.Errorf("expected %d, but received %d", e, a)
     	}
    
  • service/s3/s3crypto/kms_key_handler.go+115 16 modified
    @@ -1,6 +1,8 @@
     package s3crypto
     
     import (
    +	"fmt"
    +
     	"github.com/aws/aws-sdk-go/aws"
     	"github.com/aws/aws-sdk-go/aws/awserr"
     	"github.com/aws/aws-sdk-go/service/kms"
    @@ -10,12 +12,16 @@ import (
     const (
     	// KMSWrap is a constant used during decryption to build a KMS key handler.
     	KMSWrap = "kms"
    +
    +	// KMSContextWrap is a constant used during decryption to build a kms+context key handler
    +	KMSContextWrap = "kms+context"
     )
     
     // kmsKeyHandler will make calls to KMS to get the masterkey
     type kmsKeyHandler struct {
    -	kms   kmsiface.KMSAPI
    -	cmkID *string
    +	kms         kmsiface.KMSAPI
    +	cmkID       *string
    +	withContext bool
     
     	CipherData
     }
    @@ -28,35 +34,73 @@ type kmsKeyHandler struct {
     //	cmkID := "arn to key"
     //	matdesc := s3crypto.MaterialDescription{}
     //	handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
    +//
    +// deprecated: See NewKMSContextKeyGenerator
     func NewKMSKeyGenerator(kmsClient kmsiface.KMSAPI, cmkID string) CipherDataGenerator {
     	return NewKMSKeyGeneratorWithMatDesc(kmsClient, cmkID, MaterialDescription{})
     }
     
    -// NewKMSKeyGeneratorWithMatDesc builds a new KMS key provider using the customer key ID and material
    +// NewKMSContextKeyGenerator builds a new kms+context key provider using the customer key ID and material
     // description.
     //
     // Example:
     //	sess := session.New(&aws.Config{})
     //	cmkID := "arn to key"
     //	matdesc := s3crypto.MaterialDescription{}
    -//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
    -func NewKMSKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGenerator {
    +//	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
    +func NewKMSContextKeyGenerator(client kmsiface.KMSAPI, cmkID string) CipherDataGeneratorWithCEKAlg {
    +	return NewKMSContextKeyGeneratorWithMatDesc(client, cmkID, MaterialDescription{})
    +}
    +
    +func newKMSKeyHandler(client kmsiface.KMSAPI, cmkID string, withContext bool, matdesc MaterialDescription) *kmsKeyHandler {
    +	// These values are read only making them thread safe
    +	kp := &kmsKeyHandler{
    +		kms:         client,
    +		cmkID:       &cmkID,
    +		withContext: withContext,
    +	}
    +
     	if matdesc == nil {
     		matdesc = MaterialDescription{}
     	}
    -	matdesc["kms_cmk_id"] = &cmkID
     
     	// These values are read only making them thread safe
    -	kp := &kmsKeyHandler{
    -		kms:   kmsClient,
    -		cmkID: &cmkID,
    +	if kp.withContext {
    +		kp.CipherData.WrapAlgorithm = KMSContextWrap
    +	} else {
    +		matdesc["kms_cmk_id"] = &cmkID
    +		kp.CipherData.WrapAlgorithm = KMSWrap
     	}
    -	// These values are read only making them thread safe
    -	kp.CipherData.WrapAlgorithm = KMSWrap
     	kp.CipherData.MaterialDescription = matdesc
     	return kp
     }
     
    +// NewKMSKeyGeneratorWithMatDesc builds a new KMS key provider using the customer key ID and material
    +// description.
    +//
    +// Example:
    +//	sess := session.New(&aws.Config{})
    +//	cmkID := "arn to key"
    +//	matdesc := s3crypto.MaterialDescription{}
    +//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
    +//
    +// deprecated: See NewKMSContextKeyGeneratorWithMatDesc
    +func NewKMSKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGenerator {
    +	return newKMSKeyHandler(kmsClient, cmkID, false, matdesc)
    +}
    +
    +// NewKMSContextKeyGeneratorWithMatDesc builds a new kms+context key provider using the customer key ID and material
    +// description.
    +//
    +// Example:
    +//	sess := session.New(&aws.Config{})
    +//	cmkID := "arn to key"
    +//	matdesc := s3crypto.MaterialDescription{}
    +//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
    +func NewKMSContextKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGeneratorWithCEKAlg {
    +	return newKMSKeyHandler(kmsClient, cmkID, true, matdesc)
    +}
    +
     // NewKMSWrapEntry builds returns a new KMS key provider and its decrypt handler.
     //
     // Example:
    @@ -67,6 +111,8 @@ func NewKMSKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matd
     //	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient) {
     //		svc.WrapRegistry[s3crypto.KMSWrap] = decryptHandler
     //	}))
    +//
    +// deprecated: See NewKMSContextWrapEntry
     func NewKMSWrapEntry(kmsClient kmsiface.KMSAPI) WrapEntry {
     	// These values are read only making them thread safe
     	kp := &kmsKeyHandler{
    @@ -76,6 +122,26 @@ func NewKMSWrapEntry(kmsClient kmsiface.KMSAPI) WrapEntry {
     	return kp.decryptHandler
     }
     
    +// NewKMSContextWrapEntry builds returns a new KMS key provider and its decrypt handler.
    +//
    +// Example:
    +//	sess := session.New(&aws.Config{})
    +//	customKMSClient := kms.New(sess)
    +//	decryptHandler := s3crypto.NewKMSContextWrapEntry(customKMSClient)
    +//
    +//	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient) {
    +//		svc.WrapRegistry[s3crypto.KMSContextWrap] = decryptHandler
    +//	}))
    +func NewKMSContextWrapEntry(kmsClient kmsiface.KMSAPI) WrapEntry {
    +	// These values are read only making them thread safe
    +	kp := &kmsKeyHandler{
    +		kms:         kmsClient,
    +		withContext: true,
    +	}
    +
    +	return kp.decryptHandler
    +}
    +
     // decryptHandler initializes a KMS keyprovider with a material description. This
     // is used with Decrypting kms content, due to the cmkID being in the material description.
     func (kp kmsKeyHandler) decryptHandler(env Envelope) (CipherDataDecrypter, error) {
    @@ -86,13 +152,16 @@ func (kp kmsKeyHandler) decryptHandler(env Envelope) (CipherDataDecrypter, error
     	}
     
     	cmkID, ok := m["kms_cmk_id"]
    -	if !ok {
    +	if !kp.withContext && !ok {
     		return nil, awserr.New("MissingCMKIDError", "Material description is missing CMK ID", nil)
     	}
     
     	kp.CipherData.MaterialDescription = m
     	kp.cmkID = cmkID
     	kp.WrapAlgorithm = KMSWrap
    +	if kp.withContext {
    +		kp.WrapAlgorithm = KMSContextWrap
    +	}
     	return &kp, nil
     }
     
    @@ -121,26 +190,56 @@ func (kp *kmsKeyHandler) GenerateCipherData(keySize, ivSize int) (CipherData, er
     	return kp.GenerateCipherDataWithContext(aws.BackgroundContext(), keySize, ivSize)
     }
     
    +func (kp kmsKeyHandler) GenerateCipherDataWithCEKAlg(keySize, ivSize int, cekAlgorithm string) (CipherData, error) {
    +	return kp.GenerateCipherDataWithCEKAlgWithContext(aws.BackgroundContext(), keySize, ivSize, cekAlgorithm)
    +}
    +
     // GenerateCipherDataWithContext makes a call to KMS to generate a data key,
     // Upon making the call, it also sets the encrypted key.
     func (kp *kmsKeyHandler) GenerateCipherDataWithContext(ctx aws.Context, keySize, ivSize int) (CipherData, error) {
    +	return kp.GenerateCipherDataWithCEKAlgWithContext(ctx, keySize, ivSize, "")
    +}
    +
    +func (kp kmsKeyHandler) GenerateCipherDataWithCEKAlgWithContext(ctx aws.Context, keySize int, ivSize int, cekAlgorithm string) (CipherData, error) {
    +	md := kp.CipherData.MaterialDescription
    +
    +	wrapAlgorithm := KMSWrap
    +	if kp.withContext {
    +		wrapAlgorithm = KMSContextWrap
    +		if len(cekAlgorithm) == 0 {
    +			return CipherData{}, fmt.Errorf("CEK algorithm identifier must not be empty")
    +		}
    +		md["aws:"+cekAlgorithmHeader] = &cekAlgorithm
    +	}
    +
     	out, err := kp.kms.GenerateDataKeyWithContext(ctx,
     		&kms.GenerateDataKeyInput{
    -			EncryptionContext: kp.CipherData.MaterialDescription,
    +			EncryptionContext: md,
     			KeyId:             kp.cmkID,
     			KeySpec:           aws.String("AES_256"),
     		})
     	if err != nil {
     		return CipherData{}, err
     	}
     
    -	iv := generateBytes(ivSize)
    +	iv, err := generateBytes(ivSize)
    +	if err != nil {
    +		return CipherData{}, err
    +	}
    +
     	cd := CipherData{
     		Key:                 out.Plaintext,
     		IV:                  iv,
    -		WrapAlgorithm:       KMSWrap,
    -		MaterialDescription: kp.CipherData.MaterialDescription,
    +		WrapAlgorithm:       wrapAlgorithm,
    +		MaterialDescription: md,
     		EncryptedKey:        out.CiphertextBlob,
     	}
     	return cd, nil
     }
    +
    +func (kp *kmsKeyHandler) isUsingDeprecatedFeatures() error {
    +	if !kp.withContext {
    +		return errDeprecatedCipherDataGenerator
    +	}
    +	return nil
    +}
    
  • service/s3/s3crypto/kms_key_handler_test.go+102 4 modified
    @@ -4,7 +4,9 @@ import (
     	"bytes"
     	"encoding/base64"
     	"encoding/hex"
    +	"encoding/json"
     	"fmt"
    +	"io/ioutil"
     	"net/http"
     	"net/http/httptest"
     	"reflect"
    @@ -105,10 +107,98 @@ func TestKMSDecrypt(t *testing.T) {
     	}
     }
     
    -func TestKMSDecryptBadJSON(t *testing.T) {
    +func TestKMSContextGenerateCipherData(t *testing.T) {
    +	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    +		bodyBytes, err := ioutil.ReadAll(r.Body)
    +		if err != nil {
    +			w.WriteHeader(500)
    +			return
    +		}
    +		var body map[string]interface{}
    +		err = json.Unmarshal(bodyBytes, &body)
    +		if err != nil {
    +			w.WriteHeader(500)
    +			return
    +		}
    +
    +		md, ok := body["EncryptionContext"].(map[string]interface{})
    +		if !ok {
    +			w.WriteHeader(500)
    +			return
    +		}
    +
    +		exEncContext := map[string]interface{}{
    +			"aws:" + cekAlgorithmHeader: "cekAlgValue",
    +		}
    +
    +		if e, a := exEncContext, md; !reflect.DeepEqual(e, a) {
    +			w.WriteHeader(500)
    +			t.Errorf("expected %v, got %v", e, a)
    +			return
    +		}
    +
    +		fmt.Fprintln(w, `{"CiphertextBlob":"AQEDAHhqBCCY1MSimw8gOGcUma79cn4ANvTtQyv9iuBdbcEF1QAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDJ6IcN5E4wVbk38MNAIBEIA7oF1E3lS7FY9DkoxPc/UmJsEwHzL82zMqoLwXIvi8LQHr8If4Lv6zKqY8u0+JRgSVoqCvZDx3p8Cn6nM=","KeyId":"arn:aws:kms:us-west-2:042062605278:key/c80a5cdb-8d09-4f9f-89ee-df01b2e3870a","Plaintext":"6tmyz9JLBE2yIuU7iXpArqpDVle172WSmxjcO6GNT7E="}`)
    +	}))
    +	defer ts.Close()
    +
    +	sess := unit.Session.Copy(&aws.Config{
    +		MaxRetries:       aws.Int(0),
    +		Endpoint:         aws.String(ts.URL),
    +		DisableSSL:       aws.Bool(true),
    +		S3ForcePathStyle: aws.Bool(true),
    +		Region:           aws.String("us-west-2"),
    +	})
    +
    +	svc := kms.New(sess)
    +	handler := NewKMSContextKeyGenerator(svc, "testid")
    +
    +	keySize := 32
    +	ivSize := 16
    +
    +	cd, err := handler.GenerateCipherDataWithCEKAlg(keySize, ivSize, "cekAlgValue")
    +	if err != nil {
    +		t.Errorf("expected no error, but received %v", err)
    +	}
    +	if keySize != len(cd.Key) {
    +		t.Errorf("expected %d, but received %d", keySize, len(cd.Key))
    +	}
    +	if ivSize != len(cd.IV) {
    +		t.Errorf("expected %d, but received %d", ivSize, len(cd.IV))
    +	}
    +}
    +
    +func TestKMSContextDecrypt(t *testing.T) {
     	key, _ := hex.DecodeString("31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22")
     	keyB64 := base64.URLEncoding.EncodeToString(key)
     	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    +		bodyBytes, err := ioutil.ReadAll(r.Body)
    +		if err != nil {
    +			w.WriteHeader(500)
    +			return
    +		}
    +		var body map[string]interface{}
    +		err = json.Unmarshal(bodyBytes, &body)
    +		if err != nil {
    +			w.WriteHeader(500)
    +			return
    +		}
    +
    +		md, ok := body["EncryptionContext"].(map[string]interface{})
    +		if !ok {
    +			w.WriteHeader(500)
    +			return
    +		}
    +
    +		exEncContext := map[string]interface{}{
    +			"aws:" + cekAlgorithmHeader: "cekAlgValue",
    +		}
    +
    +		if e, a := exEncContext, md; !reflect.DeepEqual(e, a) {
    +			w.WriteHeader(500)
    +			t.Errorf("expected %v, got %v", e, a)
    +			return
    +		}
    +
     		fmt.Fprintln(w, fmt.Sprintf("%s%s%s", `{"KeyId":"test-key-id","Plaintext":"`, keyB64, `"}`))
     	}))
     	defer ts.Close()
    @@ -120,9 +210,17 @@ func TestKMSDecryptBadJSON(t *testing.T) {
     		S3ForcePathStyle: aws.Bool(true),
     		Region:           aws.String("us-west-2"),
     	})
    +	handler, err := NewKMSContextWrapEntry(kms.New(sess))(Envelope{MatDesc: `{"aws:x-amz-cek-alg": "cekAlgValue"}`})
    +	if err != nil {
    +		t.Errorf("expected no error, but received %v", err)
    +	}
    +
    +	plaintextKey, err := handler.DecryptKey([]byte{1, 2, 3, 4})
    +	if err != nil {
    +		t.Errorf("expected no error, but received %v", err)
    +	}
     
    -	_, err := (kmsKeyHandler{kms: kms.New(sess)}).decryptHandler(Envelope{MatDesc: `{"kms_cmk_id":"test"`})
    -	if err == nil {
    -		t.Errorf("expected error, but received none")
    +	if !bytes.Equal(key, plaintextKey) {
    +		t.Errorf("expected %v, but received %v", key, plaintextKey)
     	}
     }
    
  • service/s3/s3crypto/migrations_test.go+148 0 added
    @@ -0,0 +1,148 @@
    +package s3crypto_test
    +
    +import (
    +	"bytes"
    +	"fmt"
    +	"io/ioutil"
    +
    +	"github.com/aws/aws-sdk-go/aws"
    +	"github.com/aws/aws-sdk-go/aws/session"
    +	"github.com/aws/aws-sdk-go/service/kms"
    +	"github.com/aws/aws-sdk-go/service/s3"
    +	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
    +)
    +
    +// ExampleNewEncryptionClientV2_migration00 provides a migration example for how users can migrate from the V1
    +// encryption client to the V2 encryption client. This example demonstrates how an application using  the `kms` key wrap
    +// algorithm with `AES/CBC/PKCS5Padding` can migrate their application to `kms+context` key wrapping with
    +// `AES/GCM/NoPadding` content encryption.
    +func ExampleNewEncryptionClientV2_migration00() {
    +	sess := session.Must(session.NewSession())
    +	kmsClient := kms.New(sess)
    +	cmkID := "1234abcd-12ab-34cd-56ef-1234567890ab"
    +
    +	// Usage of NewKMSKeyGenerator (kms) key wrapping algorithm must be migrated to NewKMSContextKeyGenerator (kms+context) key wrapping algorithm
    +	//
    +	// cipherDataGenerator := s3crypto.NewKMSKeyGenerator(kmsClient, cmkID)
    +	cipherDataGenerator := s3crypto.NewKMSContextKeyGenerator(kmsClient, cmkID)
    +
    +	// Usage of AESCBCContentCipherBuilder (AES/CBC/PKCS5Padding) must be migrated to AESGCMContentCipherBuilder (AES/GCM/NoPadding)
    +	//
    +	// contentCipherBuilder := s3crypto.AESCBCContentCipherBuilder(cipherDataGenerator, s3crypto.AESCBCPadder)
    +	contentCipherBuilder := s3crypto.AESGCMContentCipherBuilder(cipherDataGenerator)
    +
    +	// Construction of an encryption client should be done using NewEncryptionClientV2
    +	//
    +	// encryptionClient := s3crypto.NewEncryptionClient(sess, contentCipherBuilder)
    +	encryptionClient, err := s3crypto.NewEncryptionClientV2(sess, contentCipherBuilder)
    +	if err != nil {
    +		fmt.Printf("failed to construct encryption client: %v", err)
    +		return
    +	}
    +
    +	_, err = encryptionClient.PutObject(&s3.PutObjectInput{
    +		Bucket: aws.String("your_bucket"),
    +		Key:    aws.String("your_key"),
    +		Body:   bytes.NewReader([]byte("your content")),
    +	})
    +	if err != nil {
    +		fmt.Printf("put object error: %v\n", err)
    +		return
    +	}
    +	fmt.Println("put object completed")
    +}
    +
    +// ExampleNewEncryptionClientV2_migration01 provides a more advanced migration example for how users can
    +// migrate from the V1 encryption client to the V2 encryption client using more complex client construction.
    +func ExampleNewEncryptionClientV2_migration01() {
    +	sess := session.Must(session.NewSession())
    +	kmsClient := kms.New(sess)
    +	cmkID := "1234abcd-12ab-34cd-56ef-1234567890ab"
    +
    +	cipherDataGenerator := s3crypto.NewKMSContextKeyGenerator(kmsClient, cmkID)
    +
    +	contentCipherBuilder := s3crypto.AESGCMContentCipherBuilder(cipherDataGenerator)
    +
    +	// Overriding of the encryption client options is possible by passing in functional arguments that override the
    +	// provided EncryptionClientOptions.
    +	//
    +	// encryptionClient := s3crypto.NewEncryptionClient(cipherDataGenerator, contentCipherBuilder, func(o *s3crypto.EncryptionClient) {
    +	//	 o.S3Client = s3.New(sess, &aws.Config{Region: aws.String("us-west-2")}),
    +	// })
    +	encryptionClient, err := s3crypto.NewEncryptionClientV2(sess, contentCipherBuilder, func(o *s3crypto.EncryptionClientOptions) {
    +		o.S3Client = s3.New(sess, &aws.Config{Region: aws.String("us-west-2")})
    +	})
    +	if err != nil {
    +		fmt.Printf("failed to construct encryption client: %v", err)
    +		return
    +	}
    +
    +	_, err = encryptionClient.PutObject(&s3.PutObjectInput{
    +		Bucket: aws.String("your_bucket"),
    +		Key:    aws.String("your_key"),
    +		Body:   bytes.NewReader([]byte("your content")),
    +	})
    +	if err != nil {
    +		fmt.Printf("put object error: %v\n", err)
    +		return
    +	}
    +	fmt.Println("put object completed")
    +}
    +
    +// ExampleNewDecryptionClientV2_migration00 provides a migration example for how users can migrate
    +// from the V1 Decryption Clients to the V2 Decryption Clients.
    +func ExampleNewDecryptionClientV2_migration00() {
    +	sess := session.Must(session.NewSession())
    +
    +	// Construction of an decryption client must be done using NewDecryptionClientV2
    +	// The V2 decryption client is able to decrypt object encrypted by the V1 client.
    +	//
    +	// decryptionClient := s3crypto.NewDecryptionClient(sess)
    +	decryptionClient := s3crypto.NewDecryptionClientV2(sess)
    +
    +	getObject, err := decryptionClient.GetObject(&s3.GetObjectInput{
    +		Bucket: aws.String("your_bucket"),
    +		Key:    aws.String("your_key"),
    +	})
    +	if err != nil {
    +		fmt.Printf("get object error: %v\n", err)
    +		return
    +	}
    +
    +	_, err = ioutil.ReadAll(getObject.Body)
    +	if err != nil {
    +		fmt.Printf("error reading object: %v\n", err)
    +	}
    +	fmt.Println("get object completed")
    +}
    +
    +// ExampleNewDecryptionClientV2_migration01 provides a more advanced migration example for how users can
    +// migrate from the V1 decryption client to the V2 decryption client using more complex client construction.
    +func ExampleNewDecryptionClientV2_migration01() {
    +	sess := session.Must(session.NewSession())
    +
    +	// Construction of an decryption client must be done using NewDecryptionClientV2
    +	// The V2 decryption client is able to decrypt object encrypted by the V1 client.
    +	//
    +	// decryptionClient := s3crypto.NewDecryptionClient(sess, func(o *s3crypto.DecryptionClient) {
    +	//	 o.S3Client = s3.New(sess, &aws.Config{Region: aws.String("us-west-2")})
    +	//})
    +	decryptionClient := s3crypto.NewDecryptionClientV2(sess, func(o *s3crypto.DecryptionClientOptions) {
    +		o.S3Client = s3.New(sess, &aws.Config{Region: aws.String("us-west-2")})
    +	})
    +
    +	getObject, err := decryptionClient.GetObject(&s3.GetObjectInput{
    +		Bucket: aws.String("your_bucket"),
    +		Key:    aws.String("your_key"),
    +	})
    +	if err != nil {
    +		fmt.Printf("get object error: %v\n", err)
    +		return
    +	}
    +
    +	_, err = ioutil.ReadAll(getObject.Body)
    +	if err != nil {
    +		fmt.Printf("error reading object: %v\n", err)
    +	}
    +	fmt.Println("get object completed")
    +}
    
  • service/s3/s3crypto/mock_test.go+1 3 modified
    @@ -8,8 +8,7 @@ import (
     	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
     )
     
    -type mockGenerator struct {
    -}
    +type mockGenerator struct{}
     
     func (m mockGenerator) GenerateCipherData(keySize, ivSize int) (s3crypto.CipherData, error) {
     	cd := s3crypto.CipherData{
    @@ -27,7 +26,6 @@ func (m mockGenerator) EncryptKey(key []byte) ([]byte, error) {
     
     func (m mockGenerator) DecryptKey(key []byte) ([]byte, error) {
     	return make([]byte, 16), nil
    -
     }
     
     type mockCipherBuilder struct {
    
  • service/s3/s3crypto/shared_client.go+210 0 added
    @@ -0,0 +1,210 @@
    +package s3crypto
    +
    +import (
    +	"encoding/base64"
    +	"encoding/hex"
    +	"io"
    +	"strings"
    +
    +	"github.com/aws/aws-sdk-go/aws"
    +	"github.com/aws/aws-sdk-go/aws/awserr"
    +	"github.com/aws/aws-sdk-go/aws/request"
    +	"github.com/aws/aws-sdk-go/internal/sdkio"
    +	"github.com/aws/aws-sdk-go/service/s3"
    +)
    +
    +func putObjectRequest(c EncryptionClientOptions, input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
    +	req, out := c.S3Client.PutObjectRequest(input)
    +
    +	// Get Size of file
    +	n, err := aws.SeekerLen(input.Body)
    +	if err != nil {
    +		req.Error = err
    +		return req, out
    +	}
    +
    +	dst, err := getWriterStore(req, c.TempFolderPath, n >= c.MinFileSize)
    +	if err != nil {
    +		req.Error = err
    +		return req, out
    +	}
    +
    +	req.Handlers.Build.PushFront(func(r *request.Request) {
    +		if err != nil {
    +			r.Error = err
    +			return
    +		}
    +		var encryptor ContentCipher
    +		if v, ok := c.ContentCipherBuilder.(ContentCipherBuilderWithContext); ok {
    +			encryptor, err = v.ContentCipherWithContext(r.Context())
    +		} else {
    +			encryptor, err = c.ContentCipherBuilder.ContentCipher()
    +		}
    +		if err != nil {
    +			r.Error = err
    +			return
    +		}
    +
    +		md5 := newMD5Reader(input.Body)
    +		sha := newSHA256Writer(dst)
    +		reader, err := encryptor.EncryptContents(md5)
    +		if err != nil {
    +			r.Error = err
    +			return
    +		}
    +
    +		_, err = io.Copy(sha, reader)
    +		if err != nil {
    +			r.Error = err
    +			return
    +		}
    +
    +		data := encryptor.GetCipherData()
    +		env, err := encodeMeta(md5, data)
    +		if err != nil {
    +			r.Error = err
    +			return
    +		}
    +
    +		shaHex := hex.EncodeToString(sha.GetValue())
    +		req.HTTPRequest.Header.Set("X-Amz-Content-Sha256", shaHex)
    +
    +		dst.Seek(0, sdkio.SeekStart)
    +		input.Body = dst
    +
    +		err = c.SaveStrategy.Save(env, r)
    +		r.Error = err
    +	})
    +
    +	return req, out
    +}
    +
    +func putObject(options EncryptionClientOptions, input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
    +	req, out := putObjectRequest(options, input)
    +	return out, req.Send()
    +}
    +
    +func putObjectWithContext(options EncryptionClientOptions, ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) {
    +	req, out := putObjectRequest(options, input)
    +	req.SetContext(ctx)
    +	req.ApplyOptions(opts...)
    +	return out, req.Send()
    +}
    +
    +func getObjectRequest(options DecryptionClientOptions, input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
    +	req, out := options.S3Client.GetObjectRequest(input)
    +	req.Handlers.Unmarshal.PushBack(func(r *request.Request) {
    +		env, err := options.LoadStrategy.Load(r)
    +		if err != nil {
    +			r.Error = err
    +			out.Body.Close()
    +			return
    +		}
    +
    +		// If KMS should return the correct CEK algorithm with the proper
    +		// KMS key provider
    +		cipher, err := contentCipherFromEnvelope(options, r.Context(), env)
    +		if err != nil {
    +			r.Error = err
    +			out.Body.Close()
    +			return
    +		}
    +
    +		reader, err := cipher.DecryptContents(out.Body)
    +		if err != nil {
    +			r.Error = err
    +			out.Body.Close()
    +			return
    +		}
    +		out.Body = reader
    +	})
    +	return req, out
    +}
    +
    +func getObject(options DecryptionClientOptions, input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
    +	req, out := getObjectRequest(options, input)
    +	return out, req.Send()
    +}
    +
    +func getObjectWithContext(options DecryptionClientOptions, ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) {
    +	req, out := getObjectRequest(options, input)
    +	req.SetContext(ctx)
    +	req.ApplyOptions(opts...)
    +	return out, req.Send()
    +}
    +
    +func contentCipherFromEnvelope(options DecryptionClientOptions, ctx aws.Context, env Envelope) (ContentCipher, error) {
    +	wrap, err := wrapFromEnvelope(options, env)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	return cekFromEnvelope(options, ctx, env, wrap)
    +}
    +
    +func wrapFromEnvelope(options DecryptionClientOptions, env Envelope) (CipherDataDecrypter, error) {
    +	f, ok := options.WrapRegistry[env.WrapAlg]
    +	if !ok || f == nil {
    +		return nil, awserr.New(
    +			"InvalidWrapAlgorithmError",
    +			"wrap algorithm isn't supported, "+env.WrapAlg,
    +			nil,
    +		)
    +	}
    +	return f(env)
    +}
    +
    +func cekFromEnvelope(options DecryptionClientOptions, ctx aws.Context, env Envelope, decrypter CipherDataDecrypter) (ContentCipher, error) {
    +	f, ok := options.CEKRegistry[env.CEKAlg]
    +	if !ok || f == nil {
    +		return nil, awserr.New(
    +			"InvalidCEKAlgorithmError",
    +			"cek algorithm isn't supported, "+env.CEKAlg,
    +			nil,
    +		)
    +	}
    +
    +	key, err := base64.StdEncoding.DecodeString(env.CipherKey)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	iv, err := base64.StdEncoding.DecodeString(env.IV)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	if d, ok := decrypter.(CipherDataDecrypterWithContext); ok {
    +		key, err = d.DecryptKeyWithContext(ctx, key)
    +	} else {
    +		key, err = decrypter.DecryptKey(key)
    +	}
    +
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	cd := CipherData{
    +		Key:          key,
    +		IV:           iv,
    +		CEKAlgorithm: env.CEKAlg,
    +		Padder:       getPadder(options, env.CEKAlg),
    +	}
    +	return f(cd)
    +}
    +
    +// getPadder will return an unpadder with checking the cek algorithm specific padder.
    +// If there wasn't a cek algorithm specific padder, we check the padder itself.
    +// We return a no unpadder, if no unpadder was found. This means any customization
    +// either contained padding within the cipher implementation, and to maintain
    +// backwards compatility we will simply not unpad anything.
    +func getPadder(options DecryptionClientOptions, cekAlg string) Padder {
    +	padder, ok := options.PadderRegistry[cekAlg]
    +	if !ok {
    +		padder, ok = options.PadderRegistry[cekAlg[strings.LastIndex(cekAlg, "/")+1:]]
    +		if !ok {
    +			return NoPadder
    +		}
    +	}
    +	return padder
    +}
    
  • service/s3/s3crypto/strategy.go+0 1 modified
    @@ -63,7 +63,6 @@ func (strat HeaderV2SaveStrategy) Save(env Envelope, req *request.Request) error
     	input.Metadata[http.CanonicalHeaderKey(matDescHeader)] = &env.MatDesc
     	input.Metadata[http.CanonicalHeaderKey(wrapAlgorithmHeader)] = &env.WrapAlg
     	input.Metadata[http.CanonicalHeaderKey(cekAlgorithmHeader)] = &env.CEKAlg
    -	input.Metadata[http.CanonicalHeaderKey(unencryptedMD5Header)] = &env.UnencryptedMD5
     	input.Metadata[http.CanonicalHeaderKey(unencryptedContentLengthHeader)] = &env.UnencryptedContentLen
     
     	if len(env.TagLen) > 0 {
    
  • service/s3/s3crypto/strategy_test.go+105 2 modified
    @@ -1,11 +1,16 @@
     package s3crypto_test
     
     import (
    +	"bytes"
    +	"encoding/json"
    +	"io/ioutil"
    +	"net/http"
     	"reflect"
     	"testing"
     
     	"github.com/aws/aws-sdk-go/aws"
     	"github.com/aws/aws-sdk-go/aws/request"
    +	"github.com/aws/aws-sdk-go/awstesting/unit"
     	"github.com/aws/aws-sdk-go/service/s3"
     	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
     )
    @@ -33,7 +38,6 @@ func TestHeaderV2SaveStrategy(t *testing.T) {
     				"X-Amz-Wrap-Alg":                   aws.String(s3crypto.KMSWrap),
     				"X-Amz-Cek-Alg":                    aws.String(s3crypto.AESGCMNoPadding),
     				"X-Amz-Tag-Len":                    aws.String("128"),
    -				"X-Amz-Unencrypted-Content-Md5":    aws.String("hello"),
     				"X-Amz-Unencrypted-Content-Length": aws.String("0"),
     			},
     		},
    @@ -53,7 +57,6 @@ func TestHeaderV2SaveStrategy(t *testing.T) {
     				"X-Amz-Matdesc":                    aws.String("{}"),
     				"X-Amz-Wrap-Alg":                   aws.String(s3crypto.KMSWrap),
     				"X-Amz-Cek-Alg":                    aws.String(s3crypto.AESGCMNoPadding),
    -				"X-Amz-Unencrypted-Content-Md5":    aws.String("hello"),
     				"X-Amz-Unencrypted-Content-Length": aws.String("0"),
     			},
     		},
    @@ -75,3 +78,103 @@ func TestHeaderV2SaveStrategy(t *testing.T) {
     		}
     	}
     }
    +
    +func TestS3SaveStrategy(t *testing.T) {
    +	cases := []struct {
    +		env      s3crypto.Envelope
    +		expected s3crypto.Envelope
    +	}{
    +		{
    +			s3crypto.Envelope{
    +				CipherKey:             "Foo",
    +				IV:                    "Bar",
    +				MatDesc:               "{}",
    +				WrapAlg:               s3crypto.KMSWrap,
    +				CEKAlg:                s3crypto.AESGCMNoPadding,
    +				TagLen:                "128",
    +				UnencryptedMD5:        "hello",
    +				UnencryptedContentLen: "0",
    +			},
    +			s3crypto.Envelope{
    +				CipherKey:             "Foo",
    +				IV:                    "Bar",
    +				MatDesc:               "{}",
    +				WrapAlg:               s3crypto.KMSWrap,
    +				CEKAlg:                s3crypto.AESGCMNoPadding,
    +				TagLen:                "128",
    +				UnencryptedContentLen: "0",
    +			},
    +		},
    +		{
    +			s3crypto.Envelope{
    +				CipherKey:             "Foo",
    +				IV:                    "Bar",
    +				MatDesc:               "{}",
    +				WrapAlg:               s3crypto.KMSWrap,
    +				CEKAlg:                s3crypto.AESGCMNoPadding,
    +				UnencryptedMD5:        "hello",
    +				UnencryptedContentLen: "0",
    +			},
    +			s3crypto.Envelope{
    +				CipherKey:             "Foo",
    +				IV:                    "Bar",
    +				MatDesc:               "{}",
    +				WrapAlg:               s3crypto.KMSWrap,
    +				CEKAlg:                s3crypto.AESGCMNoPadding,
    +				UnencryptedContentLen: "0",
    +			},
    +		},
    +	}
    +
    +	for _, c := range cases {
    +		params := &s3.PutObjectInput{
    +			Bucket: aws.String("fooBucket"),
    +			Key:    aws.String("barKey"),
    +		}
    +		req := &request.Request{
    +			Params: params,
    +		}
    +
    +		client := s3.New(unit.Session)
    +
    +		client.Handlers.Send.Clear()
    +		client.Handlers.Unmarshal.Clear()
    +		client.Handlers.UnmarshalMeta.Clear()
    +		client.Handlers.UnmarshalError.Clear()
    +		client.Handlers.Send.PushBack(func(r *request.Request) {
    +			bodyBytes, err := ioutil.ReadAll(r.Body)
    +			if err != nil {
    +				r.HTTPResponse = &http.Response{
    +					StatusCode: 500,
    +					Body:       ioutil.NopCloser(bytes.NewReader([]byte(err.Error()))),
    +				}
    +				return
    +			}
    +
    +			var actual s3crypto.Envelope
    +			err = json.Unmarshal(bodyBytes, &actual)
    +			if err != nil {
    +				r.HTTPResponse = &http.Response{
    +					StatusCode: 500,
    +					Body:       ioutil.NopCloser(bytes.NewReader([]byte(err.Error()))),
    +				}
    +				return
    +			}
    +
    +			if e, a := c.expected, actual; !reflect.DeepEqual(e, a) {
    +				t.Errorf("expected %v, got %v", e, a)
    +			}
    +
    +			r.HTTPResponse = &http.Response{
    +				StatusCode: 200,
    +				Body:       ioutil.NopCloser(bytes.NewReader([]byte(""))),
    +			}
    +		})
    +
    +		strat := s3crypto.S3SaveStrategy{Client: client}
    +		err := strat.Save(c.env, req)
    +		if err != nil {
    +			t.Errorf("expected no error, but received %v", err)
    +		}
    +	}
    +}
    
  • service/s3/s3crypto/testdata/aes_gcm.json+56 0 added
    @@ -0,0 +1,56 @@
    +[
    +  {
    +    "comment": "AES-128-GCM",
    +    "key": "DA2FDB0CED551AEB723D8AC1A267CEF3",
    +    "pt": "",
    +    "aad": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "iv": "A5F5160B7B0B025757ACCDAA",
    +    "ct": "",
    +    "tag": "7AD0758C4FA9B8660AA0687B3E7BD517"
    +  },
    +  {
    +    "comment": "AES-128-GCM",
    +    "key": "4194935CF4524DF93D62FEDBC818D8AC",
    +    "pt": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "aad": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "iv": "0C5A8F5AF7F6064C0130EE64",
    +    "ct": "3F4CC9A7451717E5E939D294A1362B32C274D06411188DAD76AEE3EE4DA46483EA4C1AF38B9B74D7AD2FD8E310CF82",
    +    "tag": "AD563FD10E1EFA3F26753F46E09DB3A0"
    +  },
    +  {
    +    "comment": "AES-128-GCM",
    +    "key": "AD03EE2FD6048DB7158CEC55D3D760BC",
    +    "pt": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "aad": "",
    +    "iv": "1B813A16DDCB7F08D26E2541",
    +    "ct": "ADD161BE957AE9EC3CEE6600C77FF81D64A80242A510A9D5AD872096C79073B61E8237FAA7D63A3301EA58EC11332C",
    +    "tag": "01944370EC28601ADC989DE05A794AEB"
    +  },
    +  {
    +    "comment": "AES-256-GCM",
    +    "key": "20142E898CD2FD980FBF34DE6BC85C14DA7D57BD28F4AA5CF1728AB64E843142",
    +    "pt": "",
    +    "aad": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "iv": "FB7B4A824E82DAA6C8BC1251",
    +    "ct": "",
    +    "tag": "81C0E42BB195E262CB3B3A74A0DAE1C8"
    +  },
    +  {
    +    "comment": "AES-256-GCM",
    +    "key": "D211F278A44EAB666B1021F4B4F60BA6B74464FA9CB7B134934D7891E1479169",
    +    "pt": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "aad": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "iv": "6B5CD3705A733C1AD943D58A",
    +    "ct": "4C25ABD66D3A1BCCE794ACAAF4CEFDF6D2552F4A82C50A98CB15B4812FF557ABE564A9CEFF15F32DCF5A5AA7894888",
    +    "tag": "03EDE71EC952E65AE7B4B85CFEC7D304"
    +  },
    +  {
    +    "comment": "AES-256-GCM",
    +    "key": "CFE8BFE61B89AF53D2BECE744D27B78C9E4D74D028CE88ED10A422285B1201C9",
    +    "pt": "167B5C226177733A782D616D7A2D63656B2D616C675C223A205C224145532F47434D2F4E6F50616464696E675C227D",
    +    "aad": "",
    +    "iv": "5F08EFBFB7BF5BA365D9EB1D",
    +    "ct": "0A7E82F1E5C76C69679671EEAEE455936F2C4FCCD9DDF1FAA27075E2040644938920C5D16C69E4D93375487B9A80D4",
    +    "tag": "04347D0C5B0E0DE89E033D04D0493DCA"
    +  }
    +]
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.