aws-sdk-s3 1.207.0 → 1.208.0

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-s3/client.rb +1 -1
  5. data/lib/aws-sdk-s3/customizations.rb +1 -0
  6. data/lib/aws-sdk-s3/encryption/client.rb +2 -2
  7. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
  8. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
  9. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -0
  10. data/lib/aws-sdk-s3/encryptionV2/client.rb +98 -23
  11. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
  12. data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
  13. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
  14. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
  15. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
  16. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +8 -0
  17. data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
  18. data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
  19. data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
  20. data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
  21. data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
  22. data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
  23. data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
  24. data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
  25. data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
  26. data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
  27. data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
  28. data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
  29. data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
  30. data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
  31. data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
  32. data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
  33. data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
  34. data/lib/aws-sdk-s3.rb +1 -1
  35. metadata +17 -1
@@ -0,0 +1,885 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Aws
6
+ module S3
7
+ # Provides an encryption client that encrypts and decrypts data client-side,
8
+ # storing the encrypted data in Amazon S3. The `EncryptionV3::Client` (V3 Client)
9
+ # provides improved security over the `EncryptionV2::Client` (V2 Client)
10
+ # through key commitment. You can use the V3 Client to continue decrypting
11
+ # objects encrypted by V2 by setting security_profile: :v3_and_legacy.
12
+ # The latest V2 Client also supports reading and decrypting objects
13
+ # encrypted by the V3 Client.
14
+ #
15
+ # This client uses a process called "envelope encryption". Your private
16
+ # encryption keys and your data's plain-text are **never** sent to
17
+ # Amazon S3. **If you lose you encryption keys, you will not be able to
18
+ # decrypt your data.**
19
+ #
20
+ # ## Key Commitment
21
+ #
22
+ # Key commitment (also known as robustness) is a security property that
23
+ # guarantees that each ciphertext can be decrypted to only a single plaintext.
24
+ # This prevents sophisticated attacks where a ciphertext could theoretically
25
+ # decrypt to different plaintexts under different keys.
26
+ #
27
+ # The V3 client encrypts with key commitment by default using the
28
+ # `:alg_aes_256_gcm_hkdf_sha512_commit_key` algorithm. Key commitment adds
29
+ # approximately 32 bytes to each encrypted object and slightly increases
30
+ # processing time, but significantly enhances security.
31
+ #
32
+ # ## Envelope Encryption Overview
33
+ #
34
+ # The goal of envelope encryption is to combine the performance of
35
+ # fast symmetric encryption while maintaining the secure key management
36
+ # that asymmetric keys provide.
37
+ #
38
+ # A one-time-use symmetric key (envelope key) is generated client-side.
39
+ # This is used to encrypt the data client-side. This key is then
40
+ # encrypted by your master key and stored alongside your data in Amazon
41
+ # S3.
42
+ #
43
+ # When accessing your encrypted data with the encryption client,
44
+ # the encrypted envelope key is retrieved and decrypted client-side
45
+ # with your master key. The envelope key is then used to decrypt the
46
+ # data client-side.
47
+ #
48
+ # One of the benefits of envelope encryption is that if your master key
49
+ # is compromised, you have the option of just re-encrypting the stored
50
+ # envelope symmetric keys, instead of re-encrypting all of the
51
+ # data in your account.
52
+ #
53
+ # ## Basic Usage
54
+ #
55
+ # The encryption client requires an {Aws::S3::Client}. If you do not
56
+ # provide a `:client`, then a client will be constructed for you.
57
+ #
58
+ # require 'openssl'
59
+ # key = OpenSSL::PKey::RSA.new(1024)
60
+ #
61
+ # # encryption client
62
+ # s3 = Aws::S3::EncryptionV3::Client.new(
63
+ # encryption_key: key,
64
+ # key_wrap_schema: :rsa_oaep_sha1 # the key_wrap_schema must be rsa_oaep_sha1 for asymmetric keys
65
+ # )
66
+ #
67
+ # # round-trip an object, encrypted/decrypted locally
68
+ # s3.put_object(bucket:'aws-sdk', key:'secret', body:'handshake')
69
+ # s3.get_object(bucket:'aws-sdk', key:'secret').body.read
70
+ # #=> 'handshake'
71
+ #
72
+ # # reading encrypted object without the encryption client
73
+ # # results in the getting the cipher text
74
+ # Aws::S3::Client.new.get_object(bucket:'aws-sdk', key:'secret').body.read
75
+ # #=> "... cipher text ..."
76
+ #
77
+ # ## Required Configuration
78
+ #
79
+ # You must configure the following:
80
+ #
81
+ # * a key or key provider - See the Keys section below. The key provided determines
82
+ # the key wrapping schema(s) supported for both encryption and decryption.
83
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
84
+ #
85
+ # The following have defaults and are optional:
86
+ #
87
+ # * `content_encryption_schema` - Defaults to `:alg_aes_256_gcm_hkdf_sha512_commit_key`
88
+ # * `security_profile` - Defaults to `:v3`. Set to `:v3_and_legacy` to read V2-encrypted objects.
89
+ # * `commitment_policy` - Defaults to `:require_encrypt_require_decrypt` (most secure)
90
+ #
91
+ # ## Keys
92
+ #
93
+ # For client-side encryption to work, you must provide one of the following:
94
+ #
95
+ # * An encryption key
96
+ # * A {KeyProvider}
97
+ # * A KMS encryption key id
98
+ #
99
+ # Additionally, the key wrapping schema must agree with the type of the key:
100
+ # * :aes_gcm: An AES encryption key or a key provider.
101
+ # * :rsa_oaep_sha1: An RSA encryption key or key provider.
102
+ # * :kms_context: A KMS encryption key id
103
+ #
104
+ # ### An Encryption Key
105
+ #
106
+ # You can pass a single encryption key. This is used as a master key
107
+ # encrypting and decrypting all object keys.
108
+ #
109
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key - used with `key_wrap_schema: :aes_gcm`
110
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair - used with `key_wrap_schema: :rsa_oaep_sha1`
111
+ #
112
+ # s3 = Aws::S3::EncryptionV3::Client.new(
113
+ # encryption_key: key,
114
+ # key_wrap_schema: :aes_gcm # or :rsa_oaep_sha1 if using RSA
115
+ # )
116
+ #
117
+ # ### Key Provider
118
+ #
119
+ # Alternatively, you can use a {KeyProvider}. A key provider makes
120
+ # it easy to work with multiple keys and simplifies key rotation.
121
+ #
122
+ # ### KMS Encryption Key Id
123
+ #
124
+ # If you pass the id of an AWS Key Management Service (KMS) key and
125
+ # use :kms_content for the key_wrap_schema, then KMS will be used to
126
+ # generate, encrypt and decrypt object keys.
127
+ #
128
+ # # keep track of the kms key id
129
+ # kms = Aws::KMS::Client.new
130
+ # key_id = kms.create_key.key_metadata.key_id
131
+ #
132
+ # Aws::S3::EncryptionV3::Client.new(
133
+ # kms_key_id: key_id,
134
+ # kms_client: kms,
135
+ # key_wrap_schema: :kms_context
136
+ # )
137
+ #
138
+ # ## Custom Key Providers
139
+ #
140
+ # A {KeyProvider} is any object that responds to:
141
+ #
142
+ # * `#encryption_materials`
143
+ # * `#key_for(materials_description)`
144
+ #
145
+ # Here is a trivial implementation of an in-memory key provider.
146
+ # This is provided as a demonstration of the key provider interface,
147
+ # and should not be used in production:
148
+ #
149
+ # class KeyProvider
150
+ #
151
+ # def initialize(default_key_name, keys)
152
+ # @keys = keys
153
+ # @encryption_materials = Aws::S3::EncryptionV3::Materials.new(
154
+ # key: @keys[default_key_name],
155
+ # description: JSON.dump(key: default_key_name),
156
+ # )
157
+ # end
158
+ #
159
+ # attr_reader :encryption_materials
160
+ #
161
+ # def key_for(matdesc)
162
+ # key_name = JSON.parse(matdesc)['key']
163
+ # if key = @keys[key_name]
164
+ # key
165
+ # else
166
+ # raise "encryption key not found for: #{matdesc.inspect}"
167
+ # end
168
+ # end
169
+ # end
170
+ #
171
+ # Given the above key provider, you can create an encryption client that
172
+ # chooses the key to use based on the materials description stored with
173
+ # the encrypted object. This makes it possible to use multiple keys
174
+ # and simplifies key rotation.
175
+ #
176
+ # # uses "new-key" for encrypting objects, uses either for decrypting
177
+ # keys = KeyProvider.new('new-key', {
178
+ # "old-key" => Base64.decode64("kM5UVbhE/4rtMZJfsadYEdm2vaKFsmV2f5+URSeUCV4="),
179
+ # "new-key" => Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
180
+ # }),
181
+ #
182
+ # # chooses the key based on the materials description stored
183
+ # # with the encrypted object
184
+ # s3 = Aws::S3::EncryptionV3::Client.new(
185
+ # key_provider: keys,
186
+ # key_wrap_schema: :aes_gcm # or :rsa_oaep_sha1 for RSA keys
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::EncryptionV3::Client.new(
212
+ # encryption_key: your_key,
213
+ # key_wrap_schema: :aes_gcm,
214
+ # envelope_location: :metadata
215
+ # )
216
+ #
217
+ # # store envelope in a separate object
218
+ # s3 = Aws::S3::EncryptionV3::Client.new(
219
+ # encryption_key: your_key,
220
+ # key_wrap_schema: :aes_gcm,
221
+ # envelope_location: :instruction_file,
222
+ # instruction_file_suffix: '.instruction' # default
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
+ # ## Commitment Policies Explained
230
+ #
231
+ # * `:forbid_encrypt_allow_decrypt` - Encrypts without key commitment (for
232
+ # backward compatibility with systems that have not been updated), but can decrypt
233
+ # objects with or without commitment. Use if you are not sure that all readers
234
+ # can decrypt objects encrypted with key commitment.
235
+ #
236
+ # * `:require_encrypt_allow_decrypt` - Encrypts with key commitment, can decrypt
237
+ # objects with or without commitment. Use once all readers
238
+ # can decrypt objects encrypted with key commitment.
239
+ #
240
+ # * `:require_encrypt_require_decrypt` - Encrypts with key commitment, can only
241
+ # decrypt objects with key commitment. **Recommended for new applications and
242
+ # after migrations are complete.** This is the default.
243
+ #
244
+ module EncryptionV3
245
+ class Client
246
+ ##= ../specification/s3-encryption/client.md#aws-sdk-compatibility
247
+ ##= type=implication
248
+ ##% The S3EC MUST provide a different set of configuration options than the conventional S3 client.
249
+
250
+ REQUIRED_PARAMS = [:key_wrap_schema].freeze
251
+
252
+ OPTIONAL_PARAMS = [
253
+ :kms_key_id,
254
+ :kms_client,
255
+ :key_provider,
256
+ :encryption_key,
257
+ :envelope_location,
258
+ ##= ../specification/s3-encryption/client.md#instruction-file-configuration
259
+ ##% In this case, the Instruction File Configuration SHOULD be optional, such that its default configuration is used when none is provided.
260
+ :instruction_file_suffix,
261
+ ##= ../specification/s3-encryption/client.md#encryption-algorithm
262
+ ##% The S3EC MUST support configuration of the encryption algorithm (or algorithm suite) during its initialization.
263
+ :content_encryption_schema,
264
+ :security_profile,
265
+ ##= ../specification/s3-encryption/client.md#key-commitment
266
+ ##% The S3EC MUST support configuration of the [Key Commitment policy](./key-commitment.md) during its initialization.
267
+ :commitment_policy
268
+ ].freeze
269
+ SUPPORTED_COMMITMENT_POLICIES = %i[
270
+ forbid_encrypt_allow_decrypt
271
+ require_encrypt_allow_decrypt
272
+ require_encrypt_require_decrypt
273
+ ].freeze
274
+
275
+ ##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
276
+ ##% The S3EC MUST support the option to enable or disable legacy wrapping algorithms.
277
+ ##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
278
+ ##% The S3EC MUST support the option to enable or disable legacy unauthenticated modes (content encryption algorithms).
279
+ SUPPORTED_SECURITY_PROFILES = %i[v3 v3_and_legacy].freeze
280
+
281
+ ##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
282
+ ##% The option to enable legacy unauthenticated modes MUST be set to false by default.
283
+ ##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
284
+ ##% The option to enable legacy wrapping algorithms MUST be set to false by default.
285
+ DEFAULT_SECURITY_PROFILES = :v3
286
+ DEFAULT_COMMITMENT_POLICIES = :require_encrypt_require_decrypt
287
+ DEFAULT_CONTENT_ENCRYPTION_SCHEMA = :alg_aes_256_gcm_hkdf_sha512_commit_key
288
+
289
+ extend Deprecations
290
+ extend Forwardable
291
+ def_delegators :@client, :config, :delete_object, :head_object, :build_request
292
+
293
+ # Creates a new encryption client.
294
+ #
295
+ # ## Required Configuration
296
+ #
297
+ # * a key or key provider - The key provided also determines the key wrapping
298
+ # schema(s) supported for both encryption and decryption.
299
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
300
+ #
301
+ # ## Optional Configuration (with defaults)
302
+ #
303
+ # * `content_encryption_schema` - Defaults to `:alg_aes_256_gcm_hkdf_sha512_commit_key`
304
+ # * `security_profile` - Defaults to `:v3`. Set to `:v3_and_legacy` to read V2-encrypted objects.
305
+ # * `commitment_policy` - Defaults to `:require_encrypt_require_decrypt` (most secure)
306
+ #
307
+ # To configure the key you must provide one of the following set of options:
308
+ #
309
+ # * `:encryption_key`
310
+ # * `:kms_key_id`
311
+ # * `:key_provider`
312
+ #
313
+ # You may also pass any other options accepted by `Client#initialize`.
314
+ #
315
+ # @option options [S3::Client] :client A basic S3 client that is used
316
+ # to make api calls. If a `:client` is not provided, a new {S3::Client}
317
+ # will be constructed.
318
+ #
319
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key The master
320
+ # key to use for encrypting/decrypting all objects.
321
+ #
322
+ # @option options [String] :kms_key_id When you provide a `:kms_key_id`,
323
+ # then AWS Key Management Service (KMS) will be used to manage the
324
+ # object encryption keys. By default a {KMS::Client} will be
325
+ # constructed for KMS API calls. Alternatively, you can provide
326
+ # your own via `:kms_client`. To only support decryption/reads, you may
327
+ # provide `:allow_decrypt_with_any_cmk` which will use
328
+ # the implicit CMK associated with the data during reads but will
329
+ # not allow you to encrypt/write objects with this client.
330
+ #
331
+ # @option options [#key_for] :key_provider Any object that responds
332
+ # to `#key_for`. This method should accept a materials description
333
+ # JSON document string and return return an encryption key.
334
+ #
335
+ # @option options [required, Symbol] :key_wrap_schema The Key wrapping
336
+ # schema to be used. It must match the type of key configured.
337
+ # Must be one of the following:
338
+ #
339
+ # * :kms_context (Must provide kms_key_id)
340
+ # * :aes_gcm (Must provide an AES (string) key)
341
+ # * :rsa_oaep_sha1 (Must provide an RSA key)
342
+ #
343
+ # @option options [Symbol] :content_encryption_schema (:alg_aes_256_gcm_hkdf_sha512_commit_key)
344
+ # The content encryption algorithm to use. Defaults to the V3 algorithm with key commitment.
345
+ #
346
+ # @option options [Symbol] :security_profile (:v3)
347
+ # Determines the support for reading objects written using older
348
+ # encryption schemas. Must be one of the following:
349
+ #
350
+ # * :v3 - Only reads V3-encrypted objects (default, most secure)
351
+ # * :v3_and_legacy - Enables reading of V2-encrypted objects
352
+ #
353
+ # @option options [Symbol] :commitment_policy (:require_encrypt_require_decrypt)
354
+ # Determines support for key commitment. Must be one of the following:
355
+ #
356
+ # * :forbid_encrypt_allow_decrypt - Does not encrypt with key commitment,
357
+ # can decrypt with or without. Use only for specific compatibility needs.
358
+ # * :require_encrypt_allow_decrypt - Encrypts with key commitment, can
359
+ # decrypt with or without.
360
+ # * :require_encrypt_require_decrypt - Encrypts with key commitment, only
361
+ # decrypts objects with key commitment (default, most secure)
362
+ #
363
+ # @option options [Symbol] :envelope_location (:metadata) Where to
364
+ # store the envelope encryption keys. By default, the envelope is
365
+ # stored with the encrypted object. If you pass `:instruction_file`,
366
+ # then the envelope is stored in a separate object in Amazon S3.
367
+ #
368
+ # @option options [String] :instruction_file_suffix ('.instruction')
369
+ # When `:envelope_location` is `:instruction_file` then the
370
+ # instruction file uses the object key with this suffix appended.
371
+ #
372
+ # @option options [KMS::Client] :kms_client A default {KMS::Client}
373
+ # is constructed when using KMS to manage encryption keys.
374
+ #
375
+ def initialize(options = {})
376
+ validate_params(options)
377
+ ##= ../specification/s3-encryption/client.md#wrapped-s3-client-s
378
+ ##% The S3EC MUST support the option to provide an SDK S3 client instance during its initialization.
379
+ @client = extract_client(options)
380
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
381
+ ##% Instruction File writes MUST be optionally configured during client creation or on each PutObject request.
382
+ ##= ../specification/s3-encryption/client.md#instruction-file-configuration
383
+ ##% The S3EC MAY support the option to provide Instruction File Configuration during its initialization.
384
+ ##= ../specification/s3-encryption/client.md#instruction-file-configuration
385
+ ##% If the S3EC in a given language supports Instruction Files, then it MUST accept Instruction File Configuration during its initialization.
386
+ @envelope_location = extract_location(options)
387
+ @instruction_file_suffix = extract_suffix(options)
388
+ @kms_allow_decrypt_with_any_cmk =
389
+ options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
390
+ @commitment_policy = extract_commitment_policy(options)
391
+ @security_profile = extract_security_profile(options)
392
+
393
+ ##= ../specification/s3-encryption/client.md#key-commitment
394
+ ##% The S3EC MUST validate the configured Encryption Algorithm against the provided key commitment policy.
395
+ if @commitment_policy != :require_encrypt_require_decrypt
396
+ new_options = options.merge(
397
+ {
398
+ security_profile: security_profile_to_v2(@security_profile),
399
+ ##= ../specification/s3-encryption/client.md#key-commitment
400
+ ##% If the configured Encryption Algorithm is incompatible with the key commitment policy, then it MUST throw an exception.
401
+ content_encryption_schema: if @commitment_policy == :forbid_encrypt_allow_decrypt
402
+ options[:content_encryption_schema]
403
+ else
404
+ # assert @commitment_policy = :require_encrypt_allow_decrypt
405
+ # In this case the v2_cipher_provider is only used for decrypt
406
+ :aes_gcm_no_padding
407
+ end
408
+ }
409
+ )
410
+ @v2_cipher_provider = build_v2_cipher_provider_for_decrypt(new_options)
411
+ # In this case the v3 cipher is only used for decrypt.
412
+ @v3_cipher_provider = build_cipher_provider(options.reject { |k, _| k == :content_encryption_schema })
413
+ @key_provider = @v2_cipher_provider.key_provider if @v2_cipher_provider.is_a?(DefaultCipherProvider)
414
+ else
415
+ @v3_cipher_provider = build_cipher_provider(options)
416
+ @key_provider = @v3_cipher_provider.key_provider if @v3_cipher_provider.is_a?(DefaultCipherProvider)
417
+ end
418
+ end
419
+
420
+ # @return [S3::Client]
421
+ attr_reader :client
422
+
423
+ # @return [KeyProvider, nil] Returns `nil` if you are using
424
+ # AWS Key Management Service (KMS).
425
+ attr_reader :key_provider
426
+
427
+ # @return [Symbol] Determines the support for reading objects written
428
+ # using older key wrap or content encryption schemas.
429
+ attr_reader :commitment_policy
430
+
431
+ # @return [Boolean] If true the provided KMS key_id will not be used
432
+ # during decrypt, allowing decryption with the key_id from the object.
433
+ attr_reader :kms_allow_decrypt_with_any_cmk
434
+
435
+ # @return [Symbol<:metadata, :instruction_file>]
436
+ attr_reader :envelope_location
437
+
438
+ # @return [String] When {#envelope_location} is `:instruction_file`,
439
+ # the envelope is stored in the object with the object key suffixed
440
+ # by this string.
441
+ attr_reader :instruction_file_suffix
442
+
443
+ ##= ../specification/s3-encryption/client.md#aws-sdk-compatibility
444
+ ##= type=implication
445
+ ##% The S3EC MUST adhere to the same interface for API operations as the conventional AWS SDK S3 client.
446
+ ##= ../specification/s3-encryption/client.md#aws-sdk-compatibility
447
+ ##= type=exception
448
+ ##= reason=The ruby client does not support other operations
449
+ ##% The S3EC SHOULD support invoking operations unrelated to client-side encryption e.g.
450
+
451
+ # Uploads an object to Amazon S3, encrypting data client-side.
452
+ # See {S3::Client#put_object} for documentation on accepted
453
+ # request parameters.
454
+ # @option params [Hash] :kms_encryption_context Additional encryption
455
+ # context to use with KMS. Applies only when KMS is used. In order
456
+ # to decrypt the object you will need to provide the identical
457
+ # :kms_encryption_context to `get_object`.
458
+ # @option (see S3::Client#put_object)
459
+ # @return (see S3::Client#put_object)
460
+ # @see S3::Client#put_object
461
+ def put_object(params = {})
462
+ kms_encryption_context = params.delete(:kms_encryption_context)
463
+ ##= ../specification/s3-encryption/client.md#required-api-operations
464
+ ##% - PutObject MUST be implemented by the S3EC.
465
+ req = @client.build_request(:put_object, params)
466
+ ##= ../specification/s3-encryption/client.md#required-api-operations
467
+ ##% - PutObject MUST encrypt its input data before it is uploaded to S3.
468
+ req.handlers.add(EncryptHandler, priority: 95)
469
+ req.context[:encryption] = {
470
+ cipher_provider:
471
+ if @commitment_policy == :forbid_encrypt_allow_decrypt
472
+ ##= ../specification/s3-encryption/key-commitment.md#commitment-policy
473
+ ##% When the commitment policy is FORBID_ENCRYPT_ALLOW_DECRYPT, the S3EC MUST NOT encrypt using an algorithm suite which supports key commitment.
474
+ @v2_cipher_provider
475
+ else
476
+ ##= ../specification/s3-encryption/key-commitment.md#commitment-policy
477
+ ##% When the commitment policy is REQUIRE_ENCRYPT_ALLOW_DECRYPT, the S3EC MUST only encrypt using an algorithm suite which supports key commitment.
478
+ ##= ../specification/s3-encryption/key-commitment.md#commitment-policy
479
+ ##% When the commitment policy is REQUIRE_ENCRYPT_REQUIRE_DECRYPT, the S3EC MUST only encrypt using an algorithm suite which supports key commitment.
480
+ @v3_cipher_provider
481
+ end,
482
+ envelope_location: @envelope_location,
483
+ instruction_file_suffix: @instruction_file_suffix,
484
+ kms_encryption_context: kms_encryption_context
485
+ }
486
+ Aws::Plugins::UserAgent.metric('S3_CRYPTO_V3') do
487
+ req.send_request
488
+ end
489
+ end
490
+
491
+ # Gets an object from Amazon S3, decrypting data locally.
492
+ # See {S3::Client#get_object} for documentation on accepted
493
+ # request parameters.
494
+ # Warning: If you provide a block to get_object or set the request
495
+ # parameter :response_target to a Proc, then read the entire object to the
496
+ # end before you start using the decrypted data. This is to verify that
497
+ # the object has not been modified since it was encrypted.
498
+ #
499
+ # @option options [Symbol] :security_profile
500
+ # Determines the support for reading objects written using older
501
+ # encryption schemas. Overrides the value set on client construction if provided.
502
+ # Must be one of the following:
503
+ #
504
+ # * :v3 - Only reads V3-encrypted objects (most secure)
505
+ # * :v3_and_legacy - Enables reading of V2-encrypted objects
506
+ # @option params [String] :instruction_file_suffix The suffix
507
+ # used to find the instruction file containing the encryption
508
+ # envelope. You should not set this option when the envelope
509
+ # is stored in the object metadata. Defaults to
510
+ # {#instruction_file_suffix}.
511
+ # @option params [Hash] :kms_encryption_context Additional encryption
512
+ # context to use with KMS. Applies only when KMS is used.
513
+ # @option options [Boolean] :kms_allow_decrypt_with_any_cmk (false)
514
+ # By default the KMS CMK ID (kms_key_id) will be used during decrypt
515
+ # and will fail if there is a mismatch. Setting this to true
516
+ # will use the implicit CMK associated with the data.
517
+ # @option (see S3::Client#get_object)
518
+ # @return (see S3::Client#get_object)
519
+ # @see S3::Client#get_object
520
+ # @note The `:range` request parameter is not supported.
521
+ def get_object(params = {}, &block)
522
+ raise NotImplementedError, '#get_object with :range not supported' if params[:range]
523
+
524
+ envelope_location, instruction_file_suffix = envelope_options(params)
525
+ kms_encryption_context = params.delete(:kms_encryption_context)
526
+ kms_any_cmk_mode = kms_any_cmk_mode(params)
527
+ commitment_policy = commitment_policy_from_params(params)
528
+
529
+ ##= ../specification/s3-encryption/client.md#required-api-operations
530
+ ##% - GetObject MUST be implemented by the S3EC.
531
+ req = @client.build_request(:get_object, params)
532
+ ##= ../specification/s3-encryption/client.md#required-api-operations
533
+ ##% - GetObject MUST decrypt data received from the S3 server and return it as plaintext.
534
+ req.handlers.add(DecryptHandler)
535
+ req.context[:encryption] = {
536
+ v3_cipher_provider: @v3_cipher_provider,
537
+ envelope_location: envelope_location,
538
+ instruction_file_suffix: instruction_file_suffix,
539
+ kms_encryption_context: kms_encryption_context,
540
+ kms_allow_decrypt_with_any_cmk: kms_any_cmk_mode,
541
+ commitment_policy: commitment_policy
542
+ }.tap do |hash|
543
+ if commitment_policy != :require_encrypt_require_decrypt
544
+ security_profile = security_profile_from_params(params)
545
+ hash[:security_profile] = security_profile_to_v2(security_profile)
546
+ hash[:cipher_provider] = @v2_cipher_provider
547
+ end
548
+ end
549
+ Aws::Plugins::UserAgent.metric('S3_CRYPTO_V3') do
550
+ req.send_request(target: block)
551
+ end
552
+ end
553
+
554
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
555
+ ##= type=exception
556
+ ##= reason=This has never been supported in Ruby
557
+ ##% The S3EC MAY support re-encryption/key rotation via Instruction Files.
558
+ ##= ../specification/s3-encryption/decryption.md#ranged-gets
559
+ ##= type=exception
560
+ ##= reason=This has never been supported in Ruby
561
+ ##% The S3EC MAY support the "range" parameter on GetObject which specifies a subset of bytes to download and decrypt.
562
+ ##= ../specification/s3-encryption/decryption.md#ranged-gets
563
+ ##= type=exception
564
+ ##= reason=This has never been supported in Ruby
565
+ ##% If the S3EC supports Ranged Gets, the S3EC MUST adjust the customer-provided range to include the beginning and end of the cipher blocks for the given range.
566
+ ##= ../specification/s3-encryption/decryption.md#ranged-gets
567
+ ##= type=exception
568
+ ##= reason=This has never been supported in Ruby
569
+ ##% If the object was encrypted with ALG_AES_256_GCM_IV12_TAG16_NO_KDF, then ALG_AES_256_CTR_IV16_TAG16_NO_KDF MUST be used to decrypt the range of the object.
570
+ ##= ../specification/s3-encryption/decryption.md#ranged-gets
571
+ ##= type=exception
572
+ ##= reason=This has never been supported in Ruby
573
+ ##% If the object was encrypted with ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY, then ALG_AES_256_CTR_HKDF_SHA512_COMMIT_KEY MUST be used to decrypt the range of the object.
574
+ ##= ../specification/s3-encryption/decryption.md#ranged-gets
575
+ ##= type=exception
576
+ ##= reason=This has never been supported in Ruby
577
+ ##% If the GetObject response contains a range, but the GetObject request does not contain a range, the S3EC MUST throw an exception.
578
+
579
+ private
580
+
581
+ def build_cipher_provider(options)
582
+ if options[:kms_key_id]
583
+ KmsCipherProvider.new(
584
+ kms_key_id: options[:kms_key_id],
585
+ kms_client: kms_client(options),
586
+ key_wrap_schema: options[:key_wrap_schema],
587
+ content_encryption_schema: options[:content_encryption_schema]
588
+ )
589
+ else
590
+ ##= ../specification/s3-encryption/client.md#cryptographic-materials
591
+ ##% The S3EC MAY accept key material directly.
592
+ key_provider = extract_key_provider(options)
593
+ DefaultCipherProvider.new(
594
+ key_provider: key_provider,
595
+ key_wrap_schema: options[:key_wrap_schema],
596
+ content_encryption_schema: options[:content_encryption_schema]
597
+ )
598
+ end
599
+ end
600
+
601
+ def build_v2_cipher_provider_for_decrypt(options)
602
+ if options[:kms_key_id]
603
+ Aws::S3::EncryptionV2::KmsCipherProvider.new(
604
+ kms_key_id: options[:kms_key_id],
605
+ kms_client: kms_client(options),
606
+ key_wrap_schema: options[:key_wrap_schema],
607
+ content_encryption_schema: options[:content_encryption_schema]
608
+ )
609
+ else
610
+ # Create V2 key provider explicitly for proper namespace consistency
611
+ key_provider = if options[:key_provider]
612
+ options[:key_provider]
613
+ elsif options[:encryption_key]
614
+ Aws::S3::EncryptionV2::DefaultKeyProvider.new(options)
615
+ else
616
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
617
+ raise ArgumentError, msg
618
+ end
619
+ Aws::S3::EncryptionV2::DefaultCipherProvider.new(
620
+ key_provider: key_provider,
621
+ key_wrap_schema: options[:key_wrap_schema],
622
+ content_encryption_schema: options[:content_encryption_schema]
623
+ )
624
+ end
625
+ end
626
+
627
+ # Validate required parameters exist and don't conflict.
628
+ # The cek_alg and wrap_alg are passed on to the CipherProviders
629
+ # and further validated there
630
+ def validate_params(options)
631
+ unless (missing_params = REQUIRED_PARAMS - options.keys).empty?
632
+ raise ArgumentError, 'Missing required parameter(s): '\
633
+ "#{missing_params.map { |s| ":#{s}" }.join(', ')}"
634
+ end
635
+
636
+ wrap_alg = options[:key_wrap_schema]
637
+
638
+ # validate that the wrap alg matches the type of key given
639
+ case wrap_alg
640
+ when :kms_context
641
+ raise ArgumentError, 'You must provide :kms_key_id to use :kms_context' unless options[:kms_key_id]
642
+ end
643
+ end
644
+
645
+ def extract_client(options)
646
+ ##= ../specification/s3-encryption/client.md#wrapped-s3-client-s
647
+ ##= type=exception
648
+ ##= reason=this would be a breaking change to ruby
649
+ ##% The S3EC MUST NOT support use of S3EC as the provided S3 client during its initialization; it MUST throw an exception in this case.
650
+ options[:client] || begin
651
+ ##= ../specification/s3-encryption/client.md#inherited-sdk-configuration
652
+ ##% The S3EC MAY support directly configuring the wrapped SDK clients through its initialization.
653
+ ##= ../specification/s3-encryption/client.md#inherited-sdk-configuration
654
+ ##% For example, the S3EC MAY accept a credentials provider instance during its initialization.
655
+ ##= ../specification/s3-encryption/client.md#inherited-sdk-configuration
656
+ ##% If the S3EC accepts SDK client configuration, the configuration MUST be applied to all wrapped S3 clients.
657
+ S3::Client.new(extract_sdk_options(options))
658
+ end
659
+ end
660
+
661
+ def kms_client(options)
662
+ options[:kms_client] || (@kms_client ||=
663
+ KMS::Client.new(
664
+ # extract the region and credentials first, if they are not configured, then getting them from an existing client is faster
665
+ ##= ../specification/s3-encryption/client.md#inherited-sdk-configuration
666
+ ##% If the S3EC accepts SDK client configuration, the configuration MUST be applied to all wrapped SDK clients including the KMS client.
667
+ {
668
+ region: @client.config.region,
669
+ credentials: @client.config.credentials
670
+ }.merge(extract_sdk_options(options))
671
+ )
672
+ )
673
+ end
674
+
675
+ def extract_sdk_options(options)
676
+ options = options.dup
677
+ OPTIONAL_PARAMS.each { |p| options.delete(p) }
678
+ REQUIRED_PARAMS.each { |p| options.delete(p) }
679
+ options
680
+ end
681
+
682
+ def extract_key_provider(options)
683
+ if options[:key_provider]
684
+ options[:key_provider]
685
+ elsif options[:encryption_key]
686
+ DefaultKeyProvider.new(options)
687
+ else
688
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
689
+ raise ArgumentError, msg
690
+ end
691
+ end
692
+
693
+ def envelope_options(params)
694
+ location = params.delete(:envelope_location) || @envelope_location
695
+ suffix = params.delete(:instruction_file_suffix)
696
+ if suffix
697
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
698
+ ##% The S3EC SHOULD support providing a custom Instruction File suffix on GetObject requests, regardless of whether or not re-encryption is supported.
699
+ [:instruction_file, suffix]
700
+ else
701
+ [location, @instruction_file_suffix]
702
+ end
703
+ end
704
+
705
+ def extract_location(options)
706
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#object-metadata
707
+ ##% By default, the S3EC MUST store content metadata in the S3 Object Metadata.
708
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
709
+ ##% Instruction File writes MUST NOT be enabled by default.
710
+ location = options[:envelope_location] || :metadata
711
+ if %i[metadata instruction_file].include?(location)
712
+ location
713
+ else
714
+ msg = ':envelope_location must be :metadata or :instruction_file '\
715
+ "got #{location.inspect}"
716
+ raise ArgumentError, msg
717
+ end
718
+ end
719
+
720
+ def extract_suffix(options)
721
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
722
+ ##% The default Instruction File behavior uses the same S3 object key as its associated object suffixed with ".instruction".
723
+ suffix = options[:instruction_file_suffix] || '.instruction'
724
+ if suffix.is_a? String
725
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
726
+ ##= type=exception
727
+ ##= reason=Ruby has always supported this option
728
+ ##% The S3EC MUST NOT support providing a custom Instruction File suffix on ordinary writes; custom suffixes MUST only be used during re-encryption.
729
+ suffix
730
+ else
731
+ msg = ':instruction_file_suffix must be a String'
732
+ raise ArgumentError, msg
733
+ end
734
+ end
735
+
736
+ def kms_any_cmk_mode(params)
737
+ if !params[:kms_allow_decrypt_with_any_cmk].nil?
738
+ params.delete(:kms_allow_decrypt_with_any_cmk)
739
+ else
740
+ @kms_allow_decrypt_with_any_cmk
741
+ end
742
+ end
743
+
744
+ def extract_commitment_policy(options)
745
+ validate_commitment_policy(options[:commitment_policy])
746
+ end
747
+
748
+ def commitment_policy_from_params(params)
749
+ commitment_policy =
750
+ if !params[:commitment_policy].nil?
751
+ params.delete(:commitment_policy)
752
+ else
753
+ @commitment_policy
754
+ end
755
+ validate_commitment_policy(commitment_policy)
756
+ end
757
+
758
+ def validate_commitment_policy(commitment_policy)
759
+ return DEFAULT_COMMITMENT_POLICIES if commitment_policy.nil?
760
+
761
+ unless SUPPORTED_COMMITMENT_POLICIES.include? commitment_policy
762
+ raise ArgumentError, "Unsupported security profile: :#{commitment_policy}. " \
763
+ "Please provide one of: #{SUPPORTED_COMMITMENT_POLICIES.map { |s| ":#{s}" }.join(', ')}"
764
+ end
765
+ commitment_policy
766
+ end
767
+
768
+ def extract_security_profile(options)
769
+ validate_security_profile(options[:security_profile])
770
+ end
771
+
772
+ def security_profile_from_params(params)
773
+ security_profile =
774
+ if !params[:security_profile].nil?
775
+ params.delete(:security_profile)
776
+ else
777
+ @security_profile
778
+ end
779
+ validate_security_profile(security_profile)
780
+ end
781
+
782
+ def validate_security_profile(security_profile)
783
+ return DEFAULT_SECURITY_PROFILES if security_profile.nil?
784
+
785
+ unless SUPPORTED_SECURITY_PROFILES.include? security_profile
786
+ raise ArgumentError, "Unsupported security profile: :#{security_profile}. " \
787
+ "Please provide one of: #{SUPPORTED_SECURITY_PROFILES.map { |s| ":#{s}" }.join(', ')}"
788
+ end
789
+ if security_profile == :v3_and_legacy && !@warned_about_legacy
790
+ @warned_about_legacy = true
791
+ warn(
792
+ 'The S3 Encryption Client is configured to read encrypted objects ' \
793
+ "with legacy encryption modes. If you don't have objects " \
794
+ 'encrypted with these legacy modes, you should disable support ' \
795
+ 'for them to enhance security.'
796
+ )
797
+ end
798
+ security_profile
799
+ end
800
+
801
+ def security_profile_to_v2(security_profile)
802
+ case security_profile
803
+ when :v3
804
+ :v2
805
+ when :v3_and_legacy
806
+ :v2_and_legacy
807
+ end
808
+ end
809
+ end
810
+ end
811
+ end
812
+ end
813
+
814
+ ##= ../specification/s3-encryption/client.md#cryptographic-materials
815
+ ##= type=exception
816
+ ##= reason=the ruby client does not use keyrings
817
+ ##% The S3EC MUST accept either one CMM or one Keyring instance upon initialization.
818
+ ##= ../specification/s3-encryption/client.md#cryptographic-materials
819
+ ##= type=exception
820
+ ##= reason=the ruby client does not use keyrings
821
+ ##% If both a CMM and a Keyring are provided, the S3EC MUST throw an exception.
822
+ ##= ../specification/s3-encryption/client.md#cryptographic-materials
823
+ ##= type=exception
824
+ ##= reason=the ruby client does not use keyrings
825
+ ##% When a Keyring is provided, the S3EC MUST create an instance of the DefaultCMM using the provided Keyring.
826
+ ##= ../specification/s3-encryption/client.md#enable-delayed-authentication
827
+ ##= type=exception
828
+ ##= reason=the ruby client does not support delayed authentication
829
+ ##% The S3EC MUST support the option to enable or disable Delayed Authentication mode.
830
+ ##= ../specification/s3-encryption/client.md#enable-delayed-authentication
831
+ ##= type=exception
832
+ ##= reason=the ruby client does not support delayed authentication
833
+ ##% Delayed Authentication mode MUST be set to false by default.
834
+ ##= ../specification/s3-encryption/client.md#enable-delayed-authentication
835
+ ##= type=exception
836
+ ##= reason=the ruby client does not support delayed authentication
837
+ ##% When enabled, the S3EC MAY release plaintext from a stream which has not been authenticated.
838
+ ##= ../specification/s3-encryption/client.md#enable-delayed-authentication
839
+ ##= type=exception
840
+ ##= reason=the ruby client does not support delayed authentication
841
+ ##% When disabled the S3EC MUST NOT release plaintext from a stream which has not been authenticated.
842
+ ##= ../specification/s3-encryption/client.md#set-buffer-size
843
+ ##= type=exception
844
+ ##= reason=the ruby client does not support delayed authentication
845
+ ##% The S3EC SHOULD accept a configurable buffer size which refers to the maximum ciphertext length in bytes to store in memory when Delayed Authentication mode is disabled.
846
+ ##= ../specification/s3-encryption/client.md#set-buffer-size
847
+ ##= type=exception
848
+ ##= reason=the ruby client does not support delayed authentication
849
+ ##% If Delayed Authentication mode is enabled, and the buffer size has been set to a value other than its default, the S3EC MUST throw an exception.
850
+ ##= ../specification/s3-encryption/client.md#set-buffer-size
851
+ ##= type=exception
852
+ ##= reason=the ruby client does not support delayed authentication
853
+ ##% If Delayed Authentication mode is disabled, and no buffer size is provided, the S3EC MUST set the buffer size to a reasonable default.
854
+ ##= ../specification/s3-encryption/client.md#randomness
855
+ ##= type=exception
856
+ ##= reason=the ruby client does not support a configured source of randomness
857
+ ##% The S3EC MAY accept a source of randomness during client initialization.
858
+ ##= ../specification/s3-encryption/client.md#optional-api-operations
859
+ ##= type=exception
860
+ ##= reason=the ruby client does not support any additional S3 operations
861
+ ##% - CreateMultipartUpload MAY be implemented by the S3EC.
862
+ ##% - If implemented, CreateMultipartUpload MUST initiate a multipart upload.
863
+ ##% - UploadPart MAY be implemented by the S3EC.
864
+ ##% - UploadPart MUST encrypt each part.
865
+ ##% - Each part MUST be encrypted in sequence.
866
+ ##% - Each part MUST be encrypted using the same cipher instance for each part.
867
+ ##% - CompleteMultipartUpload MAY be implemented by the S3EC.
868
+ ##% - CompleteMultipartUpload MUST complete the multipart upload.
869
+ ##% - AbortMultipartUpload MAY be implemented by the S3EC.
870
+ ##% - AbortMultipartUpload MUST abort the multipart upload.
871
+ ##%
872
+ ##% The S3EC may provide implementations for the following S3EC-specific operation(s):
873
+ ##%
874
+ ##% - ReEncryptInstructionFile MAY be implemented by the S3EC.
875
+ ##% - ReEncryptInstructionFile MUST decrypt the instruction file's encrypted data key for the given object using the client's CMM.
876
+ ##% - ReEncryptInstructionFile MUST re-encrypt the plaintext data key with a provided keyring.
877
+ ##= ../specification/s3-encryption/client.md#required-api-operations
878
+ ##= type=exception
879
+ ##= reason=the ruby client does not support the delete operation, this would be a bending change
880
+ ##% - DeleteObject MUST be implemented by the S3EC.
881
+ ##% - DeleteObject MUST delete the given object key.
882
+ ##% - DeleteObject MUST delete the associated instruction file using the default instruction file suffix.
883
+ ##% - DeleteObjects MUST be implemented by the S3EC.
884
+ ##% - DeleteObjects MUST delete each of the given objects.
885
+ ##% - DeleteObjects MUST delete each of the corresponding instruction files using the default instruction file suffix.