Skip to content

Commit

Permalink
Merge commit '12ff57a16373dda5a0c22eafdf0fa1c4c224f7c4' into release
Browse files Browse the repository at this point in the history
* commit '12ff57a16373dda5a0c22eafdf0fa1c4c224f7c4':
  Updates to the Amazon S3 Encryption Client - This change includes fixes for issues that were reported by Sophie Schmieg from the Google ISE team, and for issues that were discovered by AWS Cryptography.
  • Loading branch information
awssdkgo committed Aug 7, 2020
2 parents b811ea8 + 12ff57a commit 1e84382
Show file tree
Hide file tree
Showing 41 changed files with 2,483 additions and 537 deletions.
1 change: 1 addition & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### SDK Features
* `service/s3/s3crypto`: Updates to the Amazon S3 Encryption Client - This change includes fixes for issues that were reported by Sophie Schmieg from the Google ISE team, and for issues that were discovered by AWS Cryptography.

### SDK Enhancements

Expand Down
2 changes: 1 addition & 1 deletion aws/csm/reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func TestReportingMetrics(t *testing.T) {
{
"Type": "ApiCallAttempt",
"SdkException": request.ErrCodeRequestError,
"SdkExceptionMessage": request.ErrCodeRequestError+": sdk error",
"SdkExceptionMessage": request.ErrCodeRequestError + ": sdk error",
"HttpStatusCode": float64(500),
},
{
Expand Down
2 changes: 1 addition & 1 deletion aws/ec2metadata/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const (
enableTokenProviderHandlerName = "enableTokenProviderHandler"

// TTL constants
defaultTTL = 21600 * time.Second
defaultTTL = 21600 * time.Second
ttlExpirationWindow = 30 * time.Second
)

Expand Down
13 changes: 0 additions & 13 deletions service/s3/doc_custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,6 @@
// content from S3. The Encryption and Decryption clients can be used concurrently
// once the client is created.
//
// sess := session.Must(session.NewSession())
//
// // Create the decryption client.
// svc := s3crypto.NewDecryptionClient(sess)
//
// // The object will be downloaded from S3 and decrypted locally. By metadata
// // about the object's encryption will instruct the decryption client how
// // decrypt the content of the object. By default KMS is used for keys.
// result, err := svc.GetObject(&s3.GetObjectInput {
// Bucket: aws.String(myBucket),
// Key: aws.String(myKey),
// })
//
// See the s3crypto package documentation for more information.
// https://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3crypto/
//
Expand Down
58 changes: 48 additions & 10 deletions service/s3/s3crypto/aes_cbc_content_cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package s3crypto

import (
"io"
"strings"
)

const (
Expand All @@ -15,18 +14,38 @@ 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
// AESCBCContentCipherBuilder returns a new encryption only AES/CBC mode structure using the provided padder. The provided cipher data generator
// will be used to provide keys for content encryption.
//
// deprecated: This content cipher builder has been deprecated. Users should migrate to AESGCMContentCipherBuilder
// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
func AESCBCContentCipherBuilder(generator CipherDataGenerator, padder Padder) ContentCipherBuilder {
return cbcContentCipherBuilder{generator: generator, padder: padder}
}

// RegisterAESCBCContentCipher registers the AES/CBC cipher and padder with the provided CryptoRegistry.
//
// Example:
// cr := s3crypto.NewCryptoRegistry()
// if err := s3crypto.RegisterAESCBCContentCipher(cr, s3crypto.AESCBCPadder); err != nil {
// panic(err) // handle error
// }
//
// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
func RegisterAESCBCContentCipher(registry *CryptoRegistry, padder Padder) error {
if registry == nil {
return errNilCryptoRegistry
}
name := AESCBC + "/" + padder.Name()
err := registry.AddCEK(name, newAESCBCContentCipher)
if err != nil {
return err
}
if err := registry.AddPadder(name, padder); err != nil {
return err
}
return nil
}

func (builder cbcContentCipherBuilder) ContentCipher() (ContentCipher, error) {
cd, err := builder.generator.GenerateCipherData(cbcKeySize, cbcNonceSize)
if err != nil {
Expand All @@ -37,11 +56,22 @@ func (builder cbcContentCipherBuilder) ContentCipher() (ContentCipher, error) {
return newAESCBCContentCipher(cd)
}

func (builder cbcContentCipherBuilder) isAWSFixture() bool {
return true
}

func (cbcContentCipherBuilder) isEncryptionVersionCompatible(version clientVersion) error {
if version != v1ClientVersion {
return errDeprecatedIncompatibleCipherBuilder
}
return nil
}

// newAESCBCContentCipher will create a new aes cbc content cipher. If the cipher data's
// will set the CEK algorithm if it hasn't been set.
// will set the cek algorithm if it hasn't been set.
func newAESCBCContentCipher(cd CipherData) (ContentCipher, error) {
if len(cd.CEKAlgorithm) == 0 {
cd.CEKAlgorithm = strings.Join([]string{AESCBC, cd.Padder.Name()}, "/")
cd.CEKAlgorithm = AESCBC + "/" + cd.Padder.Name()
}
cipher, err := newAESCBC(cd, cd.Padder)
if err != nil {
Expand Down Expand Up @@ -77,3 +107,11 @@ func (cc *aesCBCContentCipher) DecryptContents(src io.ReadCloser) (io.ReadCloser
func (cc aesCBCContentCipher) GetCipherData() CipherData {
return cc.CipherData
}

var (
_ ContentCipherBuilder = (*cbcContentCipherBuilder)(nil)
_ compatibleEncryptionFixture = (*cbcContentCipherBuilder)(nil)
_ awsFixture = (*cbcContentCipherBuilder)(nil)

_ ContentCipher = (*aesCBCContentCipher)(nil)
)
68 changes: 64 additions & 4 deletions service/s3/s3crypto/aes_cbc_content_cipher_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package s3crypto_test
package s3crypto

import (
"strings"
"testing"

"github.com/aws/aws-sdk-go/service/s3/s3crypto"
)

func TestAESCBCBuilder(t *testing.T) {
generator := mockGenerator{}
builder := s3crypto.AESCBCContentCipherBuilder(generator, s3crypto.NoPadder)
builder := AESCBCContentCipherBuilder(generator, NoPadder)
if builder == nil {
t.Fatal(builder)
}
Expand All @@ -18,3 +17,64 @@ func TestAESCBCBuilder(t *testing.T) {
t.Fatal(err)
}
}

func TestAesCBCContentCipher_isFixtureEncryptionCompatible(t *testing.T) {
generator := mockGenerator{}
builder := AESCBCContentCipherBuilder(generator, NoPadder)
if builder == nil {
t.Fatal("expected builder to not be nil")
}

compatibility, ok := builder.(compatibleEncryptionFixture)
if !ok {
t.Fatal("expected builder to implement compatibleEncryptionFixture interface")
}

if err := compatibility.isEncryptionVersionCompatible(v1ClientVersion); err != nil {
t.Errorf("expected builder to be compatible with v1 client")
}

if err := compatibility.isEncryptionVersionCompatible(v2ClientVersion); err == nil {
t.Errorf("expected builder to not be compatible with v2 client")
}
}

func TestRegisterAESCBCContentCipher(t *testing.T) {
cr := NewCryptoRegistry()
padder := AESCBCPadder
err := RegisterAESCBCContentCipher(cr, padder)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}

if v, ok := cr.GetCEK("AES/CBC/PKCS5Padding"); !ok {
t.Fatal("expected cek algorithm handler to registered")
} else if v == nil {
t.Fatal("expected non-nil cek handler to be registered")
}

if v, ok := cr.GetPadder("AES/CBC/PKCS5Padding"); !ok {
t.Fatal("expected padder to be registered")
} else if v != padder {
t.Fatal("padder did not match provided value")
}

// try to register padder again
err = RegisterAESCBCContentCipher(cr, padder)
if err == nil {
t.Fatal("expected error, got none")
} else if !strings.Contains(err.Error(), "duplicate cek registry entry") {
t.Errorf("expected duplicate cek entry, got %v", err)
}

// try to regster padder with cek removed but padder entry still present
if _, ok := cr.RemoveCEK("AES/CBC/PKCS5Padding"); !ok {
t.Fatalf("expected value to be removed")
}
err = RegisterAESCBCContentCipher(cr, padder)
if err == nil {
t.Fatal("expected error, got none")
} else if !strings.Contains(err.Error(), "duplicate padder registry entry") {
t.Errorf("expected duplicate padder entry, got %v", err)
}
}
132 changes: 119 additions & 13 deletions service/s3/s3crypto/aes_gcm_content_cipher.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package s3crypto

import (
"fmt"
"io"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -11,21 +12,62 @@ const (
gcmNonceSize = 12
)

type gcmContentCipherBuilder struct {
generator CipherDataGenerator
// AESGCMContentCipherBuilder returns a new encryption only AES/GCM mode structure with a specific cipher data generator
// that will provide keys to be used for content encryption.
//
// Note: This uses the Go stdlib AEAD implementation for AES/GCM. Due to this objects to be encrypted or decrypted
// will be fully loaded into memory before encryption or decryption can occur. Caution must be taken to avoid memory
// allocation failures.
//
// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
func AESGCMContentCipherBuilder(generator CipherDataGenerator) ContentCipherBuilder {
return gcmContentCipherBuilder{generator}
}

// AESGCMContentCipherBuilderV2 returns a new encryption only AES/GCM mode structure with a specific cipher data generator
// that will provide keys to be used for content encryption. This type is compatible with the V2 encryption client.
//
// Note: This uses the Go stdlib AEAD implementation for AES/GCM. Due to this objects to be encrypted or decrypted
// will be fully loaded into memory before encryption or decryption can occur. Caution must be taken to avoid memory
// allocation failures.
func AESGCMContentCipherBuilderV2(generator CipherDataGeneratorWithCEKAlg) ContentCipherBuilder {
return gcmContentCipherBuilderV2{generator}
}

func (builder gcmContentCipherBuilder) isUsingDeprecatedFeatures() error {
if feature, ok := builder.generator.(deprecatedFeatures); ok {
return feature.isUsingDeprecatedFeatures()
// RegisterAESGCMContentCipher registers the AES/GCM content cipher algorithm with the provided CryptoRegistry.
//
// Example:
// cr := s3crypto.NewCryptoRegistry()
// if err := s3crypto.RegisterAESGCMContentCipher(cr); err != nil {
// panic(err) // handle error
// }
//
func RegisterAESGCMContentCipher(registry *CryptoRegistry) error {
if registry == nil {
return errNilCryptoRegistry
}

err := registry.AddCEK(AESGCMNoPadding, newAESGCMContentCipher)
if err != nil {
return err
}

// NoPadder is generic but required by this algorithm, so if it is already registered and is the expected implementation
// don't error.
padderName := NoPadder.Name()
if v, ok := registry.GetPadder(padderName); !ok {
if err := registry.AddPadder(padderName, NoPadder); err != nil {
return err
}
} else if _, ok := v.(noPadder); !ok {
return fmt.Errorf("%s is already registred but does not match expected type %T", padderName, NoPadder)
}
return nil
}

// AESGCMContentCipherBuilder returns a new encryption only mode structure with a specific cipher
// for the master key
func AESGCMContentCipherBuilder(generator CipherDataGenerator) ContentCipherBuilder {
return gcmContentCipherBuilder{generator}
// gcmContentCipherBuilder is a AES/GCM content cipher to be used with the V1 client CipherDataGenerator interface
type gcmContentCipherBuilder struct {
generator CipherDataGenerator
}

func (builder gcmContentCipherBuilder) ContentCipher() (ContentCipher, error) {
Expand All @@ -37,10 +79,6 @@ func (builder gcmContentCipherBuilder) ContentCipherWithContext(ctx aws.Context)
var err error

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)
default:
Expand All @@ -53,6 +91,52 @@ func (builder gcmContentCipherBuilder) ContentCipherWithContext(ctx aws.Context)
return newAESGCMContentCipher(cd)
}

// isFixtureEncryptionCompatible will ensure that this type may only be used with the V1 client
func (builder gcmContentCipherBuilder) isEncryptionVersionCompatible(version clientVersion) error {
if version != v1ClientVersion {
return errDeprecatedIncompatibleCipherBuilder
}
return nil
}

func (builder gcmContentCipherBuilder) isAWSFixture() bool {
return true
}

// gcmContentCipherBuilderV2 return a new builder for encryption content using AES/GCM/NoPadding. This type is meant
// to be used with key wrapping implementations that allow the cek algorithm to be provided when calling the
// cipher data generator.
type gcmContentCipherBuilderV2 struct {
generator CipherDataGeneratorWithCEKAlg
}

func (builder gcmContentCipherBuilderV2) ContentCipher() (ContentCipher, error) {
return builder.ContentCipherWithContext(aws.BackgroundContext())
}

func (builder gcmContentCipherBuilderV2) ContentCipherWithContext(ctx aws.Context) (ContentCipher, error) {
cd, err := builder.generator.GenerateCipherDataWithCEKAlg(ctx, gcmKeySize, gcmNonceSize, AESGCMNoPadding)
if err != nil {
return nil, err
}

return newAESGCMContentCipher(cd)
}

// isFixtureEncryptionCompatible will ensure that this type may only be used with the V2 client
func (builder gcmContentCipherBuilderV2) isEncryptionVersionCompatible(version clientVersion) error {
if version != v2ClientVersion {
return errDeprecatedIncompatibleCipherBuilder
}
return nil
}

// isAWSFixture will return whether this type was constructed with an AWS provided CipherDataGenerator
func (builder gcmContentCipherBuilderV2) isAWSFixture() bool {
v, ok := builder.generator.(awsFixture)
return ok && v.isAWSFixture()
}

func newAESGCMContentCipher(cd CipherData) (ContentCipher, error) {
cd.CEKAlgorithm = AESGCMNoPadding
cd.TagLength = "128"
Expand Down Expand Up @@ -91,3 +175,25 @@ func (cc *aesGCMContentCipher) DecryptContents(src io.ReadCloser) (io.ReadCloser
func (cc aesGCMContentCipher) GetCipherData() CipherData {
return cc.CipherData
}

// assert ContentCipherBuilder implementations
var (
_ ContentCipherBuilder = (*gcmContentCipherBuilder)(nil)
_ ContentCipherBuilder = (*gcmContentCipherBuilderV2)(nil)
)

// assert ContentCipherBuilderWithContext implementations
var (
_ ContentCipherBuilderWithContext = (*gcmContentCipherBuilder)(nil)
_ ContentCipherBuilderWithContext = (*gcmContentCipherBuilderV2)(nil)
)

// assert ContentCipher implementations
var (
_ ContentCipher = (*aesGCMContentCipher)(nil)
)

// assert awsFixture implementations
var (
_ awsFixture = (*gcmContentCipherBuilderV2)(nil)
)
Loading

0 comments on commit 1e84382

Please sign in to comment.