1package s3crypto
2
3import (
4	"encoding/base64"
5	"encoding/hex"
6	"io"
7	"strings"
8
9	"github.com/aws/aws-sdk-go/aws"
10	"github.com/aws/aws-sdk-go/aws/awserr"
11	"github.com/aws/aws-sdk-go/aws/request"
12	"github.com/aws/aws-sdk-go/internal/sdkio"
13	"github.com/aws/aws-sdk-go/service/s3"
14)
15
16func putObjectRequest(c EncryptionClientOptions, input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
17	req, out := c.S3Client.PutObjectRequest(input)
18
19	// Get Size of file
20	n, err := aws.SeekerLen(input.Body)
21	if err != nil {
22		req.Error = err
23		return req, out
24	}
25
26	dst, err := getWriterStore(req, c.TempFolderPath, n >= c.MinFileSize)
27	if err != nil {
28		req.Error = err
29		return req, out
30	}
31
32	req.Handlers.Build.PushFront(func(r *request.Request) {
33		if err != nil {
34			r.Error = err
35			return
36		}
37		var encryptor ContentCipher
38		if v, ok := c.ContentCipherBuilder.(ContentCipherBuilderWithContext); ok {
39			encryptor, err = v.ContentCipherWithContext(r.Context())
40		} else {
41			encryptor, err = c.ContentCipherBuilder.ContentCipher()
42		}
43		if err != nil {
44			r.Error = err
45			return
46		}
47
48		md5 := newMD5Reader(input.Body)
49		sha := newSHA256Writer(dst)
50		reader, err := encryptor.EncryptContents(md5)
51		if err != nil {
52			r.Error = err
53			return
54		}
55
56		_, err = io.Copy(sha, reader)
57		if err != nil {
58			r.Error = err
59			return
60		}
61
62		data := encryptor.GetCipherData()
63		env, err := encodeMeta(md5, data)
64		if err != nil {
65			r.Error = err
66			return
67		}
68
69		shaHex := hex.EncodeToString(sha.GetValue())
70		req.HTTPRequest.Header.Set("X-Amz-Content-Sha256", shaHex)
71
72		dst.Seek(0, sdkio.SeekStart)
73		input.Body = dst
74
75		err = c.SaveStrategy.Save(env, r)
76		r.Error = err
77	})
78
79	return req, out
80}
81
82func putObject(options EncryptionClientOptions, input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
83	req, out := putObjectRequest(options, input)
84	return out, req.Send()
85}
86
87func putObjectWithContext(options EncryptionClientOptions, ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) {
88	req, out := putObjectRequest(options, input)
89	req.SetContext(ctx)
90	req.ApplyOptions(opts...)
91	return out, req.Send()
92}
93
94func getObjectRequest(options DecryptionClientOptions, input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
95	req, out := options.S3Client.GetObjectRequest(input)
96	req.Handlers.Unmarshal.PushBack(func(r *request.Request) {
97		env, err := options.LoadStrategy.Load(r)
98		if err != nil {
99			r.Error = err
100			out.Body.Close()
101			return
102		}
103
104		// If KMS should return the correct CEK algorithm with the proper
105		// KMS key provider
106		cipher, err := contentCipherFromEnvelope(options, r.Context(), env)
107		if err != nil {
108			r.Error = err
109			out.Body.Close()
110			return
111		}
112
113		reader, err := cipher.DecryptContents(out.Body)
114		if err != nil {
115			r.Error = err
116			out.Body.Close()
117			return
118		}
119		out.Body = reader
120	})
121	return req, out
122}
123
124func getObject(options DecryptionClientOptions, input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
125	req, out := getObjectRequest(options, input)
126	return out, req.Send()
127}
128
129func getObjectWithContext(options DecryptionClientOptions, ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) {
130	req, out := getObjectRequest(options, input)
131	req.SetContext(ctx)
132	req.ApplyOptions(opts...)
133	return out, req.Send()
134}
135
136func contentCipherFromEnvelope(options DecryptionClientOptions, ctx aws.Context, env Envelope) (ContentCipher, error) {
137	wrap, err := wrapFromEnvelope(options, env)
138	if err != nil {
139		return nil, err
140	}
141
142	return cekFromEnvelope(options, ctx, env, wrap)
143}
144
145func wrapFromEnvelope(options DecryptionClientOptions, env Envelope) (CipherDataDecrypter, error) {
146	f, ok := options.WrapRegistry[env.WrapAlg]
147	if !ok || f == nil {
148		return nil, awserr.New(
149			"InvalidWrapAlgorithmError",
150			"wrap algorithm isn't supported, "+env.WrapAlg,
151			nil,
152		)
153	}
154	return f(env)
155}
156
157func cekFromEnvelope(options DecryptionClientOptions, ctx aws.Context, env Envelope, decrypter CipherDataDecrypter) (ContentCipher, error) {
158	f, ok := options.CEKRegistry[env.CEKAlg]
159	if !ok || f == nil {
160		return nil, awserr.New(
161			"InvalidCEKAlgorithmError",
162			"cek algorithm isn't supported, "+env.CEKAlg,
163			nil,
164		)
165	}
166
167	key, err := base64.StdEncoding.DecodeString(env.CipherKey)
168	if err != nil {
169		return nil, err
170	}
171
172	iv, err := base64.StdEncoding.DecodeString(env.IV)
173	if err != nil {
174		return nil, err
175	}
176
177	if d, ok := decrypter.(CipherDataDecrypterWithContext); ok {
178		key, err = d.DecryptKeyWithContext(ctx, key)
179	} else {
180		key, err = decrypter.DecryptKey(key)
181	}
182
183	if err != nil {
184		return nil, err
185	}
186
187	cd := CipherData{
188		Key:          key,
189		IV:           iv,
190		CEKAlgorithm: env.CEKAlg,
191		Padder:       getPadder(options, env.CEKAlg),
192	}
193	return f(cd)
194}
195
196// getPadder will return an unpadder with checking the cek algorithm specific padder.
197// If there wasn't a cek algorithm specific padder, we check the padder itself.
198// We return a no unpadder, if no unpadder was found. This means any customization
199// either contained padding within the cipher implementation, and to maintain
200// backwards compatility we will simply not unpad anything.
201func getPadder(options DecryptionClientOptions, cekAlg string) Padder {
202	padder, ok := options.PadderRegistry[cekAlg]
203	if !ok {
204		padder, ok = options.PadderRegistry[cekAlg[strings.LastIndex(cekAlg, "/")+1:]]
205		if !ok {
206			return NoPadder
207		}
208	}
209	return padder
210}
211