| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- package s3crypto
- import (
- "encoding/hex"
- "io"
- "github.com/aws/aws-sdk-go/aws/client"
- "github.com/aws/aws-sdk-go/aws/request"
- "github.com/aws/aws-sdk-go/service/s3"
- "github.com/aws/aws-sdk-go/service/s3/s3iface"
- )
- // DefaultMinFileSize is used to check whether we want to write to a temp file
- // or store the data in memory.
- const DefaultMinFileSize = 1024 * 512 * 5
- // EncryptionClient is an S3 crypto client. By default the SDK will use Authentication mode which
- // will use KMS for key wrapping and AES GCM for content encryption.
- // AES GCM will load all data into memory. However, the rest of the content algorithms
- // do not load the entire contents into memory.
- type EncryptionClient struct {
- S3Client s3iface.S3API
- ContentCipherBuilder ContentCipherBuilder
- // SaveStrategy will dictate where the envelope is saved.
- //
- // Defaults to the object's metadata
- SaveStrategy SaveStrategy
- // TempFolderPath is used to store temp files when calling PutObject.
- // Temporary files are needed to compute the X-Amz-Content-Sha256 header.
- TempFolderPath string
- // MinFileSize is the minimum size for the content to write to a
- // temporary file instead of using memory.
- MinFileSize int64
- }
- // NewEncryptionClient instantiates a new S3 crypto client
- //
- // Example:
- // cmkID := "arn:aws:kms:region:000000000000:key/00000000-0000-0000-0000-000000000000"
- // sess := session.New()
- // handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
- // svc := s3crypto.New(sess, s3crypto.AESGCMContentCipherBuilder(handler))
- func NewEncryptionClient(prov client.ConfigProvider, builder ContentCipherBuilder, options ...func(*EncryptionClient)) *EncryptionClient {
- client := &EncryptionClient{
- S3Client: s3.New(prov),
- ContentCipherBuilder: builder,
- SaveStrategy: HeaderV2SaveStrategy{},
- MinFileSize: DefaultMinFileSize,
- }
- for _, option := range options {
- option(client)
- }
- return client
- }
- // PutObjectRequest creates a temp file to encrypt the contents into. It then streams
- // that data to S3.
- //
- // Example:
- // svc := s3crypto.New(session.New(), s3crypto.AESGCMContentCipherBuilder(handler))
- // req, out := svc.PutObjectRequest(&s3.PutObjectInput {
- // Key: aws.String("testKey"),
- // Bucket: aws.String("testBucket"),
- // Body: bytes.NewBuffer("test data"),
- // })
- // err := req.Send()
- func (c *EncryptionClient) PutObjectRequest(input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
- req, out := c.S3Client.PutObjectRequest(input)
- // Get Size of file
- n, err := input.Body.Seek(0, 2)
- if err != nil {
- req.Error = err
- return req, out
- }
- input.Body.Seek(0, 0)
- dst, err := getWriterStore(req, c.TempFolderPath, n >= c.MinFileSize)
- if err != nil {
- req.Error = err
- return req, out
- }
- encryptor, err := c.ContentCipherBuilder.ContentCipher()
- req.Handlers.Build.PushFront(func(r *request.Request) {
- if err != nil {
- r.Error = err
- return
- }
- md5 := newMD5Reader(input.Body)
- sha := newSHA256Writer(dst)
- reader, err := encryptor.EncryptContents(md5)
- if err != nil {
- r.Error = err
- return
- }
- _, err = io.Copy(sha, reader)
- if err != nil {
- r.Error = err
- return
- }
- data := encryptor.GetCipherData()
- env, err := encodeMeta(md5, data)
- if err != nil {
- r.Error = err
- return
- }
- shaHex := hex.EncodeToString(sha.GetValue())
- req.HTTPRequest.Header.Set("X-Amz-Content-Sha256", shaHex)
- dst.Seek(0, 0)
- input.Body = dst
- err = c.SaveStrategy.Save(env, r)
- r.Error = err
- })
- return req, out
- }
- // PutObject is a wrapper for PutObjectRequest
- func (c *EncryptionClient) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
- req, out := c.PutObjectRequest(input)
- return out, req.Send()
- }
|