aws-sdk-s3 1.61.2 → 1.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/lib/aws-sdk-s3.rb +5 -2
  3. data/lib/aws-sdk-s3/arn/access_point_arn.rb +62 -0
  4. data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +71 -0
  5. data/lib/aws-sdk-s3/bucket.rb +61 -10
  6. data/lib/aws-sdk-s3/bucket_acl.rb +7 -0
  7. data/lib/aws-sdk-s3/bucket_cors.rb +15 -2
  8. data/lib/aws-sdk-s3/bucket_lifecycle.rb +14 -1
  9. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +14 -1
  10. data/lib/aws-sdk-s3/bucket_logging.rb +7 -0
  11. data/lib/aws-sdk-s3/bucket_notification.rb +7 -0
  12. data/lib/aws-sdk-s3/bucket_policy.rb +14 -1
  13. data/lib/aws-sdk-s3/bucket_region_cache.rb +2 -0
  14. data/lib/aws-sdk-s3/bucket_request_payment.rb +7 -0
  15. data/lib/aws-sdk-s3/bucket_tagging.rb +14 -1
  16. data/lib/aws-sdk-s3/bucket_versioning.rb +17 -0
  17. data/lib/aws-sdk-s3/bucket_website.rb +18 -3
  18. data/lib/aws-sdk-s3/client.rb +2521 -1187
  19. data/lib/aws-sdk-s3/client_api.rb +188 -18
  20. data/lib/aws-sdk-s3/customizations.rb +3 -0
  21. data/lib/aws-sdk-s3/customizations/bucket.rb +11 -4
  22. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  23. data/lib/aws-sdk-s3/customizations/object.rb +23 -5
  24. data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
  25. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  26. data/lib/aws-sdk-s3/encryption.rb +4 -0
  27. data/lib/aws-sdk-s3/encryption/client.rb +18 -5
  28. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +72 -26
  29. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  30. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  31. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  32. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  33. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  34. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  35. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  36. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +34 -3
  38. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  39. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  40. data/lib/aws-sdk-s3/encryptionV2/client.rb +566 -0
  41. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +226 -0
  42. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  43. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  44. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +69 -0
  45. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  46. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  47. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  48. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  49. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  50. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +169 -0
  51. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  52. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  53. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  54. data/lib/aws-sdk-s3/errors.rb +2 -0
  55. data/lib/aws-sdk-s3/event_streams.rb +7 -0
  56. data/lib/aws-sdk-s3/file_downloader.rb +10 -8
  57. data/lib/aws-sdk-s3/file_part.rb +2 -0
  58. data/lib/aws-sdk-s3/file_uploader.rb +14 -1
  59. data/lib/aws-sdk-s3/legacy_signer.rb +2 -0
  60. data/lib/aws-sdk-s3/multipart_file_uploader.rb +39 -2
  61. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +3 -1
  62. data/lib/aws-sdk-s3/multipart_upload.rb +18 -1
  63. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  64. data/lib/aws-sdk-s3/multipart_upload_part.rb +66 -7
  65. data/lib/aws-sdk-s3/object.rb +174 -23
  66. data/lib/aws-sdk-s3/object_acl.rb +15 -0
  67. data/lib/aws-sdk-s3/object_copier.rb +2 -0
  68. data/lib/aws-sdk-s3/object_multipart_copier.rb +2 -0
  69. data/lib/aws-sdk-s3/object_summary.rb +185 -20
  70. data/lib/aws-sdk-s3/object_version.rb +44 -4
  71. data/lib/aws-sdk-s3/plugins/accelerate.rb +29 -38
  72. data/lib/aws-sdk-s3/plugins/arn.rb +187 -0
  73. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +2 -2
  74. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +3 -1
  75. data/lib/aws-sdk-s3/plugins/dualstack.rb +5 -1
  76. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +3 -4
  77. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +2 -0
  78. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +11 -3
  79. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +3 -1
  80. data/lib/aws-sdk-s3/plugins/location_constraint.rb +2 -0
  81. data/lib/aws-sdk-s3/plugins/md5s.rb +26 -25
  82. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  83. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  84. data/lib/aws-sdk-s3/plugins/s3_signer.rb +31 -7
  85. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  86. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +118 -0
  87. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  88. data/lib/aws-sdk-s3/presigned_post.rb +68 -32
  89. data/lib/aws-sdk-s3/presigner.rb +102 -34
  90. data/lib/aws-sdk-s3/resource.rb +4 -2
  91. data/lib/aws-sdk-s3/types.rb +2016 -256
  92. data/lib/aws-sdk-s3/waiters.rb +2 -0
  93. metadata +23 -6
  94. data/lib/aws-sdk-s3/plugins/bucket_arn.rb +0 -211
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -10,6 +12,7 @@ module Aws
10
12
  envelope, cipher = context[:encryption][:cipher_provider].encryption_cipher
11
13
  apply_encryption_envelope(context, envelope, cipher)
12
14
  apply_encryption_cipher(context, cipher)
15
+ apply_cse_user_agent(context)
13
16
  @handler.call(context)
14
17
  end
15
18
 
@@ -36,14 +39,22 @@ module Aws
36
39
  context.params[:body] = IOEncrypter.new(cipher, io)
37
40
  context.params[:metadata] ||= {}
38
41
  context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
39
- if md5 = context.params.delete(:content_md5)
40
- context.params[:metadata]['x-amz-unencrypted-content-md5'] = md5
42
+ if context.params.delete(:content_md5)
43
+ warn('Setting content_md5 on client side encrypted objects is deprecated')
41
44
  end
42
45
  context.http_response.on_headers do
43
46
  context.params[:body].close
44
47
  end
45
48
  end
46
49
 
50
+ def apply_cse_user_agent(context)
51
+ if context.config.user_agent_suffix.nil?
52
+ context.config.user_agent_suffix = EC_USER_AGENT
53
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
54
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
55
+ end
56
+ end
57
+
47
58
  end
48
59
  end
49
60
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -7,8 +9,10 @@ module Aws
7
9
  # @param [OpenSSL::Cipher] cipher
8
10
  # @param [IO#write] io An IO-like object that responds to `#write`.
9
11
  def initialize(cipher, io)
10
- @cipher = cipher.clone
11
- @io = io
12
+ @cipher = cipher
13
+ # Ensure that IO is reset between retries
14
+ @io = io.tap { |io| io.truncate(0) if io.respond_to?(:truncate) }
15
+ @cipher_buffer = String.new
12
16
  end
13
17
 
14
18
  # @return [#write]
@@ -16,7 +20,11 @@ module Aws
16
20
 
17
21
  def write(chunk)
18
22
  # decrypt and write
19
- @io.write(@cipher.update(chunk))
23
+ if @cipher.method(:update).arity == 1
24
+ @io.write(@cipher.update(chunk))
25
+ else
26
+ @io.write(@cipher.update(chunk, @cipher_buffer))
27
+ end
20
28
  end
21
29
 
22
30
  def finalize
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require 'tempfile'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -34,15 +36,36 @@ module Aws
34
36
 
35
37
  # @return [Cipher] Given an encryption envelope, returns a
36
38
  # decryption cipher.
37
- def decryption_cipher(envelope)
39
+ def decryption_cipher(envelope, options = {})
38
40
  encryption_context = Json.load(envelope['x-amz-matdesc'])
41
+ cek_alg = envelope['x-amz-cek-alg']
42
+
43
+ case envelope['x-amz-wrap-alg']
44
+ when 'kms'; # NO OP
45
+ when 'kms+context'
46
+ if cek_alg != encryption_context['aws:x-amz-cek-alg']
47
+ raise Errors::DecryptionError, 'Value of cek-alg from envelope'\
48
+ ' does not match the value in the encryption context'
49
+ end
50
+ when 'AES/GCM'
51
+ raise ArgumentError, 'Key mismatch - Client is configured' \
52
+ ' with a KMS key and the x-amz-wrap-alg is AES/GCM.'
53
+ when 'RSA-OAEP-SHA1'
54
+ raise ArgumentError, 'Key mismatch - Client is configured' \
55
+ ' with a KMS key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
56
+ else
57
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
58
+ "#{envelope['x-amz-wrap-alg']}"
59
+ end
60
+
39
61
  key = @kms_client.decrypt(
40
62
  ciphertext_blob: decode64(envelope['x-amz-key-v2']),
41
- encryption_context: encryption_context,
63
+ encryption_context: encryption_context
42
64
  ).plaintext
65
+
43
66
  iv = decode64(envelope['x-amz-iv'])
44
67
  block_mode =
45
- case envelope['x-amz-cek-alg']
68
+ case cek_alg
46
69
  when 'AES/CBC/PKCS5Padding'
47
70
  :CBC
48
71
  when 'AES/CBC/PKCS7Padding'
@@ -59,6 +82,14 @@ module Aws
59
82
 
60
83
  private
61
84
 
85
+ def build_encryption_context(cek_alg, options = {})
86
+ kms_context = (options[:kms_encryption_context] || {})
87
+ .each_with_object({}) { |(k, v), h| h[k.to_s] = v }
88
+ {
89
+ 'aws:x-amz-cek-alg' => cek_alg
90
+ }.merge(kms_context)
91
+ end
92
+
62
93
  def encode64(str)
63
94
  Base64.encode64(str).split("\n") * ""
64
95
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -32,14 +34,14 @@ module Aws
32
34
  if [32, 24, 16].include?(key.bytesize)
33
35
  key
34
36
  else
35
- msg = "invalid key, symmetric key required to be 16, 24, or "
36
- msg << "32 bytes in length, saw length " + key.bytesize.to_s
37
+ msg = 'invalid key, symmetric key required to be 16, 24, or '\
38
+ '32 bytes in length, saw length ' + key.bytesize.to_s
37
39
  raise ArgumentError, msg
38
40
  end
39
41
  else
40
- msg = "invalid encryption key, expected an OpenSSL::PKey::RSA key "
41
- msg << "(for asymmetric encryption) or a String (for symmetric "
42
- msg << "encryption)."
42
+ msg = 'invalid encryption key, expected an OpenSSL::PKey::RSA key '\
43
+ '(for asymmetric encryption) or a String (for symmetric '\
44
+ 'encryption).'
43
45
  raise ArgumentError, msg
44
46
  end
45
47
  end
@@ -48,7 +50,7 @@ module Aws
48
50
  Json.load(description)
49
51
  description
50
52
  rescue Json::ParseError, EncodingError
51
- msg = "expected description to be a valid JSON document string"
53
+ msg = 'expected description to be a valid JSON document string'
52
54
  raise ArgumentError, msg
53
55
  end
54
56
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module Aws
@@ -37,6 +39,29 @@ module Aws
37
39
  end
38
40
  end
39
41
 
42
+
43
+ def decrypt_aes_gcm(key, data, auth_data)
44
+ # data is iv (12B) + key + tag (16B)
45
+ buf = data.unpack('C*')
46
+ iv = buf[0,12].pack('C*') # iv will always be 12 bytes
47
+ tag = buf[-16, 16].pack('C*') # tag is 16 bytes
48
+ enc_key = buf[12, buf.size - (12+16)].pack('C*')
49
+ cipher = aes_cipher(:decrypt, :GCM, key, iv)
50
+ cipher.auth_tag = tag
51
+ cipher.auth_data = auth_data
52
+ cipher.update(enc_key) + cipher.final
53
+ end
54
+
55
+ # returns the decrypted data + auth_data
56
+ def decrypt_rsa(key, enc_data)
57
+ # Plaintext must be KeyLengthInBytes (1 Byte) + DataKey + AuthData
58
+ buf = key.private_decrypt(enc_data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING).unpack('C*')
59
+ key_length = buf[0]
60
+ data = buf[1, key_length].pack('C*')
61
+ auth_data = buf[key_length+1, buf.length - key_length].pack('C*')
62
+ [data, auth_data]
63
+ end
64
+
40
65
  # @param [String] block_mode "CBC" or "ECB"
41
66
  # @param [OpenSSL::PKey::RSA, String, nil] key
42
67
  # @param [String, nil] iv The initialization vector
@@ -0,0 +1,566 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Aws
6
+ module S3
7
+
8
+ REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile]
9
+ SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy]
10
+
11
+ # Provides an encryption client that encrypts and decrypts data client-side,
12
+ # storing the encrypted data in Amazon S3. The `EncryptionV2::Client` (V2 Client)
13
+ # provides improved security over the `Encryption::Client` (V1 Client)
14
+ # by using more modern and secure algorithms. You can use the V2 Client
15
+ # to continue decrypting objects encrypted using deprecated algorithms
16
+ # by setting security_profile: :v2_and_legacy. The latest V1 Client also
17
+ # supports reading and decrypting objects encrypted by the V2 Client.
18
+ #
19
+ # This client uses a process called "envelope encryption". Your private
20
+ # encryption keys and your data's plain-text are **never** sent to
21
+ # Amazon S3. **If you lose you encryption keys, you will not be able to
22
+ # decrypt your data.**
23
+ #
24
+ # ## Envelope Encryption Overview
25
+ #
26
+ # The goal of envelope encryption is to combine the performance of
27
+ # fast symmetric encryption while maintaining the secure key management
28
+ # that asymmetric keys provide.
29
+ #
30
+ # A one-time-use symmetric key (envelope key) is generated client-side.
31
+ # This is used to encrypt the data client-side. This key is then
32
+ # encrypted by your master key and stored alongside your data in Amazon
33
+ # S3.
34
+ #
35
+ # When accessing your encrypted data with the encryption client,
36
+ # the encrypted envelope key is retrieved and decrypted client-side
37
+ # with your master key. The envelope key is then used to decrypt the
38
+ # data client-side.
39
+ #
40
+ # One of the benefits of envelope encryption is that if your master key
41
+ # is compromised, you have the option of just re-encrypting the stored
42
+ # envelope symmetric keys, instead of re-encrypting all of the
43
+ # data in your account.
44
+ #
45
+ # ## Basic Usage
46
+ #
47
+ # The encryption client requires an {Aws::S3::Client}. If you do not
48
+ # provide a `:client`, then a client will be constructed for you.
49
+ #
50
+ # require 'openssl'
51
+ # key = OpenSSL::PKey::RSA.new(1024)
52
+ #
53
+ # # encryption client
54
+ # s3 = Aws::S3::EncryptionV2::Client.new(
55
+ # encryption_key: key,
56
+ # key_wrap_schema: :rsa_oaep_sha1, # the key_wrap_schema must be rsa_oaep_sha1 for asymmetric keys
57
+ # content_encryption_schema: :aes_gcm_no_padding,
58
+ # security_profile: :v2 # use :v2_and_legacy to allow reading/decrypting objects encrypted by the V1 encryption client
59
+ # )
60
+ #
61
+ # # round-trip an object, encrypted/decrypted locally
62
+ # s3.put_object(bucket:'aws-sdk', key:'secret', body:'handshake')
63
+ # s3.get_object(bucket:'aws-sdk', key:'secret').body.read
64
+ # #=> 'handshake'
65
+ #
66
+ # # reading encrypted object without the encryption client
67
+ # # results in the getting the cipher text
68
+ # Aws::S3::Client.new.get_object(bucket:'aws-sdk', key:'secret').body.read
69
+ # #=> "... cipher text ..."
70
+ #
71
+ # ## Required Configuration
72
+ #
73
+ # You must configure all of the following:
74
+ #
75
+ # * a key or key provider - See the Keys section below. The key provided determines
76
+ # the key wrapping schema(s) supported for both encryption and decryption.
77
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
78
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`.
79
+ # More options will be added in future releases.
80
+ # * `security_profile` - Determines the support for reading objects written
81
+ # using older key wrap or content encryption schemas. If you need to read
82
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
83
+ # Otherwise, set it to `:v2`
84
+ #
85
+ # ## Keys
86
+ #
87
+ # For client-side encryption to work, you must provide one of the following:
88
+ #
89
+ # * An encryption key
90
+ # * A {KeyProvider}
91
+ # * A KMS encryption key id
92
+ #
93
+ # Additionally, the key wrapping schema must agree with the type of the key:
94
+ # * :aes_gcm: An AES encryption key or a key provider.
95
+ # * :rsa_oaep_sha1: An RSA encryption key or key provider.
96
+ # * :kms_context: A KMS encryption key id
97
+ #
98
+ # ### An Encryption Key
99
+ #
100
+ # You can pass a single encryption key. This is used as a master key
101
+ # encrypting and decrypting all object keys.
102
+ #
103
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key - used with `key_wrap_schema: :aes_gcm`
104
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair - used with `key_wrap_schema: :rsa_oaep_sha1`
105
+ #
106
+ # s3 = Aws::S3::EncryptionV2::Client.new(
107
+ # encryption_key: key,
108
+ # key_wrap_schema: :aes_gcm, # or :rsa_oaep_sha1 if using RSA
109
+ # content_encryption_schema: :aes_gcm_no_padding,
110
+ # security_profile: :v2
111
+ # )
112
+ #
113
+ # ### Key Provider
114
+ #
115
+ # Alternatively, you can use a {KeyProvider}. A key provider makes
116
+ # it easy to work with multiple keys and simplifies key rotation.
117
+ #
118
+ # ### KMS Encryption Key Id
119
+ #
120
+ # If you pass the id of an AWS Key Management Service (KMS) key and
121
+ # use :kms_content for the key_wrap_schema, then KMS will be used to
122
+ # generate, encrypt and decrypt object keys.
123
+ #
124
+ # # keep track of the kms key id
125
+ # kms = Aws::KMS::Client.new
126
+ # key_id = kms.create_key.key_metadata.key_id
127
+ #
128
+ # Aws::S3::EncryptionV2::Client.new(
129
+ # kms_key_id: key_id,
130
+ # kms_client: kms,
131
+ # key_wrap_schema: :kms_context,
132
+ # content_encryption_schema: :aes_gcm_no_padding,
133
+ # security_profile: :v2
134
+ # )
135
+ #
136
+ # ## Custom Key Providers
137
+ #
138
+ # A {KeyProvider} is any object that responds to:
139
+ #
140
+ # * `#encryption_materials`
141
+ # * `#key_for(materials_description)`
142
+ #
143
+ # Here is a trivial implementation of an in-memory key provider.
144
+ # This is provided as a demonstration of the key provider interface,
145
+ # and should not be used in production:
146
+ #
147
+ # class KeyProvider
148
+ #
149
+ # def initialize(default_key_name, keys)
150
+ # @keys = keys
151
+ # @encryption_materials = Aws::S3::EncryptionV2::Materials.new(
152
+ # key: @keys[default_key_name],
153
+ # description: JSON.dump(key: default_key_name),
154
+ # )
155
+ # end
156
+ #
157
+ # attr_reader :encryption_materials
158
+ #
159
+ # def key_for(matdesc)
160
+ # key_name = JSON.load(matdesc)['key']
161
+ # if key = @keys[key_name]
162
+ # key
163
+ # else
164
+ # raise "encryption key not found for: #{matdesc.inspect}"
165
+ # end
166
+ # end
167
+ # end
168
+ #
169
+ # Given the above key provider, you can create an encryption client that
170
+ # chooses the key to use based on the materials description stored with
171
+ # the encrypted object. This makes it possible to use multiple keys
172
+ # and simplifies key rotation.
173
+ #
174
+ # # uses "new-key" for encrypting objects, uses either for decrypting
175
+ # keys = KeyProvider.new('new-key', {
176
+ # "old-key" => Base64.decode64("kM5UVbhE/4rtMZJfsadYEdm2vaKFsmV2f5+URSeUCV4="),
177
+ # "new-key" => Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
178
+ # }),
179
+ #
180
+ # # chooses the key based on the materials description stored
181
+ # # with the encrypted object
182
+ # s3 = Aws::S3::EncryptionV2::Client.new(
183
+ # key_provider: keys,
184
+ # key_wrap_schema: ...,
185
+ # content_encryption_schema: :aes_gcm_no_padding,
186
+ # security_profile: :v2
187
+ # )
188
+ #
189
+ # ## Materials Description
190
+ #
191
+ # A materials description is JSON document string that is stored
192
+ # in the metadata (or instruction file) of an encrypted object.
193
+ # The {DefaultKeyProvider} uses the empty JSON document `"{}"`.
194
+ #
195
+ # When building a key provider, you are free to store whatever
196
+ # information you need to identify the master key that was used
197
+ # to encrypt the object.
198
+ #
199
+ # ## Envelope Location
200
+ #
201
+ # By default, the encryption client store the encryption envelope
202
+ # with the object, as metadata. You can choose to have the envelope
203
+ # stored in a separate "instruction file". An instruction file
204
+ # is an object, with the key of the encrypted object, suffixed with
205
+ # `".instruction"`.
206
+ #
207
+ # Specify the `:envelope_location` option as `:instruction_file` to
208
+ # use an instruction file for storing the envelope.
209
+ #
210
+ # # default behavior
211
+ # s3 = Aws::S3::EncryptionV2::Client.new(
212
+ # key_provider: ...,
213
+ # envelope_location: :metadata,
214
+ # )
215
+ #
216
+ # # store envelope in a separate object
217
+ # s3 = Aws::S3::EncryptionV2::Client.new(
218
+ # key_provider: ...,
219
+ # envelope_location: :instruction_file,
220
+ # instruction_file_suffix: '.instruction' # default
221
+ # key_wrap_schema: ...,
222
+ # content_encryption_schema: :aes_gcm_no_padding,
223
+ # security_profile: :v2
224
+ # )
225
+ #
226
+ # When using an instruction file, multiple requests are made when
227
+ # putting and getting the object. **This may cause issues if you are
228
+ # issuing concurrent PUT and GET requests to an encrypted object.**
229
+ #
230
+ module EncryptionV2
231
+ class Client
232
+
233
+ extend Deprecations
234
+ extend Forwardable
235
+ def_delegators :@client, :config, :delete_object, :head_object, :build_request
236
+
237
+ # Creates a new encryption client. You must configure all of the following:
238
+ #
239
+ # * a key or key provider - The key provided also determines the key wrapping
240
+ # schema(s) supported for both encryption and decryption.
241
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
242
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`
243
+ # More options will be added in future releases.
244
+ # * `security_profile` - Determines the support for reading objects written
245
+ # using older key wrap or content encryption schemas. If you need to read
246
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
247
+ # Otherwise, set it to `:v2`
248
+ #
249
+ # To configure the key you must provide one of the following set of options:
250
+ #
251
+ # * `:encryption_key`
252
+ # * `:kms_key_id`
253
+ # * `:key_provider`
254
+ #
255
+ # You may also pass any other options accepted by `Client#initialize`.
256
+ #
257
+ # @option options [S3::Client] :client A basic S3 client that is used
258
+ # to make api calls. If a `:client` is not provided, a new {S3::Client}
259
+ # will be constructed.
260
+ #
261
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key The master
262
+ # key to use for encrypting/decrypting all objects.
263
+ #
264
+ # @option options [String] :kms_key_id When you provide a `:kms_key_id`,
265
+ # then AWS Key Management Service (KMS) will be used to manage the
266
+ # object encryption keys. By default a {KMS::Client} will be
267
+ # constructed for KMS API calls. Alternatively, you can provide
268
+ # your own via `:kms_client`. To only support decryption/reads, you may
269
+ # provide `:allow_decrypt_with_any_cmk` which will use
270
+ # the implicit CMK associated with the data during reads but will
271
+ # not allow you to encrypt/write objects with this client.
272
+ #
273
+ # @option options [#key_for] :key_provider Any object that responds
274
+ # to `#key_for`. This method should accept a materials description
275
+ # JSON document string and return return an encryption key.
276
+ #
277
+ # @option options [required, Symbol] :key_wrap_schema The Key wrapping
278
+ # schema to be used. It must match the type of key configured.
279
+ # Must be one of the following:
280
+ #
281
+ # * :kms_context (Must provide kms_key_id)
282
+ # * :aes_gcm (Must provide an AES (string) key)
283
+ # * :rsa_oaep_sha1 (Must provide an RSA key)
284
+ #
285
+ # @option options [required, Symbol] :content_encryption_schema
286
+ # Must be one of the following:
287
+ #
288
+ # * :aes_gcm_no_padding
289
+ #
290
+ # @option options [Required, Symbol] :security_profile
291
+ # Determines the support for reading objects written using older
292
+ # key wrap or content encryption schemas.
293
+ # Must be one of the following:
294
+ #
295
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
296
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
297
+ #
298
+ # @option options [Symbol] :envelope_location (:metadata) Where to
299
+ # store the envelope encryption keys. By default, the envelope is
300
+ # stored with the encrypted object. If you pass `:instruction_file`,
301
+ # then the envelope is stored in a separate object in Amazon S3.
302
+ #
303
+ # @option options [String] :instruction_file_suffix ('.instruction')
304
+ # When `:envelope_location` is `:instruction_file` then the
305
+ # instruction file uses the object key with this suffix appended.
306
+ #
307
+ # @option options [KMS::Client] :kms_client A default {KMS::Client}
308
+ # is constructed when using KMS to manage encryption keys.
309
+ #
310
+ def initialize(options = {})
311
+ validate_params(options)
312
+ @client = extract_client(options)
313
+ @cipher_provider = cipher_provider(options)
314
+ @envelope_location = extract_location(options)
315
+ @instruction_file_suffix = extract_suffix(options)
316
+ @kms_allow_decrypt_with_any_cmk =
317
+ options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
318
+ @security_profile = extract_security_profile(options)
319
+ end
320
+
321
+ # @return [S3::Client]
322
+ attr_reader :client
323
+
324
+ # @return [KeyProvider, nil] Returns `nil` if you are using
325
+ # AWS Key Management Service (KMS).
326
+ attr_reader :key_provider
327
+
328
+ # @return [Symbol] Determines the support for reading objects written
329
+ # using older key wrap or content encryption schemas.
330
+ attr_reader :security_profile
331
+
332
+ # @return [Boolean] If true the provided KMS key_id will not be used
333
+ # during decrypt, allowing decryption with the key_id from the object.
334
+ attr_reader :kms_allow_decrypt_with_any_cmk
335
+
336
+ # @return [Symbol<:metadata, :instruction_file>]
337
+ attr_reader :envelope_location
338
+
339
+ # @return [String] When {#envelope_location} is `:instruction_file`,
340
+ # the envelope is stored in the object with the object key suffixed
341
+ # by this string.
342
+ attr_reader :instruction_file_suffix
343
+
344
+ # Uploads an object to Amazon S3, encrypting data client-side.
345
+ # See {S3::Client#put_object} for documentation on accepted
346
+ # request parameters.
347
+ # @option params [Hash] :kms_encryption_context Additional encryption
348
+ # context to use with KMS. Applies only when KMS is used. In order
349
+ # to decrypt the object you will need to provide the identical
350
+ # :kms_encryption_context to `get_object`.
351
+ # @option (see S3::Client#put_object)
352
+ # @return (see S3::Client#put_object)
353
+ # @see S3::Client#put_object
354
+ def put_object(params = {})
355
+ kms_encryption_context = params.delete(:kms_encryption_context)
356
+ req = @client.build_request(:put_object, params)
357
+ req.handlers.add(EncryptHandler, priority: 95)
358
+ req.context[:encryption] = {
359
+ cipher_provider: @cipher_provider,
360
+ envelope_location: @envelope_location,
361
+ instruction_file_suffix: @instruction_file_suffix,
362
+ kms_encryption_context: kms_encryption_context
363
+ }
364
+ req.send_request
365
+ end
366
+
367
+ # Gets an object from Amazon S3, decrypting data locally.
368
+ # See {S3::Client#get_object} for documentation on accepted
369
+ # request parameters.
370
+ # Warning: If you provide a block to get_object or set the request
371
+ # parameter :response_target to a Proc, then read the entire object to the
372
+ # end before you start using the decrypted data. This is to verify that
373
+ # the object has not been modified since it was encrypted.
374
+ #
375
+ # @option options [Symbol] :security_profile
376
+ # Determines the support for reading objects written using older
377
+ # key wrap or content encryption schemas. Overrides the value set
378
+ # on client construction if provided.
379
+ # Must be one of the following:
380
+ #
381
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
382
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
383
+ # @option params [String] :instruction_file_suffix The suffix
384
+ # used to find the instruction file containing the encryption
385
+ # envelope. You should not set this option when the envelope
386
+ # is stored in the object metadata. Defaults to
387
+ # {#instruction_file_suffix}.
388
+ # @option params [Hash] :kms_encryption_context Additional encryption
389
+ # context to use with KMS. Applies only when KMS is used.
390
+ # @option options [Boolean] :kms_allow_decrypt_with_any_cmk (false)
391
+ # By default the KMS CMK ID (kms_key_id) will be used during decrypt
392
+ # and will fail if there is a mismatch. Setting this to true
393
+ # will use the implicit CMK associated with the data.
394
+ # @option (see S3::Client#get_object)
395
+ # @return (see S3::Client#get_object)
396
+ # @see S3::Client#get_object
397
+ # @note The `:range` request parameter is not supported.
398
+ def get_object(params = {}, &block)
399
+ if params[:range]
400
+ raise NotImplementedError, '#get_object with :range not supported'
401
+ end
402
+ envelope_location, instruction_file_suffix = envelope_options(params)
403
+ kms_encryption_context = params.delete(:kms_encryption_context)
404
+ kms_any_cmk_mode = kms_any_cmk_mode(params)
405
+ security_profile = security_profile_from_params(params)
406
+
407
+ req = @client.build_request(:get_object, params)
408
+ req.handlers.add(DecryptHandler)
409
+ req.context[:encryption] = {
410
+ cipher_provider: @cipher_provider,
411
+ envelope_location: envelope_location,
412
+ instruction_file_suffix: instruction_file_suffix,
413
+ kms_encryption_context: kms_encryption_context,
414
+ kms_allow_decrypt_with_any_cmk: kms_any_cmk_mode,
415
+ security_profile: security_profile
416
+ }
417
+ req.send_request(target: block)
418
+ end
419
+
420
+ private
421
+
422
+ # Validate required parameters exist and don't conflict.
423
+ # The cek_alg and wrap_alg are passed on to the CipherProviders
424
+ # and further validated there
425
+ def validate_params(options)
426
+ unless (missing_params = REQUIRED_PARAMS - options.keys).empty?
427
+ raise ArgumentError, "Missing required parameter(s): "\
428
+ "#{missing_params.map{ |s| ":#{s}" }.join(', ')}"
429
+ end
430
+
431
+ wrap_alg = options[:key_wrap_schema]
432
+
433
+ # validate that the wrap alg matches the type of key given
434
+ case wrap_alg
435
+ when :kms_context
436
+ unless options[:kms_key_id]
437
+ raise ArgumentError, 'You must provide :kms_key_id to use :kms_context'
438
+ end
439
+ end
440
+ end
441
+
442
+ def extract_client(options)
443
+ options[:client] || begin
444
+ options = options.dup
445
+ options.delete(:kms_key_id)
446
+ options.delete(:kms_client)
447
+ options.delete(:key_provider)
448
+ options.delete(:encryption_key)
449
+ options.delete(:envelope_location)
450
+ options.delete(:instruction_file_suffix)
451
+ REQUIRED_PARAMS.each { |p| options.delete(p) }
452
+ S3::Client.new(options)
453
+ end
454
+ end
455
+
456
+ def kms_client(options)
457
+ options[:kms_client] || begin
458
+ KMS::Client.new(
459
+ region: @client.config.region,
460
+ credentials: @client.config.credentials,
461
+ )
462
+ end
463
+ end
464
+
465
+ def cipher_provider(options)
466
+ if options[:kms_key_id]
467
+ KmsCipherProvider.new(
468
+ kms_key_id: options[:kms_key_id],
469
+ kms_client: kms_client(options),
470
+ key_wrap_schema: options[:key_wrap_schema],
471
+ content_encryption_schema: options[:content_encryption_schema]
472
+ )
473
+ else
474
+ @key_provider = extract_key_provider(options)
475
+ DefaultCipherProvider.new(
476
+ key_provider: @key_provider,
477
+ key_wrap_schema: options[:key_wrap_schema],
478
+ content_encryption_schema: options[:content_encryption_schema]
479
+ )
480
+ end
481
+ end
482
+
483
+ def extract_key_provider(options)
484
+ if options[:key_provider]
485
+ options[:key_provider]
486
+ elsif options[:encryption_key]
487
+ DefaultKeyProvider.new(options)
488
+ else
489
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
490
+ raise ArgumentError, msg
491
+ end
492
+ end
493
+
494
+ def envelope_options(params)
495
+ location = params.delete(:envelope_location) || @envelope_location
496
+ suffix = params.delete(:instruction_file_suffix)
497
+ if suffix
498
+ [:instruction_file, suffix]
499
+ else
500
+ [location, @instruction_file_suffix]
501
+ end
502
+ end
503
+
504
+ def extract_location(options)
505
+ location = options[:envelope_location] || :metadata
506
+ if [:metadata, :instruction_file].include?(location)
507
+ location
508
+ else
509
+ msg = ':envelope_location must be :metadata or :instruction_file '\
510
+ "got #{location.inspect}"
511
+ raise ArgumentError, msg
512
+ end
513
+ end
514
+
515
+ def extract_suffix(options)
516
+ suffix = options[:instruction_file_suffix] || '.instruction'
517
+ if suffix.is_a? String
518
+ suffix
519
+ else
520
+ msg = ':instruction_file_suffix must be a String'
521
+ raise ArgumentError, msg
522
+ end
523
+ end
524
+
525
+ def kms_any_cmk_mode(params)
526
+ if !params[:kms_allow_decrypt_with_any_cmk].nil?
527
+ params.delete(:kms_allow_decrypt_with_any_cmk)
528
+ else
529
+ @kms_allow_decrypt_with_any_cmk
530
+ end
531
+ end
532
+
533
+ def extract_security_profile(options)
534
+ validate_security_profile(options[:security_profile])
535
+ end
536
+
537
+ def security_profile_from_params(params)
538
+ security_profile =
539
+ if !params[:security_profile].nil?
540
+ params.delete(:security_profile)
541
+ else
542
+ @security_profile
543
+ end
544
+ validate_security_profile(security_profile)
545
+ end
546
+
547
+ def validate_security_profile(security_profile)
548
+ unless SUPPORTED_SECURITY_PROFILES.include? security_profile
549
+ raise ArgumentError, "Unsupported security profile: :#{security_profile}. " \
550
+ "Please provide one of: #{SUPPORTED_SECURITY_PROFILES.map { |s| ":#{s}" }.join(', ')}"
551
+ end
552
+ if security_profile == :v2_and_legacy && !@warned_about_legacy
553
+ @warned_about_legacy = true
554
+ warn(
555
+ 'The S3 Encryption Client is configured to read encrypted objects ' \
556
+ "with legacy encryption modes. If you don't have objects " \
557
+ 'encrypted with these legacy modes, you should disable support ' \
558
+ 'for them to enhance security.'
559
+ )
560
+ end
561
+ security_profile
562
+ end
563
+ end
564
+ end
565
+ end
566
+ end