v3/internal/strategy.go (112 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package internal import ( "context" "encoding/json" "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/smithy-go" "io" "net/http" "strings" "github.com/aws/aws-sdk-go-v2/service/s3" ) // GetObjectAPIClient is a client that implements the GetObject operation type GetObjectAPIClient interface { GetObject(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) (*s3.GetObjectOutput, error) } // SaveStrategyRequest represents a request sent to a SaveStrategy to save the contents of an ObjectMetadata type SaveStrategyRequest struct { // The envelope to save Envelope *ObjectMetadata // The HTTP request being built HTTPRequest *http.Request // The operation Input type Input interface{} } // ObjectMetadataSaveStrategy will save the metadata of the crypto contents to the header of // the object. type ObjectMetadataSaveStrategy struct{} // Save will save the envelope to the request's header. func (strat ObjectMetadataSaveStrategy) Save(ctx context.Context, saveReq *SaveStrategyRequest) error { input := saveReq.Input.(*s3.PutObjectInput) if input.Metadata == nil { input.Metadata = map[string]string{} } env := saveReq.Envelope input.Metadata[http.CanonicalHeaderKey(keyV2Header)] = env.CipherKey input.Metadata[http.CanonicalHeaderKey(ivHeader)] = env.IV input.Metadata[http.CanonicalHeaderKey(matDescHeader)] = env.MatDesc input.Metadata[http.CanonicalHeaderKey(KeyringAlgorithmHeader)] = env.KeyringAlg input.Metadata[http.CanonicalHeaderKey(CekAlgorithmHeader)] = env.CEKAlg input.Metadata[http.CanonicalHeaderKey(unencryptedContentLengthHeader)] = env.UnencryptedContentLen if len(env.TagLen) > 0 { input.Metadata[http.CanonicalHeaderKey(tagLengthHeader)] = env.TagLen } return nil } // LoadStrategyRequest represents a request sent to a LoadStrategy to load the contents of an ObjectMetadata type LoadStrategyRequest struct { // The HTTP response HTTPResponse *http.Response // The operation Input type Input interface{} } // LoadStrategy ... type LoadStrategy interface { Load(context.Context, *LoadStrategyRequest) (ObjectMetadata, error) } // S3LoadStrategy will load the instruction file from s3 type s3LoadStrategy struct { APIClient GetObjectAPIClient InstructionFileSuffix string } // Load from a given instruction file suffix func (load s3LoadStrategy) Load(ctx context.Context, req *LoadStrategyRequest) (ObjectMetadata, error) { env := ObjectMetadata{} if load.InstructionFileSuffix == "" { load.InstructionFileSuffix = DefaultInstructionKeySuffix } input := req.Input.(*s3.GetObjectInput) out, err := load.APIClient.GetObject(ctx, &s3.GetObjectInput{ Key: aws.String(strings.Join([]string{*input.Key, load.InstructionFileSuffix}, "")), Bucket: input.Bucket, }) if err != nil { return env, err } b, err := io.ReadAll(out.Body) if err != nil { return env, err } err = json.Unmarshal(b, &env) return env, err } // headerV2LoadStrategy will load the envelope from the metadata type headerV2LoadStrategy struct{} // Load from a given object's header func (load headerV2LoadStrategy) Load(ctx context.Context, req *LoadStrategyRequest) (ObjectMetadata, error) { env := ObjectMetadata{} env.CipherKey = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, keyV2Header}, "-")) env.IV = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, ivHeader}, "-")) env.MatDesc = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, matDescHeader}, "-")) env.KeyringAlg = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, KeyringAlgorithmHeader}, "-")) env.CEKAlg = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, CekAlgorithmHeader}, "-")) env.TagLen = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, tagLengthHeader}, "-")) env.UnencryptedContentLen = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, unencryptedContentLengthHeader}, "-")) return env, nil } // DefaultLoadStrategy This is the only exported LoadStrategy since cx are no longer able to configure their client // with a specific load strategy. Instead, we figure out which strategy to use based on the response header on decrypt. type DefaultLoadStrategy struct { client GetObjectAPIClient suffix string } func (load DefaultLoadStrategy) Load(ctx context.Context, req *LoadStrategyRequest) (ObjectMetadata, error) { if value := req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, keyV2Header}, "-")); value != "" { strat := headerV2LoadStrategy{} return strat.Load(ctx, req) } else if value = req.HTTPResponse.Header.Get(strings.Join([]string{metaHeader, keyV1Header}, "-")); value != "" { // In other S3EC implementations, decryption of v1 objects is supported. // Go, however, does not support this. return ObjectMetadata{}, &smithy.GenericAPIError{ Code: "V1NotSupportedError", Message: "The AWS SDK for Go does not support version 1", } } var client GetObjectAPIClient if load.client == nil { cfg, err := config.LoadDefaultConfig(context.Background()) if err != nil { return ObjectMetadata{}, fmt.Errorf("unable to create S3 client to load instruction file: ") } client = s3.NewFromConfig(cfg) } else { client = load.client } strat := s3LoadStrategy{ APIClient: client, InstructionFileSuffix: load.suffix, } return strat.Load(ctx, req) }