1package s3crypto
2
3import (
4	"strings"
5
6	"github.com/aws/aws-sdk-go/aws"
7	"github.com/aws/aws-sdk-go/aws/client"
8	"github.com/aws/aws-sdk-go/aws/request"
9	"github.com/aws/aws-sdk-go/service/kms"
10	"github.com/aws/aws-sdk-go/service/s3"
11	"github.com/aws/aws-sdk-go/service/s3/s3iface"
12)
13
14// WrapEntry is builder that return a proper key decrypter and error
15type WrapEntry func(Envelope) (CipherDataDecrypter, error)
16
17// CEKEntry is a builder thatn returns a proper content decrypter and error
18type CEKEntry func(CipherData) (ContentCipher, error)
19
20// DecryptionClient is an S3 crypto client. The decryption client
21// will handle all get object requests from Amazon S3.
22// Supported key wrapping algorithms:
23//	*AWS KMS
24//
25// Supported content ciphers:
26//	* AES/GCM
27//	* AES/CBC
28type DecryptionClient struct {
29	S3Client s3iface.S3API
30	// LoadStrategy is used to load the metadata either from the metadata of the object
31	// or from a separate file in s3.
32	//
33	// Defaults to our default load strategy.
34	LoadStrategy LoadStrategy
35
36	WrapRegistry   map[string]WrapEntry
37	CEKRegistry    map[string]CEKEntry
38	PadderRegistry map[string]Padder
39}
40
41// NewDecryptionClient instantiates a new S3 crypto client
42//
43// Example:
44//	sess := session.New()
45//	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient{
46//		// Custom client options here
47//	}))
48func NewDecryptionClient(prov client.ConfigProvider, options ...func(*DecryptionClient)) *DecryptionClient {
49	s3client := s3.New(prov)
50	client := &DecryptionClient{
51		S3Client: s3client,
52		LoadStrategy: defaultV2LoadStrategy{
53			client: s3client,
54		},
55		WrapRegistry: map[string]WrapEntry{
56			KMSWrap: (kmsKeyHandler{
57				kms: kms.New(prov),
58			}).decryptHandler,
59		},
60		CEKRegistry: map[string]CEKEntry{
61			AESGCMNoPadding:                                          newAESGCMContentCipher,
62			strings.Join([]string{AESCBC, AESCBCPadder.Name()}, "/"): newAESCBCContentCipher,
63		},
64		PadderRegistry: map[string]Padder{
65			strings.Join([]string{AESCBC, AESCBCPadder.Name()}, "/"): AESCBCPadder,
66			"NoPadding": NoPadder,
67		},
68	}
69	for _, option := range options {
70		option(client)
71	}
72
73	return client
74}
75
76// GetObjectRequest will make a request to s3 and retrieve the object. In this process
77// decryption will be done. The SDK only supports V2 reads of KMS and GCM.
78//
79// Example:
80//	sess := session.New()
81//	svc := s3crypto.NewDecryptionClient(sess)
82//	req, out := svc.GetObjectRequest(&s3.GetObjectInput {
83//	  Key: aws.String("testKey"),
84//	  Bucket: aws.String("testBucket"),
85//	})
86//	err := req.Send()
87func (c *DecryptionClient) GetObjectRequest(input *s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) {
88	req, out := c.S3Client.GetObjectRequest(input)
89	req.Handlers.Unmarshal.PushBack(func(r *request.Request) {
90		env, err := c.LoadStrategy.Load(r)
91		if err != nil {
92			r.Error = err
93			out.Body.Close()
94			return
95		}
96
97		// If KMS should return the correct CEK algorithm with the proper
98		// KMS key provider
99		cipher, err := c.contentCipherFromEnvelope(env)
100		if err != nil {
101			r.Error = err
102			out.Body.Close()
103			return
104		}
105
106		reader, err := cipher.DecryptContents(out.Body)
107		if err != nil {
108			r.Error = err
109			out.Body.Close()
110			return
111		}
112		out.Body = reader
113	})
114	return req, out
115}
116
117// GetObject is a wrapper for GetObjectRequest
118func (c *DecryptionClient) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
119	req, out := c.GetObjectRequest(input)
120	return out, req.Send()
121}
122
123// GetObjectWithContext is a wrapper for GetObjectRequest with the additional
124// context, and request options support.
125//
126// GetObjectWithContext is the same as GetObject with the additional support for
127// Context input parameters. The Context must not be nil. A nil Context will
128// cause a panic. Use the Context to add deadlining, timeouts, etc. In the future
129// this may create sub-contexts for individual underlying requests.
130func (c *DecryptionClient) GetObjectWithContext(ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) {
131	req, out := c.GetObjectRequest(input)
132	req.SetContext(ctx)
133	req.ApplyOptions(opts...)
134	return out, req.Send()
135}
136