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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4ff627467ee5db92a979ebf4dcbfa0072ca5bc4f939e0c3224ae0157524784d
4
- data.tar.gz: 2205c3657b3f955970d538410e0ad3bd6e468de3798f07eebdba748f5306ccc3
3
+ metadata.gz: d8d399e87c3b6a02f60b4fc2efbb7e656af4f8ef66302eec0454129bded2fdb0
4
+ data.tar.gz: 3d348a0ce7abfbc7bfd9210227df82f33d4576f321f9da5374b29f6834e62a38
5
5
  SHA512:
6
- metadata.gz: 23fd6bff03cc4b19d477effb9ab3350bf141176974de99450c25be573275c1cd25c981750cab5c8d691ffc41c21fae8db7bfb9723672b446a219066449075bc0
7
- data.tar.gz: b5a9324cf75285acd712ce6ffbfdfce32e1531c5fecc70c252de7b7c6fae379066248fcd3919a597dcf1af06d5d05aef54055eaa537f1f379083879eb7adbbc2
6
+ metadata.gz: 525a8c73faf271316eb39fad4880032687e38fc54f452b733c7f161af4e367bf142c73b7218185f5d335219940bce706a5f9884d0716a388a8289a1ea44150f4
7
+ data.tar.gz: ff3d94155efdef438f2db5f4e2a6126590924fb5b712e74981dd174f2e05a7bebcfb1c23e69f00d1aa84b0a1f20415670a630a93ef142472159ee6d8de20df3b
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  Unreleased Changes
2
2
  ------------------
3
3
 
4
+ 1.208.0 (2025-12-16)
5
+ ------------------
6
+
7
+ * Feature - Updates to the S3 Encryption Client. The V3 S3 Encryption Client now requires key committing algorithm suites by default.
8
+
4
9
  1.207.0 (2025-12-15)
5
10
  ------------------
6
11
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.207.0
1
+ 1.208.0
@@ -22283,7 +22283,7 @@ module Aws::S3
22283
22283
  tracer: tracer
22284
22284
  )
22285
22285
  context[:gem_name] = 'aws-sdk-s3'
22286
- context[:gem_version] = '1.207.0'
22286
+ context[:gem_version] = '1.208.0'
22287
22287
  Seahorse::Client::Request.new(handlers, context)
22288
22288
  end
22289
22289
 
@@ -6,6 +6,7 @@ module Aws
6
6
  autoload :BucketRegionCache, 'aws-sdk-s3/bucket_region_cache'
7
7
  autoload :Encryption, 'aws-sdk-s3/encryption'
8
8
  autoload :EncryptionV2, 'aws-sdk-s3/encryption_v2'
9
+ autoload :EncryptionV3, 'aws-sdk-s3/encryption_v3'
9
10
  autoload :FilePart, 'aws-sdk-s3/file_part'
10
11
  autoload :DefaultExecutor, 'aws-sdk-s3/default_executor'
11
12
  autoload :FileUploader, 'aws-sdk-s3/file_uploader'
@@ -6,9 +6,9 @@ module Aws
6
6
  module S3
7
7
 
8
8
  # [MAINTENANCE MODE] There is a new version of the Encryption Client.
9
- # AWS strongly recommends upgrading to the {Aws::S3::EncryptionV2::Client},
9
+ # AWS strongly recommends upgrading to the {Aws::S3::EncryptionV3::Client},
10
10
  # which provides updated data security best practices.
11
- # See documentation for {Aws::S3::EncryptionV2::Client}.
11
+ # See documentation for {Aws::S3::EncryptionV3::Client}.
12
12
  # Provides an encryption client that encrypts and decrypts data client-side,
13
13
  # storing the encrypted data in Amazon S3.
14
14
  #
@@ -16,6 +16,8 @@ module Aws
16
16
  # envelope and encryption cipher.
17
17
  def encryption_cipher
18
18
  cipher = Utils.aes_encryption_cipher(:CBC)
19
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
20
+ ##% Objects encrypted with ALG_AES_256_CBC_IV16_NO_KDF MAY use either the V1 or V2 message format version.
19
21
  envelope = {
20
22
  'x-amz-key' => encode64(encrypt(envelope_key(cipher))),
21
23
  'x-amz-iv' => encode64(envelope_iv(cipher)),
@@ -38,6 +38,8 @@ module Aws
38
38
  io = StringIO.new(io) if String === io
39
39
  context.params[:body] = IOEncrypter.new(cipher, io)
40
40
  context.params[:metadata] ||= {}
41
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
42
+ ##% - The mapkey "x-amz-unencrypted-content-length" SHOULD be present for V1 format objects.
41
43
  context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
42
44
  if context.params.delete(:content_md5)
43
45
  warn('Setting content_md5 on client side encrypted objects is deprecated')
@@ -26,6 +26,8 @@ module Aws
26
26
  end
27
27
  cipher = Utils.aes_encryption_cipher(:CBC)
28
28
  cipher.key = key_data.plaintext
29
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
30
+ ##% Objects encrypted with ALG_AES_256_CBC_IV16_NO_KDF MAY use either the V1 or V2 message format version.
29
31
  envelope = {
30
32
  'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
31
33
  'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
@@ -5,9 +5,17 @@ require 'forwardable'
5
5
  module Aws
6
6
  module S3
7
7
 
8
- REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile]
9
- SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy]
8
+ REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile].freeze
9
+ SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy].freeze
10
+ SUPPORTED_COMMITMENT_POLICIES = [:forbid_encrypt_allow_decrypt].freeze
10
11
 
12
+ # [MAINTENANCE MODE] There is a new version of the Encryption Client.
13
+ # AWS strongly recommends upgrading to the {Aws::S3::EncryptionV3::Client},
14
+ # which provides updated data security best practices.
15
+ # For migration guidance, see: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-encryption-migration-v2-v3.html
16
+ # Provides an encryption client that encrypts and decrypts data client-side,
17
+ # storing the encrypted data in Amazon S3.
18
+ #
11
19
  # Provides an encryption client that encrypts and decrypts data client-side,
12
20
  # storing the encrypted data in Amazon S3. The `EncryptionV2::Client` (V2 Client)
13
21
  # provides improved security over the `Encryption::Client` (V1 Client)
@@ -307,15 +315,29 @@ module Aws
307
315
  # @option options [KMS::Client] :kms_client A default {KMS::Client}
308
316
  # is constructed when using KMS to manage encryption keys.
309
317
  #
318
+ # @option options [Symbol] :commitment_policy (nil)
319
+ # Optional parameter for migration from V2 to V3. When set to
320
+ # :forbid_encrypt_allow_decrypt, this explicitly indicates you are
321
+ # maintaining V2 encryption behavior while preparing for migration.
322
+ # This allows the V2 client to decrypt V3-encrypted objects while
323
+ # continuing to encrypt new objects using V2 algorithms.
324
+ # Only :forbid_encrypt_allow_decrypt is supported.
325
+ # For migration guidance, see: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-encryption-migration-v2-v3.html
326
+ #
310
327
  def initialize(options = {})
311
328
  validate_params(options)
312
329
  @client = extract_client(options)
313
- @cipher_provider = cipher_provider(options)
330
+ @cipher_provider = build_cipher_provider(options)
331
+ @key_provider = @cipher_provider.key_provider if @cipher_provider.is_a?(DefaultCipherProvider)
314
332
  @envelope_location = extract_location(options)
315
333
  @instruction_file_suffix = extract_suffix(options)
316
334
  @kms_allow_decrypt_with_any_cmk =
317
335
  options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
318
336
  @security_profile = extract_security_profile(options)
337
+ @commitment_policy = extract_commitment_policy(options)
338
+ # The v3 cipher is only used for decrypt.
339
+ # Therefore any configured v2 `content_encryption_schema` is going to be incorrect.
340
+ @v3_cipher_provider = build_v3_cipher_provider_for_decrypt(options.reject { |k, _| k == :content_encryption_schema })
319
341
  end
320
342
 
321
343
  # @return [S3::Client]
@@ -341,6 +363,11 @@ module Aws
341
363
  # by this string.
342
364
  attr_reader :instruction_file_suffix
343
365
 
366
+ # @return [Symbol, nil] Optional commitment policy for V2 to V3 migration.
367
+ # When set to :forbid_encrypt_allow_decrypt, explicitly indicates
368
+ # maintaining V2 encryption behavior while preparing for migration.
369
+ attr_reader :commitment_policy
370
+
344
371
  # Uploads an object to Amazon S3, encrypting data client-side.
345
372
  # See {S3::Client#put_object} for documentation on accepted
346
373
  # request parameters.
@@ -410,6 +437,7 @@ module Aws
410
437
  req.handlers.add(DecryptHandler)
411
438
  req.context[:encryption] = {
412
439
  cipher_provider: @cipher_provider,
440
+ v3_cipher_provider: @v3_cipher_provider,
413
441
  envelope_location: envelope_location,
414
442
  instruction_file_suffix: instruction_file_suffix,
415
443
  kms_encryption_context: kms_encryption_context,
@@ -423,6 +451,50 @@ module Aws
423
451
 
424
452
  private
425
453
 
454
+ def build_cipher_provider(options)
455
+ if options[:kms_key_id]
456
+ KmsCipherProvider.new(
457
+ kms_key_id: options[:kms_key_id],
458
+ kms_client: kms_client(options),
459
+ key_wrap_schema: options[:key_wrap_schema],
460
+ content_encryption_schema: options[:content_encryption_schema]
461
+ )
462
+ else
463
+ key_provider = extract_key_provider(options)
464
+ DefaultCipherProvider.new(
465
+ key_provider: key_provider,
466
+ key_wrap_schema: options[:key_wrap_schema],
467
+ content_encryption_schema: options[:content_encryption_schema]
468
+ )
469
+ end
470
+ end
471
+
472
+ def build_v3_cipher_provider_for_decrypt(options)
473
+ if options[:kms_key_id]
474
+ Aws::S3::EncryptionV3::KmsCipherProvider.new(
475
+ kms_key_id: options[:kms_key_id],
476
+ kms_client: kms_client(options),
477
+ key_wrap_schema: options[:key_wrap_schema],
478
+ content_encryption_schema: options[:content_encryption_schema]
479
+ )
480
+ else
481
+ # Create V3 key provider explicitly for proper namespace consistency
482
+ key_provider = if options[:key_provider]
483
+ options[:key_provider]
484
+ elsif options[:encryption_key]
485
+ Aws::S3::EncryptionV3::DefaultKeyProvider.new(options)
486
+ else
487
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
488
+ raise ArgumentError, msg
489
+ end
490
+ Aws::S3::EncryptionV3::DefaultCipherProvider.new(
491
+ key_provider: key_provider,
492
+ key_wrap_schema: options[:key_wrap_schema],
493
+ content_encryption_schema: options[:content_encryption_schema]
494
+ )
495
+ end
496
+ end
497
+
426
498
  # Validate required parameters exist and don't conflict.
427
499
  # The cek_alg and wrap_alg are passed on to the CipherProviders
428
500
  # and further validated there
@@ -452,36 +524,19 @@ module Aws
452
524
  options.delete(:encryption_key)
453
525
  options.delete(:envelope_location)
454
526
  options.delete(:instruction_file_suffix)
527
+ options.delete(:commitment_policy)
455
528
  REQUIRED_PARAMS.each { |p| options.delete(p) }
456
529
  S3::Client.new(options)
457
530
  end
458
531
  end
459
532
 
460
533
  def kms_client(options)
461
- options[:kms_client] || begin
534
+ options[:kms_client] || (@kms_client ||=
462
535
  KMS::Client.new(
463
536
  region: @client.config.region,
464
537
  credentials: @client.config.credentials,
465
538
  )
466
- end
467
- end
468
-
469
- def cipher_provider(options)
470
- if options[:kms_key_id]
471
- KmsCipherProvider.new(
472
- kms_key_id: options[:kms_key_id],
473
- kms_client: kms_client(options),
474
- key_wrap_schema: options[:key_wrap_schema],
475
- content_encryption_schema: options[:content_encryption_schema]
476
- )
477
- else
478
- @key_provider = extract_key_provider(options)
479
- DefaultCipherProvider.new(
480
- key_provider: @key_provider,
481
- key_wrap_schema: options[:key_wrap_schema],
482
- content_encryption_schema: options[:content_encryption_schema]
483
- )
484
- end
539
+ )
485
540
  end
486
541
 
487
542
  def extract_key_provider(options)
@@ -564,7 +619,27 @@ module Aws
564
619
  end
565
620
  security_profile
566
621
  end
622
+
623
+ def extract_commitment_policy(options)
624
+ validate_commitment_policy(options[:commitment_policy])
625
+ end
626
+
627
+ def validate_commitment_policy(commitment_policy)
628
+ return nil if commitment_policy.nil?
629
+
630
+ unless SUPPORTED_COMMITMENT_POLICIES.include? commitment_policy
631
+ raise ArgumentError, "Unsupported commitment policy: :#{commitment_policy}. " \
632
+ "The V2 client only supports :forbid_encrypt_allow_decrypt for migration purposes. " \
633
+ "For migration guidance, see: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-encryption-migration-v2-v3.html"
634
+ end
635
+ commitment_policy
636
+ end
567
637
  end
568
638
  end
569
639
  end
570
640
  end
641
+
642
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v1-v2-shared
643
+ ##= type=exception
644
+ ##= reason=This has never been supported in Ruby
645
+ ##% This string MAY be encoded by the esoteric double-encoding scheme used by the S3 web server.
@@ -9,40 +9,6 @@ module Aws
9
9
  class DecryptHandler < Seahorse::Client::Handler
10
10
  @@warned_response_target_proc = false
11
11
 
12
- V1_ENVELOPE_KEYS = %w(
13
- x-amz-key
14
- x-amz-iv
15
- x-amz-matdesc
16
- )
17
-
18
- V2_ENVELOPE_KEYS = %w(
19
- x-amz-key-v2
20
- x-amz-iv
21
- x-amz-cek-alg
22
- x-amz-wrap-alg
23
- x-amz-matdesc
24
- )
25
-
26
- V2_OPTIONAL_KEYS = %w(x-amz-tag-len)
27
-
28
- POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS +
29
- V2_ENVELOPE_KEYS + V2_OPTIONAL_KEYS).uniq
30
-
31
- POSSIBLE_WRAPPING_FORMATS = %w(
32
- AES/GCM
33
- kms
34
- kms+context
35
- RSA-OAEP-SHA1
36
- )
37
-
38
- POSSIBLE_ENCRYPTION_FORMATS = %w(
39
- AES/GCM/NoPadding
40
- AES/CBC/PKCS5Padding
41
- AES/CBC/PKCS7Padding
42
- )
43
-
44
- AUTH_REQUIRED_CEK_ALGS = %w(AES/GCM/NoPadding)
45
-
46
12
  def call(context)
47
13
  attach_http_event_listeners(context)
48
14
  apply_cse_user_agent(context)
@@ -63,12 +29,14 @@ module Aws
63
29
  private
64
30
 
65
31
  def attach_http_event_listeners(context)
66
-
67
32
  context.http_response.on_headers(200) do
68
- cipher, envelope = decryption_cipher(context)
69
- decrypter = body_contains_auth_tag?(envelope) ?
70
- authenticated_decrypter(context, cipher, envelope) :
71
- IODecrypter.new(cipher, context.http_response.body)
33
+ decrypter = if Aws::S3::EncryptionV3::Decryption.v3?(context)
34
+ cipher, envelope = Aws::S3::EncryptionV3::Decryption.decryption_cipher(context)
35
+ Aws::S3::EncryptionV3::Decryption.get_decrypter(context, cipher, envelope)
36
+ else
37
+ cipher, envelope = Aws::S3::EncryptionV2::Decryption.decryption_cipher(context)
38
+ Aws::S3::EncryptionV2::Decryption.get_decrypter(context, cipher, envelope)
39
+ end
72
40
  context.http_response.body = decrypter
73
41
  end
74
42
 
@@ -86,129 +54,6 @@ module Aws
86
54
  end
87
55
  end
88
56
 
89
- def decryption_cipher(context)
90
- if (envelope = get_encryption_envelope(context))
91
- cipher = context[:encryption][:cipher_provider]
92
- .decryption_cipher(
93
- envelope,
94
- context[:encryption]
95
- )
96
- [cipher, envelope]
97
- else
98
- raise Errors::DecryptionError, "unable to locate encryption envelope"
99
- end
100
- end
101
-
102
- def get_encryption_envelope(context)
103
- if context[:encryption][:envelope_location] == :metadata
104
- envelope_from_metadata(context) || envelope_from_instr_file(context)
105
- else
106
- envelope_from_instr_file(context) || envelope_from_metadata(context)
107
- end
108
- end
109
-
110
- def envelope_from_metadata(context)
111
- possible_envelope = {}
112
- POSSIBLE_ENVELOPE_KEYS.each do |suffix|
113
- if value = context.http_response.headers["x-amz-meta-#{suffix}"]
114
- possible_envelope[suffix] = value
115
- end
116
- end
117
- extract_envelope(possible_envelope)
118
- end
119
-
120
- def envelope_from_instr_file(context)
121
- suffix = context[:encryption][:instruction_file_suffix]
122
- possible_envelope = Json.load(context.client.get_object(
123
- bucket: context.params[:bucket],
124
- key: context.params[:key] + suffix
125
- ).body.read)
126
- extract_envelope(possible_envelope)
127
- rescue S3::Errors::ServiceError, Json::ParseError
128
- nil
129
- end
130
-
131
- def extract_envelope(hash)
132
- return nil unless hash
133
- return v1_envelope(hash) if hash.key?('x-amz-key')
134
- return v2_envelope(hash) if hash.key?('x-amz-key-v2')
135
- if hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
136
- msg = "unsupported envelope encryption version #{$1}"
137
- raise Errors::DecryptionError, msg
138
- end
139
- end
140
-
141
- def v1_envelope(envelope)
142
- envelope
143
- end
144
-
145
- def v2_envelope(envelope)
146
- unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
147
- alg = envelope['x-amz-cek-alg'].inspect
148
- msg = "unsupported content encrypting key (cek) format: #{alg}"
149
- raise Errors::DecryptionError, msg
150
- end
151
- unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
152
- alg = envelope['x-amz-wrap-alg'].inspect
153
- msg = "unsupported key wrapping algorithm: #{alg}"
154
- raise Errors::DecryptionError, msg
155
- end
156
- unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
157
- msg = "incomplete v2 encryption envelope:\n"
158
- msg += " missing: #{missing_keys.join(',')}\n"
159
- raise Errors::DecryptionError, msg
160
- end
161
- envelope
162
- end
163
-
164
- # This method fetches the tag from the end of the object by
165
- # making a GET Object w/range request. This auth tag is used
166
- # to initialize the cipher, and the decrypter truncates the
167
- # auth tag from the body when writing the final bytes.
168
- def authenticated_decrypter(context, cipher, envelope)
169
- http_resp = context.http_response
170
- content_length = http_resp.headers['content-length'].to_i
171
- auth_tag_length = auth_tag_length(envelope)
172
-
173
- auth_tag = context.client.get_object(
174
- bucket: context.params[:bucket],
175
- key: context.params[:key],
176
- version_id: context.params[:version_id],
177
- range: "bytes=-#{auth_tag_length}"
178
- ).body.read
179
-
180
- cipher.auth_tag = auth_tag
181
- cipher.auth_data = ''
182
-
183
- # The encrypted object contains both the cipher text
184
- # plus a trailing auth tag.
185
- IOAuthDecrypter.new(
186
- io: http_resp.body,
187
- encrypted_content_length: content_length - auth_tag_length,
188
- cipher: cipher)
189
- end
190
-
191
- def body_contains_auth_tag?(envelope)
192
- AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
193
- end
194
-
195
- # Determine the auth tag length from the algorithm
196
- # Validate it against the value provided in the x-amz-tag-len
197
- # Return the tag length in bytes
198
- def auth_tag_length(envelope)
199
- tag_length =
200
- case envelope['x-amz-cek-alg']
201
- when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
202
- else
203
- raise ArgumentError, 'Unsupported cek-alg: ' \
204
- "#{envelope['x-amz-cek-alg']}"
205
- end
206
- if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
207
- raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
208
- end
209
- tag_length
210
- end
211
-
212
57
  def apply_cse_user_agent(context)
213
58
  if context.config.user_agent_suffix.nil?
214
59
  context.config.user_agent_suffix = EC_USER_AGENT
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV2
8
+ # @api private
9
+ class Decryption
10
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
11
+ ##% - The mapkey "x-amz-key" MUST be present for V1 format objects.
12
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
13
+ ##% - The mapkey "x-amz-iv" MUST be present for V1 format objects.
14
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
15
+ ##% - The mapkey "x-amz-matdesc" MUST be present for V1 format objects.
16
+ V1_ENVELOPE_KEYS = %w[
17
+ x-amz-key
18
+ x-amz-iv
19
+ x-amz-matdesc
20
+ ].freeze
21
+
22
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
23
+ ##% - The mapkey "x-amz-key-v2" MUST be present for V2 format objects.
24
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
25
+ ##% - The mapkey "x-amz-iv" MUST be present for V2 format objects.
26
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
27
+ ##% - The mapkey "x-amz-cek-alg" MUST be present for V2 format objects.
28
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
29
+ ##% - The mapkey "x-amz-wrap-alg" MUST be present for V2 format objects.
30
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
31
+ ##% - The mapkey "x-amz-matdesc" MUST be present for V2 format objects.
32
+ V2_ENVELOPE_KEYS = %w[
33
+ x-amz-key-v2
34
+ x-amz-iv
35
+ x-amz-cek-alg
36
+ x-amz-wrap-alg
37
+ x-amz-matdesc
38
+ ].freeze
39
+
40
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
41
+ ##= type=exception
42
+ ##= reason=The implementation treats this as optional, but verifies its value.
43
+ ##% - The mapkey "x-amz-tag-len" MUST be present for V2 format objects.
44
+ V2_OPTIONAL_KEYS = %w[x-amz-tag-len].freeze
45
+
46
+ POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS + V2_ENVELOPE_KEYS + V2_OPTIONAL_KEYS).uniq
47
+
48
+ POSSIBLE_WRAPPING_FORMATS = %w[
49
+ AES/GCM
50
+ kms
51
+ kms+context
52
+ RSA-OAEP-SHA1
53
+ ].freeze
54
+
55
+ POSSIBLE_ENCRYPTION_FORMATS = %w[
56
+ AES/GCM/NoPadding
57
+ AES/CBC/PKCS5Padding
58
+ AES/CBC/PKCS7Padding
59
+ ].freeze
60
+
61
+ AUTH_REQUIRED_CEK_ALGS = %w[AES/GCM/NoPadding].freeze
62
+
63
+ class << self
64
+ def decryption_cipher(context)
65
+ if (envelope = get_encryption_envelope(context))
66
+ cipher = context[:encryption][:cipher_provider]
67
+ .decryption_cipher(
68
+ envelope,
69
+ context[:encryption]
70
+ )
71
+ [cipher, envelope]
72
+ else
73
+ raise Errors::DecryptionError, 'unable to locate encryption envelope'
74
+ end
75
+ end
76
+
77
+ def get_decrypter(context, cipher, envelope)
78
+ if body_contains_auth_tag?(envelope)
79
+ authenticated_decrypter(context, cipher, envelope)
80
+ else
81
+ IODecrypter.new(cipher, context.http_response.body)
82
+ end
83
+ end
84
+
85
+ def get_encryption_envelope(context)
86
+ if context[:encryption][:envelope_location] == :metadata
87
+ envelope_from_metadata(context) || envelope_from_instr_file(context)
88
+ else
89
+ envelope_from_instr_file(context) || envelope_from_metadata(context)
90
+ end
91
+ end
92
+
93
+ def envelope_from_metadata(context)
94
+ possible_envelope = {}
95
+ POSSIBLE_ENVELOPE_KEYS.each do |suffix|
96
+ if (value = context.http_response.headers["x-amz-meta-#{suffix}"])
97
+ possible_envelope[suffix] = value
98
+ end
99
+ end
100
+ extract_envelope(possible_envelope)
101
+ end
102
+
103
+ def envelope_from_instr_file(context)
104
+ suffix = context[:encryption][:instruction_file_suffix]
105
+ possible_envelope = Json.load(context.client.get_object(
106
+ bucket: context.params[:bucket],
107
+ key: context.params[:key] + suffix
108
+ ).body.read)
109
+ extract_envelope(possible_envelope)
110
+ rescue S3::Errors::ServiceError, Json::ParseError
111
+ nil
112
+ end
113
+
114
+ def extract_envelope(hash)
115
+ return nil unless hash
116
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
117
+ ##% - If the metadata contains "x-amz-iv" and "x-amz-key" then the object MUST be considered as an S3EC-encrypted object using the V1 format.
118
+ return v1_envelope(hash) if hash.key?('x-amz-key')
119
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
120
+ ##% - If the metadata contains "x-amz-iv" and "x-amz-metadata-x-amz-key-v2" then the object MUST be considered as an S3EC-encrypted object using the V2 format.
121
+ return v2_envelope(hash) if hash.key?('x-amz-key-v2')
122
+
123
+ return unless hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
124
+
125
+ msg = "unsupported envelope encryption version #{::Regexp.last_match(1)}"
126
+ raise Errors::DecryptionError, msg
127
+ end
128
+
129
+ def v1_envelope(envelope)
130
+ envelope
131
+ end
132
+
133
+ def v2_envelope(envelope)
134
+ unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
135
+ alg = envelope['x-amz-cek-alg'].inspect
136
+ msg = "unsupported content encrypting key (cek) format: #{alg}"
137
+ raise Errors::DecryptionError, msg
138
+ end
139
+ unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
140
+ alg = envelope['x-amz-wrap-alg'].inspect
141
+ msg = "unsupported key wrapping algorithm: #{alg}"
142
+ raise Errors::DecryptionError, msg
143
+ end
144
+ unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
145
+ msg = "incomplete v2 encryption envelope:\n"
146
+ msg += " missing: #{missing_keys.join(',')}\n"
147
+ raise Errors::DecryptionError, msg
148
+ end
149
+ envelope
150
+ end
151
+
152
+ def body_contains_auth_tag?(envelope)
153
+ AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
154
+ end
155
+
156
+ # This method fetches the tag from the end of the object by
157
+ # making a GET Object w/range request. This auth tag is used
158
+ # to initialize the cipher, and the decrypter truncates the
159
+ # auth tag from the body when writing the final bytes.
160
+ def authenticated_decrypter(context, cipher, envelope)
161
+ http_resp = context.http_response
162
+ content_length = http_resp.headers['content-length'].to_i
163
+ auth_tag_length = auth_tag_length(envelope)
164
+
165
+ auth_tag = context.client.get_object(
166
+ bucket: context.params[:bucket],
167
+ key: context.params[:key],
168
+ version_id: context.params[:version_id],
169
+ range: "bytes=-#{auth_tag_length}"
170
+ ).body.read
171
+
172
+ cipher.auth_tag = auth_tag
173
+ cipher.auth_data = ''
174
+
175
+ # The encrypted object contains both the cipher text
176
+ # plus a trailing auth tag.
177
+ IOAuthDecrypter.new(
178
+ io: http_resp.body,
179
+ encrypted_content_length: content_length - auth_tag_length,
180
+ cipher: cipher
181
+ )
182
+ end
183
+
184
+ # Determine the auth tag length from the algorithm
185
+ # Validate it against the value provided in the x-amz-tag-len
186
+ # Return the tag length in bytes
187
+ def auth_tag_length(envelope)
188
+ tag_length =
189
+ case envelope['x-amz-cek-alg']
190
+ when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
191
+ else
192
+ raise ArgumentError, 'Unsupported cek-alg: ' \
193
+ "#{envelope['x-amz-cek-alg']}"
194
+ end
195
+ if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
196
+ raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
197
+ end
198
+
199
+ tag_length
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end