aws-sdk-resources 2.11.561 → 2.11.562

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-sdk-resources/services/s3.rb +1 -0
  3. data/lib/aws-sdk-resources/services/s3/encryption.rb +3 -0
  4. data/lib/aws-sdk-resources/services/s3/encryption/client.rb +24 -7
  5. data/lib/aws-sdk-resources/services/s3/encryption/decrypt_handler.rb +65 -25
  6. data/lib/aws-sdk-resources/services/s3/encryption/default_cipher_provider.rb +43 -5
  7. data/lib/aws-sdk-resources/services/s3/encryption/default_key_provider.rb +2 -0
  8. data/lib/aws-sdk-resources/services/s3/encryption/encrypt_handler.rb +13 -2
  9. data/lib/aws-sdk-resources/services/s3/encryption/errors.rb +2 -0
  10. data/lib/aws-sdk-resources/services/s3/encryption/io_auth_decrypter.rb +2 -0
  11. data/lib/aws-sdk-resources/services/s3/encryption/io_decrypter.rb +8 -1
  12. data/lib/aws-sdk-resources/services/s3/encryption/io_encrypter.rb +2 -0
  13. data/lib/aws-sdk-resources/services/s3/encryption/key_provider.rb +2 -0
  14. data/lib/aws-sdk-resources/services/s3/encryption/kms_cipher_provider.rb +36 -3
  15. data/lib/aws-sdk-resources/services/s3/encryption/materials.rb +8 -6
  16. data/lib/aws-sdk-resources/services/s3/encryption/utils.rb +25 -0
  17. data/lib/aws-sdk-resources/services/s3/encryptionV2/client.rb +559 -0
  18. data/lib/aws-sdk-resources/services/s3/encryptionV2/decrypt_handler.rb +214 -0
  19. data/lib/aws-sdk-resources/services/s3/encryptionV2/default_cipher_provider.rb +170 -0
  20. data/lib/aws-sdk-resources/services/s3/encryptionV2/default_key_provider.rb +40 -0
  21. data/lib/aws-sdk-resources/services/s3/encryptionV2/encrypt_handler.rb +69 -0
  22. data/lib/aws-sdk-resources/services/s3/encryptionV2/errors.rb +37 -0
  23. data/lib/aws-sdk-resources/services/s3/encryptionV2/io_auth_decrypter.rb +58 -0
  24. data/lib/aws-sdk-resources/services/s3/encryptionV2/io_decrypter.rb +37 -0
  25. data/lib/aws-sdk-resources/services/s3/encryptionV2/io_encrypter.rb +73 -0
  26. data/lib/aws-sdk-resources/services/s3/encryptionV2/key_provider.rb +31 -0
  27. data/lib/aws-sdk-resources/services/s3/encryptionV2/kms_cipher_provider.rb +169 -0
  28. data/lib/aws-sdk-resources/services/s3/encryptionV2/materials.rb +60 -0
  29. data/lib/aws-sdk-resources/services/s3/encryptionV2/utils.rb +103 -0
  30. data/lib/aws-sdk-resources/services/s3/encryption_v2.rb +24 -0
  31. metadata +18 -4
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -34,17 +36,40 @@ 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
71
+ when 'AES/CBC/PKCS7Padding'
72
+ :CBC
48
73
  when 'AES/GCM/NoPadding'
49
74
  :GCM
50
75
  else
@@ -57,6 +82,14 @@ module Aws
57
82
 
58
83
  private
59
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
+
60
93
  def encode64(str)
61
94
  Base64.encode64(str).split("\n") * ""
62
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,559 @@
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
+ # * a key or key provider - See the Keys section below. The key provided determines
75
+ # the key wrapping schema(s) supported for both encryption and decryption.
76
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
77
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`.
78
+ # More options will be added in future releases.
79
+ # * `security_profile` - Determines the support for reading objects written
80
+ # using older key wrap or content encryption schemas. If you need to read
81
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
82
+ # Otherwise, set it to `:v2`
83
+ #
84
+ # ## Keys
85
+ #
86
+ # For client-side encryption to work, you must provide one of the following:
87
+ #
88
+ # * An encryption key
89
+ # * A {KeyProvider}
90
+ # * A KMS encryption key id
91
+ #
92
+ # Additionally, the key wrapping schema must agree with the type of the key:
93
+ # * :aes_gcm: An AES encryption key or a key provider.
94
+ # * :rsa_oaep_sha1: An RSA encryption key or key provider.
95
+ # * :kms_context: A KMS encryption key id
96
+ #
97
+ # ### An Encryption Key
98
+ #
99
+ # You can pass a single encryption key. This is used as a master key
100
+ # encrypting and decrypting all object keys.
101
+ #
102
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key - used with `key_wrap_schema: :aes_gcm`
103
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair - used with `key_wrap_schema: :rsa_oaep_sha1`
104
+ #
105
+ # s3 = Aws::S3::EncryptionV2::Client.new(
106
+ # encryption_key: key,
107
+ # key_wrap_schema: :aes_gcm, # or :rsa_oaep_sha1 if using RSA
108
+ # content_encryption_schema: :aes_gcm_no_padding,
109
+ # security_profile: :v2
110
+ # )
111
+ #
112
+ # ### Key Provider
113
+ #
114
+ # Alternatively, you can use a {KeyProvider}. A key provider makes
115
+ # it easy to work with multiple keys and simplifies key rotation.
116
+ #
117
+ # ### KMS Encryption Key Id
118
+ #
119
+ # If you pass the id of an AWS Key Management Service (KMS) key and
120
+ # use :kms_content for the key_wrap_schema, then KMS will be used to
121
+ # generate, encrypt and decrypt object keys.
122
+ #
123
+ # # keep track of the kms key id
124
+ # kms = Aws::KMS::Client.new
125
+ # key_id = kms.create_key.key_metadata.key_id
126
+ #
127
+ # Aws::S3::EncryptionV2::Client.new(
128
+ # kms_key_id: key_id,
129
+ # kms_client: kms,
130
+ # key_wrap_schema: :kms_context,
131
+ # content_encryption_schema: :aes_gcm_no_padding,
132
+ # security_profile: :v2
133
+ # )
134
+ #
135
+ # ## Custom Key Providers
136
+ #
137
+ # A {KeyProvider} is any object that responds to:
138
+ #
139
+ # * `#encryption_materials`
140
+ # * `#key_for(materials_description)`
141
+ #
142
+ # Here is a trivial implementation of an in-memory key provider.
143
+ # This is provided as a demonstration of the key provider interface,
144
+ # and should not be used in production:
145
+ #
146
+ # class KeyProvider
147
+ #
148
+ # def initialize(default_key_name, keys)
149
+ # @keys = keys
150
+ # @encryption_materials = Aws::S3::EncryptionV2::Materials.new(
151
+ # key: @keys[default_key_name],
152
+ # description: JSON.dump(key: default_key_name),
153
+ # )
154
+ # end
155
+ #
156
+ # attr_reader :encryption_materials
157
+ #
158
+ # def key_for(matdesc)
159
+ # key_name = JSON.load(matdesc)['key']
160
+ # if key = @keys[key_name]
161
+ # key
162
+ # else
163
+ # raise "encryption key not found for: #{matdesc.inspect}"
164
+ # end
165
+ # end
166
+ # end
167
+ #
168
+ # Given the above key provider, you can create an encryption client that
169
+ # chooses the key to use based on the materials description stored with
170
+ # the encrypted object. This makes it possible to use multiple keys
171
+ # and simplifies key rotation.
172
+ #
173
+ # # uses "new-key" for encrypting objects, uses either for decrypting
174
+ # keys = KeyProvider.new('new-key', {
175
+ # "old-key" => Base64.decode64("kM5UVbhE/4rtMZJfsadYEdm2vaKFsmV2f5+URSeUCV4="),
176
+ # "new-key" => Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
177
+ # }),
178
+ #
179
+ # # chooses the key based on the materials description stored
180
+ # # with the encrypted object
181
+ # s3 = Aws::S3::EncryptionV2::Client.new(
182
+ # key_provider: keys,
183
+ # key_wrap_schema: ...,
184
+ # content_encryption_schema: :aes_gcm_no_padding,
185
+ # security_profile: :v2
186
+ # )
187
+ #
188
+ # ## Materials Description
189
+ #
190
+ # A materials description is JSON document string that is stored
191
+ # in the metadata (or instruction file) of an encrypted object.
192
+ # The {DefaultKeyProvider} uses the empty JSON document `"{}"`.
193
+ #
194
+ # When building a key provider, you are free to store whatever
195
+ # information you need to identify the master key that was used
196
+ # to encrypt the object.
197
+ #
198
+ # ## Envelope Location
199
+ #
200
+ # By default, the encryption client store the encryption envelope
201
+ # with the object, as metadata. You can choose to have the envelope
202
+ # stored in a separate "instruction file". An instruction file
203
+ # is an object, with the key of the encrypted object, suffixed with
204
+ # `".instruction"`.
205
+ #
206
+ # Specify the `:envelope_location` option as `:instruction_file` to
207
+ # use an instruction file for storing the envelope.
208
+ #
209
+ # # default behavior
210
+ # s3 = Aws::S3::EncryptionV2::Client.new(
211
+ # key_provider: ...,
212
+ # envelope_location: :metadata,
213
+ # )
214
+ #
215
+ # # store envelope in a separate object
216
+ # s3 = Aws::S3::EncryptionV2::Client.new(
217
+ # key_provider: ...,
218
+ # envelope_location: :instruction_file,
219
+ # instruction_file_suffix: '.instruction' # default
220
+ # key_wrap_schema: ...,
221
+ # content_encryption_schema: :aes_gcm_no_padding,
222
+ # security_profile: :v2
223
+ # )
224
+ #
225
+ # When using an instruction file, multiple requests are made when
226
+ # putting and getting the object. **This may cause issues if you are
227
+ # issuing concurrent PUT and GET requests to an encrypted object.**
228
+ #
229
+ module EncryptionV2
230
+ class Client
231
+
232
+ extend Deprecations
233
+ extend Forwardable
234
+ def_delegators :@client, :config, :delete_object, :head_object, :build_request
235
+
236
+ # Creates a new encryption client. You must configure all of the following:
237
+ # * a key or key provider - The key provided also determines the key wrapping
238
+ # schema(s) supported for both encryption and decryption.
239
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
240
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`
241
+ # More options will be added in future releases.
242
+ # * `security_profile` - Determines the support for reading objects written
243
+ # using older key wrap or content encryption schemas. If you need to read
244
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
245
+ # Otherwise, set it to `:v2`
246
+ #
247
+ # To configure the key you must provide one of the following set of options:
248
+ #
249
+ # * `:encryption_key`
250
+ # * `:kms_key_id`
251
+ # * `:key_provider`
252
+ #
253
+ # You may also pass any other options accepted by `Client#initialize`.
254
+ #
255
+ # @option options [S3::Client] :client A basic S3 client that is used
256
+ # to make api calls. If a `:client` is not provided, a new {S3::Client}
257
+ # will be constructed.
258
+ #
259
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key The master
260
+ # key to use for encrypting/decrypting all objects.
261
+ #
262
+ # @option options [String] :kms_key_id When you provide a `:kms_key_id`,
263
+ # then AWS Key Management Service (KMS) will be used to manage the
264
+ # object encryption keys. By default a {KMS::Client} will be
265
+ # constructed for KMS API calls. Alternatively, you can provide
266
+ # your own via `:kms_client`. To only support decryption/reads, you may
267
+ # provide `:allow_decrypt_with_any_cmk` which will use
268
+ # the implicit CMK associated with the data during reads but will
269
+ # not allow you to encrypt/write objects with this client.
270
+ #
271
+ # @option options [#key_for] :key_provider Any object that responds
272
+ # to `#key_for`. This method should accept a materials description
273
+ # JSON document string and return return an encryption key.
274
+ #
275
+ # @option options [required, Symbol] :key_wrap_schema The Key wrapping
276
+ # schema to be used. It must match the type of key configured.
277
+ # Must be one of the following:
278
+ #
279
+ # * :kms_context (Must provide kms_key_id)
280
+ # * :aes_gcm (Must provide an AES (string) key)
281
+ # * :rsa_oaep_sha1 (Must provide an RSA key)
282
+ #
283
+ # @option options [required, Symbol] :content_encryption_schema
284
+ # Must be one of the following:
285
+ #
286
+ # * :aes_gcm_no_padding
287
+ #
288
+ # @option options [Required, Symbol] :security_profile
289
+ # Determines the support for reading objects written using older
290
+ # key wrap or content encryption schemas.
291
+ # Must be one of the following:
292
+ #
293
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
294
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
295
+ #
296
+ # @option options [Symbol] :envelope_location (:metadata) Where to
297
+ # store the envelope encryption keys. By default, the envelope is
298
+ # stored with the encrypted object. If you pass `:instruction_file`,
299
+ # then the envelope is stored in a separate object in Amazon S3.
300
+ #
301
+ # @option options [String] :instruction_file_suffix ('.instruction')
302
+ # When `:envelope_location` is `:instruction_file` then the
303
+ # instruction file uses the object key with this suffix appended.
304
+ #
305
+ # @option options [KMS::Client] :kms_client A default {KMS::Client}
306
+ # is constructed when using KMS to manage encryption keys.
307
+ #
308
+ def initialize(options = {})
309
+ validate_params(options)
310
+ @client = extract_client(options)
311
+ @cipher_provider = cipher_provider(options)
312
+ @envelope_location = extract_location(options)
313
+ @instruction_file_suffix = extract_suffix(options)
314
+ @kms_allow_decrypt_with_any_cmk =
315
+ options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
316
+ @security_profile = extract_security_profile(options)
317
+ end
318
+
319
+ # @return [S3::Client]
320
+ attr_reader :client
321
+
322
+ # @return [KeyProvider, nil] Returns `nil` if you are using
323
+ # AWS Key Management Service (KMS).
324
+ attr_reader :key_provider
325
+
326
+ # @return [Symbol] Determines the support for reading objects written
327
+ # using older key wrap or content encryption schemas.
328
+ attr_reader :security_profile
329
+
330
+ # @return [Boolean] If true the provided KMS key_id will not be used
331
+ # during decrypt, allowing decryption with the key_id from the object.
332
+ attr_reader :kms_allow_decrypt_with_any_cmk
333
+
334
+ # @return [Symbol<:metadata, :instruction_file>]
335
+ attr_reader :envelope_location
336
+
337
+ # @return [String] When {#envelope_location} is `:instruction_file`,
338
+ # the envelope is stored in the object with the object key suffixed
339
+ # by this string.
340
+ attr_reader :instruction_file_suffix
341
+
342
+ # Uploads an object to Amazon S3, encrypting data client-side.
343
+ # See {S3::Client#put_object} for documentation on accepted
344
+ # request parameters.
345
+ # @option params [Hash] :kms_encryption_context Additional encryption
346
+ # context to use with KMS. Applies only when KMS is used. In order
347
+ # to decrypt the object you will need to provide the identical
348
+ # :kms_encryption_context to `get_object`.
349
+ # @option (see S3::Client#put_object)
350
+ # @return (see S3::Client#put_object)
351
+ # @see S3::Client#put_object
352
+ def put_object(params = {})
353
+ kms_encryption_context = params.delete(:kms_encryption_context)
354
+ req = @client.build_request(:put_object, params)
355
+ req.handlers.add(EncryptHandler, priority: 95)
356
+ req.context[:encryption] = {
357
+ cipher_provider: @cipher_provider,
358
+ envelope_location: @envelope_location,
359
+ instruction_file_suffix: @instruction_file_suffix,
360
+ kms_encryption_context: kms_encryption_context
361
+ }
362
+ req.send_request
363
+ end
364
+
365
+ # Gets an object from Amazon S3, decrypting data locally.
366
+ # See {S3::Client#get_object} for documentation on accepted
367
+ # request parameters.
368
+ # @option options [Symbol] :security_profile
369
+ # Determines the support for reading objects written using older
370
+ # key wrap or content encryption schemas. Overrides the value set
371
+ # on client construction if provided.
372
+ # Must be one of the following:
373
+ #
374
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
375
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
376
+ # @option params [String] :instruction_file_suffix The suffix
377
+ # used to find the instruction file containing the encryption
378
+ # envelope. You should not set this option when the envelope
379
+ # is stored in the object metadata. Defaults to
380
+ # {#instruction_file_suffix}.
381
+ # @option params [Hash] :kms_encryption_context Additional encryption
382
+ # context to use with KMS. Applies only when KMS is used.
383
+ # @option options [Boolean] :kms_allow_decrypt_with_any_cmk (false)
384
+ # By default the KMS CMK ID (kms_key_id) will be used during decrypt
385
+ # and will fail if there is a mismatch. Setting this to true
386
+ # will use the implicit CMK associated with the data.
387
+ # @option (see S3::Client#get_object)
388
+ # @return (see S3::Client#get_object)
389
+ # @see S3::Client#get_object
390
+ # @note The `:range` request parameter is not yet supported.
391
+ def get_object(params = {}, &block)
392
+ if params[:range]
393
+ raise NotImplementedError, '#get_object with :range not supported'
394
+ end
395
+ envelope_location, instruction_file_suffix = envelope_options(params)
396
+ kms_encryption_context = params.delete(:kms_encryption_context)
397
+ kms_any_cmk_mode = kms_any_cmk_mode(params)
398
+ security_profile = security_profile_from_params(params)
399
+
400
+ req = @client.build_request(:get_object, params)
401
+ req.handlers.add(DecryptHandler)
402
+ req.context[:encryption] = {
403
+ cipher_provider: @cipher_provider,
404
+ envelope_location: envelope_location,
405
+ instruction_file_suffix: instruction_file_suffix,
406
+ kms_encryption_context: kms_encryption_context,
407
+ kms_allow_decrypt_with_any_cmk: kms_any_cmk_mode,
408
+ security_profile: security_profile
409
+ }
410
+ req.send_request(target: block)
411
+ end
412
+
413
+ private
414
+
415
+ # Validate required parameters exist and don't conflict.
416
+ # The cek_alg and wrap_alg are passed on to the CipherProviders
417
+ # and further validated there
418
+ def validate_params(options)
419
+ unless (missing_params = REQUIRED_PARAMS - options.keys).empty?
420
+ raise ArgumentError, "Missing required parameter(s): "\
421
+ "#{missing_params.map{ |s| ":#{s}" }.join(', ')}"
422
+ end
423
+
424
+ wrap_alg = options[:key_wrap_schema]
425
+
426
+ # validate that the wrap alg matches the type of key given
427
+ case wrap_alg
428
+ when :kms_context
429
+ unless options[:kms_key_id]
430
+ raise ArgumentError, 'You must provide :kms_key_id to use :kms_context'
431
+ end
432
+ end
433
+ end
434
+
435
+ def extract_client(options)
436
+ options[:client] || begin
437
+ options = options.dup
438
+ options.delete(:kms_key_id)
439
+ options.delete(:kms_client)
440
+ options.delete(:key_provider)
441
+ options.delete(:encryption_key)
442
+ options.delete(:envelope_location)
443
+ options.delete(:instruction_file_suffix)
444
+ REQUIRED_PARAMS.each { |p| options.delete(p) }
445
+ S3::Client.new(options)
446
+ end
447
+ end
448
+
449
+ def kms_client(options)
450
+ options[:kms_client] || begin
451
+ KMS::Client.new(
452
+ region: @client.config.region,
453
+ credentials: @client.config.credentials,
454
+ )
455
+ end
456
+ end
457
+
458
+ def cipher_provider(options)
459
+ if options[:kms_key_id]
460
+ KmsCipherProvider.new(
461
+ kms_key_id: options[:kms_key_id],
462
+ kms_client: kms_client(options),
463
+ key_wrap_schema: options[:key_wrap_schema],
464
+ content_encryption_schema: options[:content_encryption_schema]
465
+ )
466
+ else
467
+ @key_provider = extract_key_provider(options)
468
+ DefaultCipherProvider.new(
469
+ key_provider: @key_provider,
470
+ key_wrap_schema: options[:key_wrap_schema],
471
+ content_encryption_schema: options[:content_encryption_schema]
472
+ )
473
+ end
474
+ end
475
+
476
+ def extract_key_provider(options)
477
+ if options[:key_provider]
478
+ options[:key_provider]
479
+ elsif options[:encryption_key]
480
+ DefaultKeyProvider.new(options)
481
+ else
482
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
483
+ raise ArgumentError, msg
484
+ end
485
+ end
486
+
487
+ def envelope_options(params)
488
+ location = params.delete(:envelope_location) || @envelope_location
489
+ suffix = params.delete(:instruction_file_suffix)
490
+ if suffix
491
+ [:instruction_file, suffix]
492
+ else
493
+ [location, @instruction_file_suffix]
494
+ end
495
+ end
496
+
497
+ def extract_location(options)
498
+ location = options[:envelope_location] || :metadata
499
+ if [:metadata, :instruction_file].include?(location)
500
+ location
501
+ else
502
+ msg = ':envelope_location must be :metadata or :instruction_file '\
503
+ "got #{location.inspect}"
504
+ raise ArgumentError, msg
505
+ end
506
+ end
507
+
508
+ def extract_suffix(options)
509
+ suffix = options[:instruction_file_suffix] || '.instruction'
510
+ if suffix.is_a? String
511
+ suffix
512
+ else
513
+ msg = ':instruction_file_suffix must be a String'
514
+ raise ArgumentError, msg
515
+ end
516
+ end
517
+
518
+ def kms_any_cmk_mode(params)
519
+ if !params[:kms_allow_decrypt_with_any_cmk].nil?
520
+ params.delete(:kms_allow_decrypt_with_any_cmk)
521
+ else
522
+ @kms_allow_decrypt_with_any_cmk
523
+ end
524
+ end
525
+
526
+ def extract_security_profile(options)
527
+ validate_security_profile(options[:security_profile])
528
+ end
529
+
530
+ def security_profile_from_params(params)
531
+ security_profile =
532
+ if !params[:security_profile].nil?
533
+ params.delete(:security_profile)
534
+ else
535
+ @security_profile
536
+ end
537
+ validate_security_profile(security_profile)
538
+ end
539
+
540
+ def validate_security_profile(security_profile)
541
+ unless SUPPORTED_SECURITY_PROFILES.include? security_profile
542
+ raise ArgumentError, "Unsupported security profile: :#{security_profile}. " \
543
+ "Please provide one of: #{SUPPORTED_SECURITY_PROFILES.map { |s| ":#{s}" }.join(', ')}"
544
+ end
545
+ if security_profile == :v2_and_legacy && !@warned_about_legacy
546
+ @warned_about_legacy = true
547
+ warn(
548
+ 'The S3 Encryption Client is configured to read encrypted objects ' \
549
+ "with legacy encryption modes. If you don't have objects " \
550
+ 'encrypted with these legacy modes, you should disable support ' \
551
+ 'for them to enhance security.'
552
+ )
553
+ end
554
+ security_profile
555
+ end
556
+ end
557
+ end
558
+ end
559
+ end