CVE-2022-2582 (Go, published 2022-12-27T22:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 4.3)
Weakness: CWE-326 \u2014 Inadequate Encryption Strength
The product stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required.
Repository: https://github.com/aws/aws-sdk-go
Files changed:
CHANGELOG_PENDING.md
internal/sync/singleflight/singleflight_test.go
service/s3/s3crypto/aes_cbc_content_cipher.go
service/s3/s3crypto/aes_gcm_content_cipher.go
service/s3/s3crypto/aes_gcm_content_cipher_test.go
service/s3/s3crypto/aes_gcm_test.go
service/s3/s3crypto/cipher_util.go
service/s3/s3crypto/cipher_util_test.go
service/s3/s3crypto/decryption_client.go
service/s3/s3crypto/decryption_client_v2.go
service/s3/s3crypto/decryption_client_v2_test.go
service/s3/s3crypto/deprecations.go
service/s3/s3crypto/deprecations_test.go
service/s3/s3crypto/doc.go
service/s3/s3crypto/encryption_client.go
service/s3/s3crypto/encryption_client_v2.go
service/s3/s3crypto/envelope.go
service/s3/s3crypto/integration/main_test.go
service/s3/s3crypto/key_handler.go
service/s3/s3crypto/key_handler_test.go
service/s3/s3crypto/kms_key_handler.go
service/s3/s3crypto/kms_key_handler_test.go
service/s3/s3crypto/migrations_test.go
service/s3/s3crypto/mock_test.go
service/s3/s3crypto/shared_client.go
service/s3/s3crypto/strategy.go
service/s3/s3crypto/strategy_test.go
service/s3/s3crypto/testdata/aes_gcm.json
Commit message: service/s3/s3crypto: V2 Client Release (#3403)



Vulnerable code:
	if v, ok := builder.generator.(CipherDataGeneratorWithContext); ok {
	} else {
	expected, _ := hex.DecodeString("fa4362189661d163fcd6a56d8bf0405ad636ac1bbedd5cc3ee727dc2ab4a9489")
	expected, _ := hex.DecodeString("eb8e6175f1fe38eb1acf95fd5188a8b74bb74fda553e91020a23deed45")
	expected, _ := hex.DecodeString("44dc868006b21d49284016565ffb3979cc4271d967628bf7cdaf86db888e92e501a2b578aa2f41ec6379a44a31cc019c")
	expected, _ := hex.DecodeString("bbca4a9e09ae9690c0f6f8d405e53dccd666aa9c5fa13c8758bc30abe1ddd1bcce0d36a1eaaaaffef20cd3c5970b9673f8a65c26ccecb9976fd6ac9c2c0f372c52c821")
	etag := ciphertext[len(ciphertext)-16:]
	if !bytes.Equal(ciphertext, expected) {
	"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)
}

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
}

	c := DecryptionClient{
	wrap, err := c.wrapFromEnvelope(env)
	c := DecryptionClient{
	wrap, err := c.wrapFromEnvelope(env)
	c := DecryptionClient{
	wrap, err := c.wrapFromEnvelope(env)
	c := DecryptionClient{
	wrap, err := c.wrapFromEnvelope(env)
	cek, err := c.cekFromEnvelope(aws.BackgroundContext(), env, wrap)
	c := DecryptionClient{
	wrap, err := c.wrapFromEnvelope(env)
	cek, err := c.cekFromEnvelope(aws.BackgroundContext(), env, wrap)
	c := DecryptionClient{
	wrap, err := c.wrapFromEnvelope(env)
	cek, err := c.cekFromEnvelope(aws.BackgroundContext(), env, wrap)
//	sess := session.New()
	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
	req, out := c.GetObjectRequest(input)
	return out, req.Send()
	req, out := c.GetObjectRequest(input)
	req.SetContext(ctx)
	req.ApplyOptions(opts...)
	return out, req.Send()
	sess := session.New()
	handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
	svc := s3crypto.NewEncryptionClient(sess, s3crypto.AESGCMContentCipherBuilder(handler))
	svc := s3crypto.NewDecryptionClient(sess)
	cfg := s3crypto.EncryptionConfig{
		SaveStrategy: NewS3SaveStrategy(session.New(), ""),
		InstructionFileSuffix: ".example",
		TempFolderPath: "/path/to/tmp/folder/",
		MinFileSize: int64(1024 * 1024 * 1024),
	}
	svc := s3crypto.NewDecryptionClient(sess)
	svc.WrapRegistry["AESWrap"] = NewAESWrap
	svc.CEKRegistry["AES/CTR/NoPadding"] = NewAESCTR
the wrap as `AESWrap` then it'll use that wrap algorithm. This is also true for `AES/CTR/NoPadding`.
	// Our wrap algorithm, AESWrap
	handler := NewAESWrap(key, iv)
	// Our content cipher builder, AESCTRContentCipherBuilder
	svc := s3crypto.NewEncryptionClient(sess, NewAESCTRContentCipherBuilder(handler))
	"encoding/hex"
	"io"

	"github.com/aws/aws-sdk-go/internal/sdkio"
//	sess := session.New()
//	svc := s3crypto.New(sess, s3crypto.AESGCMContentCipherBuilder(handler))
	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
	req, out := c.PutObjectRequest(input)
	return out, req.Send()
	req, out := c.PutObjectRequest(input)
	req.SetContext(ctx)
	req.ApplyOptions(opts...)
	return out, req.Send()
	UnencryptedMD5        string `json:"x-amz-unencrypted-content-md5"`
func generateBytes(n int) []byte {
	rand.Read(b)
	return b
	b := generateBytes(5)
	b = generateBytes(0)
	b = generateBytes(1024)
	kms   kmsiface.KMSAPI
	cmkID *string
// NewKMSKeyGeneratorWithMatDesc builds a new KMS key provider using the customer key ID and material
//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
func NewKMSKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGenerator {
	matdesc["kms_cmk_id"] = &cmkID
	kp := &kmsKeyHandler{
		kms:   kmsClient,
		cmkID: &cmkID,
	// These values are read only making them thread safe
	kp.CipherData.WrapAlgorithm = KMSWrap
	if !ok {
			EncryptionContext: kp.CipherData.MaterialDescription,
	iv := generateBytes(ivSize)
		WrapAlgorithm:       KMSWrap,
		MaterialDescription: kp.CipherData.MaterialDescription,
func TestKMSDecryptBadJSON(t *testing.T) {
	_, err := (kmsKeyHandler{kms: kms.New(sess)}).decryptHandler(Envelope{MatDesc: `{"kms_cmk_id":"test"`})
	if err == nil {
		t.Errorf("expected error, but received none")
type mockGenerator struct {
}

	input.Metadata[http.CanonicalHeaderKey(unencryptedMD5Header)] = &env.UnencryptedMD5
				"X-Amz-Unencrypted-Content-Md5":    aws.String("hello"),
				"X-Amz-Unencrypted-Content-Md5":    aws.String("hello"),

Fixed code:
* `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.
	t.Skip("singleflight tests not stable")
	t.Skip("singleflight tests not stable")
	t.Skip("singleflight tests not stable")
	t.Skip("singleflight tests not stable")
func (cbcContentCipherBuilder) isUsingDeprecatedFeatures() error {
	return errDeprecatedCipherBuilder
}

//
// deprecated: This content cipher builder has been deprecated. Users should migrate to AESGCMContentCipherBuilder
func (builder gcmContentCipherBuilder) isUsingDeprecatedFeatures() error {
	if feature, ok := builder.generator.(deprecatedFeatures); ok {
		return feature.isUsingDeprecatedFeatures()
	}
	return nil
}

	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:
	default:
	"github.com/aws/aws-sdk-go/service/kms/kmsiface"

type mockKMS struct {
	kmsiface.KMSAPI
}
// +build go1.9

	"encoding/json"
	expected, _ := hex.DecodeString("fa4362189661d163fcd6a56d8bf0405a")
	expected, _ := hex.DecodeString("eb8e6175f1fe38eb1acf95fd51")
	expected, _ := hex.DecodeString("44dc868006b21d49284016565ffb3979cc4271d967628bf7cdaf86db888e92e5")
	expected, _ := hex.DecodeString("bbca4a9e09ae9690c0f6f8d405e53dccd666aa9c5fa13c8758bc30abe1ddd1bcce0d36a1eaaaaffef20cd3c5970b9673f8a65c")
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)
		})
	}
}

	t.Helper()
	const gcmTagSize = 16
	etag := ciphertext[len(ciphertext)-gcmTagSize:]
	if !bytes.Equal(ciphertext[:len(ciphertext)-gcmTagSize], expected) {
	o := DecryptionClientOptions{
	wrap, err := wrapFromEnvelope(o, env)
	o := DecryptionClientOptions{
	wrap, err := wrapFromEnvelope(o, env)
	o := DecryptionClientOptions{
	wrap, err := wrapFromEnvelope(o, env)
	o := DecryptionClientOptions{
	wrap, err := wrapFromEnvelope(o, env)
	cek, err := cekFromEnvelope(o, aws.BackgroundContext(), env, wrap)
	o := DecryptionClientOptions{
	wrap, err := wrapFromEnvelope(o, env)
	cek, err := cekFromEnvelope(o, aws.BackgroundContext(), env, wrap)
	o := DecryptionClientOptions{
	wrap, err := wrapFromEnvelope(o, env)
	cek, err := cekFromEnvelope(o, aws.BackgroundContext(), env, wrap)
//
// deprecated: See DecryptionClientV2
//
// deprecated: see NewDecryptionClientV2
//  sess := session.Must(session.NewSession())
//
// deprecated: see DecryptionClientV2.GetObjectRequest
	return getObjectRequest(c.getClientOptions(), input)
//
// deprecated: see DecryptionClientV2.GetObject
	return getObject(c.getClientOptions(), input)
//
// deprecated: see DecryptionClientV2.GetObjectWithContext
	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,
	}
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()
}
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")
	}
}
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
}
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")
	}
}
	sess := session.Must(session.NewSession())
	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilder(handler))
	svc := s3crypto.NewDecryptionClientV2(sess)
	sess := session.Must(session.NewSession())
	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilder(handler), func (o *s3crypto.EncryptionClientOptions) {
		o.SaveStrategy = NewS3SaveStrategy(sess, "")

		o.InstructionFileSuffix = ".example"

		o.TempFolderPath = "/path/to/tmp/folder/"

		o.MinFileSize = int64(1024 * 1024 * 1024)
	})
	svc := s3crypto.NewDecryptionClientV2(sess, func(o *s3crypto.DecryptionClientOptions) {
		o.WrapRegistry["CustomWrap"] = NewCustomWrap
		o.CEKRegistry["CustomCEK"] = NewCustomCEK
	})
the wrap as `CustomWrap` then it'll use that wrap algorithm. This is also true for `CustomCEK`.
	// 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.
//
// deprecated: See EncryptionClientV2
//  sess := session.Must(session.NewSession())
//	svc := s3crypto.NewEncryptionClient(sess, s3crypto.AESGCMContentCipherBuilder(handler))
//
// deprecated: See NewEncryptionClientV2
//
// deprecated: See EncryptionClientV2.PutObjectRequest
	return putObjectRequest(c.getClientOptions(), input)
//
// deprecated: See EncryptionClientV2.PutObject
	return putObject(c.getClientOptions(), input)
// PutObject is a wrapper for PutObjectRequest
//
// deprecated: See EncryptionClientV2.PutObjectWithContext
	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,
	}
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...)
}
	UnencryptedMD5        string `json:"-"`
// +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)
		}
	}
}
// 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)
}

func generateBytes(n int) ([]byte, error) {
	_, err := rand.Read(b)
	if err != nil {
		return nil, err
	}
	return b, nil
	b, _ := generateBytes(5)
	b, _ = generateBytes(0)
	b, _ = generateBytes(1024)
	"fmt"


	// KMSContextWrap is a constant used during decryption to build a kms+context key handler
	KMSContextWrap = "kms+context"
	kms         kmsiface.KMSAPI
	cmkID       *string
	withContext bool
//
// deprecated: See NewKMSContextKeyGenerator
// NewKMSContextKeyGenerator builds a new kms+context key provider using the customer key ID and material
//	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 kp.withContext {
		kp.CipherData.WrapAlgorithm = KMSContextWrap
	} else {
		matdesc["kms_cmk_id"] = &cmkID
		kp.CipherData.WrapAlgorithm = KMSWrap
// 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)
}

//
// deprecated: See NewKMSContextWrapEntry
// 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
}

	if !kp.withContext && !ok {
	if kp.withContext {
		kp.WrapAlgorithm = KMSContextWrap
	}
func (kp kmsKeyHandler) GenerateCipherDataWithCEKAlg(keySize, ivSize int, cekAlgorithm string) (CipherData, error) {
	return kp.GenerateCipherDataWithCEKAlgWithContext(aws.BackgroundContext(), keySize, ivSize, cekAlgorithm)
}

	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
	}

			EncryptionContext: md,
	iv, err := generateBytes(ivSize)
	if err != nil {
		return CipherData{}, err
	}

		WrapAlgorithm:       wrapAlgorithm,
		MaterialDescription: md,

func (kp *kmsKeyHandler) isUsingDeprecatedFeatures() error {
	if !kp.withContext {
		return errDeprecatedCipherDataGenerator
	}
	return nil
}
	"encoding/json"
	"io/ioutil"
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) {
		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
		}

	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)
	}
	if !bytes.Equal(key, plaintextKey) {
		t.Errorf("expected %v, but received %v", key, plaintextKey)
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")
}
type mockGenerator struct{}
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
}
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"github.com/aws/aws-sdk-go/awstesting/unit"

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)
		}
	}
}
[
  {
    "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"
  }
]