1package s3crypto
2
3import (
4	"github.com/aws/aws-sdk-go/aws"
5	"github.com/aws/aws-sdk-go/aws/client"
6	"github.com/aws/aws-sdk-go/aws/request"
7	"github.com/aws/aws-sdk-go/service/s3"
8	"github.com/aws/aws-sdk-go/service/s3/s3iface"
9)
10
11const customTypeWarningMessage = "WARNING: The S3 Encryption Client is configured to write encrypted objects using types not provided by AWS. Security and compatibility with these types can not be guaranteed."
12
13// EncryptionClientV2 is an S3 crypto client. By default the SDK will use Authentication mode which
14// will use KMS for key wrapping and AES GCM for content encryption.
15// AES GCM will load all data into memory. However, the rest of the content algorithms
16// do not load the entire contents into memory.
17type EncryptionClientV2 struct {
18	options EncryptionClientOptions
19}
20
21// EncryptionClientOptions is the configuration options for EncryptionClientV2
22type EncryptionClientOptions struct {
23	S3Client             s3iface.S3API
24	ContentCipherBuilder ContentCipherBuilder
25	// SaveStrategy will dictate where the envelope is saved.
26	//
27	// Defaults to the object's metadata
28	SaveStrategy SaveStrategy
29	// TempFolderPath is used to store temp files when calling PutObject.
30	// Temporary files are needed to compute the X-Amz-Content-Sha256 header.
31	TempFolderPath string
32	// MinFileSize is the minimum size for the content to write to a
33	// temporary file instead of using memory.
34	MinFileSize int64
35}
36
37// NewEncryptionClientV2 instantiates a new S3 crypto client. An error will be returned to the caller if the provided
38// contentCipherBuilder has been deprecated or was constructed with a deprecated component.
39//
40// Example:
41//	cmkID := "arn:aws:kms:region:000000000000:key/00000000-0000-0000-0000-000000000000"
42//  sess := session.Must(session.NewSession())
43//	var matdesc s3crypto.MaterialDescription
44//	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID, matdesc)
45//	svc := s3crypto.NewEncryptionClientV2(sess, s3crypto.AESGCMContentCipherBuilderV2(handler))
46func NewEncryptionClientV2(prov client.ConfigProvider, contentCipherBuilder ContentCipherBuilder, options ...func(clientOptions *EncryptionClientOptions),
47) (
48	client *EncryptionClientV2, err error,
49) {
50	s3client := s3.New(prov)
51	s3client.Handlers.Build.PushBack(func(r *request.Request) {
52		request.AddToUserAgent(r, "S3CryptoV2")
53	})
54
55	clientOptions := &EncryptionClientOptions{
56		S3Client:             s3client,
57		ContentCipherBuilder: contentCipherBuilder,
58		SaveStrategy:         HeaderV2SaveStrategy{},
59		MinFileSize:          DefaultMinFileSize,
60	}
61	for _, option := range options {
62		option(clientOptions)
63	}
64
65	// Check that the configured client uses a compatible ContentCipherBuilder.
66	// User provided types will not implement this method
67	if fixture, ok := contentCipherBuilder.(compatibleEncryptionFixture); ok {
68		if err := fixture.isEncryptionVersionCompatible(v2ClientVersion); err != nil {
69			return nil, err
70		}
71	}
72
73	// Check if the passed in type is an fixture, if not log a warning message to the user
74	if fixture, ok := contentCipherBuilder.(awsFixture); !ok || !fixture.isAWSFixture() {
75		if s3client.Config.Logger != nil {
76			s3client.Config.Logger.Log(customTypeWarningMessage)
77		}
78	}
79
80	client = &EncryptionClientV2{
81		*clientOptions,
82	}
83
84	return client, err
85}
86
87// PutObjectRequest creates a temp file to encrypt the contents into. It then streams
88// that data to S3.
89//
90// Example:
91//	req, out := svc.PutObjectRequest(&s3.PutObjectInput {
92//	  Key: aws.String("testKey"),
93//	  Bucket: aws.String("testBucket"),
94//	  Body: strings.NewReader("test data"),
95//	})
96//	err := req.Send()
97func (c *EncryptionClientV2) PutObjectRequest(input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
98	return putObjectRequest(c.options, input)
99}
100
101// PutObject is a wrapper for PutObjectRequest
102func (c *EncryptionClientV2) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
103	return putObject(c.options, input)
104}
105
106// PutObjectWithContext is a wrapper for PutObjectRequest with the additional
107// context, and request options support.
108//
109// PutObjectWithContext is the same as PutObject with the additional support for
110// Context input parameters. The Context must not be nil. A nil Context will
111// cause a panic. Use the Context to add deadlining, timeouts, etc. In the future
112// this may create sub-contexts for individual underlying requests.
113func (c *EncryptionClientV2) PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) {
114	return putObjectWithContext(c.options, ctx, input, opts...)
115}
116