aws-sdk-s3 1.203.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-s3/bucket.rb +1 -1
  5. data/lib/aws-sdk-s3/bucket_acl.rb +1 -1
  6. data/lib/aws-sdk-s3/client.rb +319 -260
  7. data/lib/aws-sdk-s3/client_api.rb +59 -0
  8. data/lib/aws-sdk-s3/customizations/object.rb +3 -4
  9. data/lib/aws-sdk-s3/customizations.rb +1 -0
  10. data/lib/aws-sdk-s3/encryption/client.rb +2 -2
  11. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
  12. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
  13. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -0
  14. data/lib/aws-sdk-s3/encryptionV2/client.rb +98 -23
  15. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
  16. data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
  17. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
  18. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
  19. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
  20. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +8 -0
  21. data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
  22. data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
  23. data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
  24. data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
  25. data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
  26. data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
  27. data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
  28. data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
  29. data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
  30. data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
  31. data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
  32. data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
  33. data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
  34. data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
  35. data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
  36. data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
  37. data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
  38. data/lib/aws-sdk-s3/endpoints.rb +26 -0
  39. data/lib/aws-sdk-s3/file_downloader.rb +19 -2
  40. data/lib/aws-sdk-s3/object.rb +4 -4
  41. data/lib/aws-sdk-s3/object_acl.rb +1 -1
  42. data/lib/aws-sdk-s3/object_summary.rb +4 -4
  43. data/lib/aws-sdk-s3/transfer_manager.rb +3 -4
  44. data/lib/aws-sdk-s3/types.rb +206 -98
  45. data/lib/aws-sdk-s3.rb +1 -1
  46. data/sig/bucket.rbs +1 -1
  47. data/sig/client.rbs +38 -12
  48. data/sig/multipart_upload.rbs +1 -1
  49. data/sig/object.rbs +5 -5
  50. data/sig/object_summary.rbs +5 -5
  51. data/sig/types.rbs +45 -14
  52. metadata +17 -1
@@ -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
@@ -14,11 +14,15 @@ module Aws
14
14
  options[:key_wrap_schema],
15
15
  @key_provider.encryption_materials.key
16
16
  )
17
+ ##= ../specification/s3-encryption/encryption.md#content-encryption
18
+ ##% The S3EC MUST use the encryption algorithm configured during [client](./client.md) initialization.
17
19
  @content_encryption_schema = validate_cek(
18
20
  options[:content_encryption_schema]
19
21
  )
20
22
  end
21
23
 
24
+ attr_reader :key_provider
25
+
22
26
  # @return [Array<Hash,Cipher>] Creates an returns a new encryption
23
27
  # envelope and encryption cipher.
24
28
  def encryption_cipher(options = {})
@@ -30,9 +34,14 @@ module Aws
30
34
  )
31
35
  else
32
36
  enc_key = encode64(
37
+ ##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
38
+ ##% The client MUST NOT provide any AAD when encrypting with ALG_AES_256_GCM_IV12_TAG16_NO_KDF.
33
39
  encrypt_aes_gcm(envelope_key(cipher), @content_encryption_schema)
34
40
  )
35
41
  end
42
+
43
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
44
+ ##% Objects encrypted with ALG_AES_256_GCM_IV12_TAG16_NO_KDF MUST use the V2 message format version only.
36
45
  envelope = {
37
46
  'x-amz-key-v2' => enc_key,
38
47
  'x-amz-cek-alg' => @content_encryption_schema,
@@ -52,8 +61,16 @@ module Aws
52
61
  master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
53
62
  if envelope.key? 'x-amz-key'
54
63
  unless options[:security_profile] == :v2_and_legacy
64
+ ##= ../specification/s3-encryption/decryption.md#legacy-decryption
65
+ ##% If the S3EC is not configured to enable legacy unauthenticated content decryption, the client MUST throw an exception when attempting to decrypt an object encrypted with a legacy unauthenticated algorithm suite.
66
+ ##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
67
+ ##% When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy content encryption algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy content encryption algorithm.
55
68
  raise Errors::LegacyDecryptionError
56
69
  end
70
+ ##= ../specification/s3-encryption/decryption.md#legacy-decryption
71
+ ##% The S3EC MUST NOT decrypt objects encrypted using legacy unauthenticated algorithm suites unless specifically configured to do so.
72
+ ##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
73
+ ##% When enabled, the S3EC MUST be able to decrypt objects encrypted with all content encryption algorithms (both legacy and fully supported).
57
74
  # Support for decryption of legacy objects
58
75
  key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
59
76
  iv = decode64(envelope['x-amz-iv'])
@@ -28,6 +28,8 @@ module Aws
28
28
  context.client.put_object(
29
29
  bucket: context.params[:bucket],
30
30
  key: context.params[:key] + suffix,
31
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#v1-v2-instruction-files
32
+ ##% In the V1/V2 message format, all of the content metadata MUST be stored in the Instruction File.
31
33
  body: Json.dump(envelope)
32
34
  )
33
35
  else # :metadata
@@ -44,6 +44,8 @@ module Aws
44
44
  private
45
45
 
46
46
  def encrypt_to_stringio(cipher, plain_text)
47
+ ##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
48
+ ##% The client MUST append the GCM auth tag to the ciphertext if the underlying crypto provider does not do so automatically.
47
49
  if plain_text.empty?
48
50
  StringIO.new(cipher.final + cipher.auth_tag)
49
51
  else
@@ -33,6 +33,8 @@ module Aws
33
33
  end
34
34
  cipher = Utils.aes_encryption_cipher(:GCM)
35
35
  cipher.key = key_data.plaintext
36
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
37
+ ##% Objects encrypted with ALG_AES_256_GCM_IV12_TAG16_NO_KDF MUST use the V2 message format version only.
36
38
  envelope = {
37
39
  'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
38
40
  'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
@@ -53,9 +55,15 @@ module Aws
53
55
 
54
56
  case envelope['x-amz-wrap-alg']
55
57
  when 'kms'
58
+ ##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
59
+ ##% The S3EC MUST support the option to enable or disable legacy wrapping algorithms.
56
60
  unless options[:security_profile] == :v2_and_legacy
61
+ ##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
62
+ ##% When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy wrapping algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy wrapping algorithm.
57
63
  raise Errors::LegacyDecryptionError
58
64
  end
65
+ ##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
66
+ ##% When enabled, the S3EC MUST be able to decrypt objects encrypted with all supported wrapping algorithms (both legacy and fully supported).
59
67
  when 'kms+context'
60
68
  if cek_alg != encryption_context['aws:x-amz-cek-alg']
61
69
  raise Errors::CEKAlgMismatchError
@@ -80,6 +80,11 @@ module Aws
80
80
  # @param [OpenSSL::PKey::RSA, String, nil] key
81
81
  # @param [String, nil] iv The initialization vector
82
82
  def aes_cipher(mode, block_mode, key, iv)
83
+ ##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
84
+ ##% The client MUST initialize the cipher,
85
+ ##% or call an AES-GCM encryption API, with the plaintext data key, the generated IV,
86
+ ##% and the tag length defined in the Algorithm Suite
87
+ ##% when encrypting with ALG_AES_256_GCM_IV12_TAG16_NO_KDF.
83
88
  cipher = key ?
84
89
  OpenSSL::Cipher.new("aes-#{cipher_size(key)}-#{block_mode.downcase}") :
85
90
  OpenSSL::Cipher.new("aes-256-#{block_mode.downcase}")