/
strategy.go
160 lines (134 loc) · 5.4 KB
/
strategy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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)
}