encryption_client.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package s3crypto
  2. import (
  3. "encoding/hex"
  4. "io"
  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. // DefaultMinFileSize is used to check whether we want to write to a temp file
  11. // or store the data in memory.
  12. const DefaultMinFileSize = 1024 * 512 * 5
  13. // EncryptionClient 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.
  17. type EncryptionClient struct {
  18. S3Client s3iface.S3API
  19. ContentCipherBuilder ContentCipherBuilder
  20. // SaveStrategy will dictate where the envelope is saved.
  21. //
  22. // Defaults to the object's metadata
  23. SaveStrategy SaveStrategy
  24. // TempFolderPath is used to store temp files when calling PutObject.
  25. // Temporary files are needed to compute the X-Amz-Content-Sha256 header.
  26. TempFolderPath string
  27. // MinFileSize is the minimum size for the content to write to a
  28. // temporary file instead of using memory.
  29. MinFileSize int64
  30. }
  31. // NewEncryptionClient instantiates a new S3 crypto client
  32. //
  33. // Example:
  34. // cmkID := "arn:aws:kms:region:000000000000:key/00000000-0000-0000-0000-000000000000"
  35. // sess := session.New()
  36. // handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
  37. // svc := s3crypto.New(sess, s3crypto.AESGCMContentCipherBuilder(handler))
  38. func NewEncryptionClient(prov client.ConfigProvider, builder ContentCipherBuilder, options ...func(*EncryptionClient)) *EncryptionClient {
  39. client := &EncryptionClient{
  40. S3Client: s3.New(prov),
  41. ContentCipherBuilder: builder,
  42. SaveStrategy: HeaderV2SaveStrategy{},
  43. MinFileSize: DefaultMinFileSize,
  44. }
  45. for _, option := range options {
  46. option(client)
  47. }
  48. return client
  49. }
  50. // PutObjectRequest creates a temp file to encrypt the contents into. It then streams
  51. // that data to S3.
  52. //
  53. // Example:
  54. // svc := s3crypto.New(session.New(), s3crypto.AESGCMContentCipherBuilder(handler))
  55. // req, out := svc.PutObjectRequest(&s3.PutObjectInput {
  56. // Key: aws.String("testKey"),
  57. // Bucket: aws.String("testBucket"),
  58. // Body: bytes.NewBuffer("test data"),
  59. // })
  60. // err := req.Send()
  61. func (c *EncryptionClient) PutObjectRequest(input *s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) {
  62. req, out := c.S3Client.PutObjectRequest(input)
  63. // Get Size of file
  64. n, err := input.Body.Seek(0, 2)
  65. if err != nil {
  66. req.Error = err
  67. return req, out
  68. }
  69. input.Body.Seek(0, 0)
  70. dst, err := getWriterStore(req, c.TempFolderPath, n >= c.MinFileSize)
  71. if err != nil {
  72. req.Error = err
  73. return req, out
  74. }
  75. encryptor, err := c.ContentCipherBuilder.ContentCipher()
  76. req.Handlers.Build.PushFront(func(r *request.Request) {
  77. if err != nil {
  78. r.Error = err
  79. return
  80. }
  81. md5 := newMD5Reader(input.Body)
  82. sha := newSHA256Writer(dst)
  83. reader, err := encryptor.EncryptContents(md5)
  84. if err != nil {
  85. r.Error = err
  86. return
  87. }
  88. _, err = io.Copy(sha, reader)
  89. if err != nil {
  90. r.Error = err
  91. return
  92. }
  93. data := encryptor.GetCipherData()
  94. env, err := encodeMeta(md5, data)
  95. if err != nil {
  96. r.Error = err
  97. return
  98. }
  99. shaHex := hex.EncodeToString(sha.GetValue())
  100. req.HTTPRequest.Header.Set("X-Amz-Content-Sha256", shaHex)
  101. dst.Seek(0, 0)
  102. input.Body = dst
  103. err = c.SaveStrategy.Save(env, r)
  104. r.Error = err
  105. })
  106. return req, out
  107. }
  108. // PutObject is a wrapper for PutObjectRequest
  109. func (c *EncryptionClient) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
  110. req, out := c.PutObjectRequest(input)
  111. return out, req.Send()
  112. }