aws-sdk-s3 1.48.0 → 1.183.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 (134) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1352 -0
  3. data/LICENSE.txt +202 -0
  4. data/VERSION +1 -0
  5. data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
  6. data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
  7. data/lib/aws-sdk-s3/bucket.rb +1005 -106
  8. data/lib/aws-sdk-s3/bucket_acl.rb +65 -18
  9. data/lib/aws-sdk-s3/bucket_cors.rb +80 -18
  10. data/lib/aws-sdk-s3/bucket_lifecycle.rb +71 -20
  11. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +126 -21
  12. data/lib/aws-sdk-s3/bucket_logging.rb +68 -16
  13. data/lib/aws-sdk-s3/bucket_notification.rb +52 -20
  14. data/lib/aws-sdk-s3/bucket_policy.rb +108 -17
  15. data/lib/aws-sdk-s3/bucket_region_cache.rb +11 -5
  16. data/lib/aws-sdk-s3/bucket_request_payment.rb +60 -15
  17. data/lib/aws-sdk-s3/bucket_tagging.rb +71 -18
  18. data/lib/aws-sdk-s3/bucket_versioning.rb +133 -17
  19. data/lib/aws-sdk-s3/bucket_website.rb +78 -21
  20. data/lib/aws-sdk-s3/client.rb +14517 -941
  21. data/lib/aws-sdk-s3/client_api.rb +1296 -197
  22. data/lib/aws-sdk-s3/customizations/bucket.rb +56 -37
  23. data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
  24. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  25. data/lib/aws-sdk-s3/customizations/object.rb +288 -68
  26. data/lib/aws-sdk-s3/customizations/object_summary.rb +10 -0
  27. data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
  28. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  29. data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
  30. data/lib/aws-sdk-s3/customizations.rb +27 -28
  31. data/lib/aws-sdk-s3/encryption/client.rb +28 -7
  32. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
  33. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  34. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  35. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  36. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  38. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  39. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  40. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  41. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +46 -11
  42. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  43. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  44. data/lib/aws-sdk-s3/encryption.rb +4 -0
  45. data/lib/aws-sdk-s3/encryptionV2/client.rb +570 -0
  46. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +223 -0
  47. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  48. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  49. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +65 -0
  50. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  51. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  52. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  53. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  54. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  55. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +173 -0
  56. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  57. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  58. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  59. data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
  60. data/lib/aws-sdk-s3/endpoint_provider.rb +716 -0
  61. data/lib/aws-sdk-s3/endpoints.rb +1434 -0
  62. data/lib/aws-sdk-s3/errors.rb +170 -1
  63. data/lib/aws-sdk-s3/event_streams.rb +8 -1
  64. data/lib/aws-sdk-s3/express_credentials.rb +55 -0
  65. data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
  66. data/lib/aws-sdk-s3/file_downloader.rb +161 -46
  67. data/lib/aws-sdk-s3/file_part.rb +11 -6
  68. data/lib/aws-sdk-s3/file_uploader.rb +39 -18
  69. data/lib/aws-sdk-s3/legacy_signer.rb +17 -25
  70. data/lib/aws-sdk-s3/multipart_file_uploader.rb +104 -27
  71. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +61 -21
  72. data/lib/aws-sdk-s3/multipart_upload.rb +342 -32
  73. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  74. data/lib/aws-sdk-s3/multipart_upload_part.rb +384 -46
  75. data/lib/aws-sdk-s3/object.rb +2600 -231
  76. data/lib/aws-sdk-s3/object_acl.rb +103 -25
  77. data/lib/aws-sdk-s3/object_copier.rb +9 -5
  78. data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -22
  79. data/lib/aws-sdk-s3/object_summary.rb +2174 -204
  80. data/lib/aws-sdk-s3/object_version.rb +539 -80
  81. data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
  82. data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
  83. data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
  84. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +7 -43
  85. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +20 -3
  86. data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
  87. data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
  88. data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
  89. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
  90. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
  91. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  92. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +62 -17
  93. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
  94. data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
  95. data/lib/aws-sdk-s3/plugins/md5s.rb +14 -70
  96. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  97. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  98. data/lib/aws-sdk-s3/plugins/s3_signer.rb +63 -94
  99. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  100. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +139 -0
  101. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  102. data/lib/aws-sdk-s3/presigned_post.rb +160 -99
  103. data/lib/aws-sdk-s3/presigner.rb +141 -62
  104. data/lib/aws-sdk-s3/resource.rb +156 -17
  105. data/lib/aws-sdk-s3/types.rb +13021 -4106
  106. data/lib/aws-sdk-s3/waiters.rb +67 -1
  107. data/lib/aws-sdk-s3.rb +46 -32
  108. data/sig/bucket.rbs +222 -0
  109. data/sig/bucket_acl.rbs +78 -0
  110. data/sig/bucket_cors.rbs +69 -0
  111. data/sig/bucket_lifecycle.rbs +88 -0
  112. data/sig/bucket_lifecycle_configuration.rbs +115 -0
  113. data/sig/bucket_logging.rbs +76 -0
  114. data/sig/bucket_notification.rbs +114 -0
  115. data/sig/bucket_policy.rbs +59 -0
  116. data/sig/bucket_request_payment.rbs +54 -0
  117. data/sig/bucket_tagging.rbs +65 -0
  118. data/sig/bucket_versioning.rbs +77 -0
  119. data/sig/bucket_website.rbs +93 -0
  120. data/sig/client.rbs +2472 -0
  121. data/sig/customizations/bucket.rbs +19 -0
  122. data/sig/customizations/object.rbs +38 -0
  123. data/sig/customizations/object_summary.rbs +35 -0
  124. data/sig/errors.rbs +42 -0
  125. data/sig/multipart_upload.rbs +120 -0
  126. data/sig/multipart_upload_part.rbs +109 -0
  127. data/sig/object.rbs +459 -0
  128. data/sig/object_acl.rbs +86 -0
  129. data/sig/object_summary.rbs +345 -0
  130. data/sig/object_version.rbs +143 -0
  131. data/sig/resource.rbs +134 -0
  132. data/sig/types.rbs +2712 -0
  133. data/sig/waiters.rbs +95 -0
  134. metadata +74 -15
@@ -1,6 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
1
5
  module Aws
2
6
  module S3
3
7
 
8
+ # [MAINTENANCE MODE] There is a new version of the Encryption Client.
9
+ # AWS strongly recommends upgrading to the {Aws::S3::EncryptionV2::Client},
10
+ # which provides updated data security best practices.
11
+ # See documentation for {Aws::S3::EncryptionV2::Client}.
4
12
  # Provides an encryption client that encrypts and decrypts data client-side,
5
13
  # storing the encrypted data in Amazon S3.
6
14
  #
@@ -112,7 +120,7 @@ module Aws
112
120
  # attr_reader :encryption_materials
113
121
  #
114
122
  # def key_for(matdesc)
115
- # key_name = JSON.load(matdesc)['key']
123
+ # key_name = JSON.parse(matdesc)['key']
116
124
  # if key = @keys[key_name]
117
125
  # key
118
126
  # else
@@ -178,6 +186,8 @@ module Aws
178
186
  class Client
179
187
 
180
188
  extend Deprecations
189
+ extend Forwardable
190
+ def_delegators :@client, :config, :delete_object, :head_object, :build_request
181
191
 
182
192
  # Creates a new encryption client. You must provide one of the following
183
193
  # options:
@@ -223,6 +233,13 @@ module Aws
223
233
  @envelope_location = extract_location(options)
224
234
  @instruction_file_suffix = extract_suffix(options)
225
235
  end
236
+ deprecated :initialize,
237
+ message:
238
+ '[MAINTENANCE MODE] This version of the S3 Encryption client is currently in maintenance mode. ' \
239
+ 'AWS strongly recommends upgrading to the Aws::S3::EncryptionV2::Client, ' \
240
+ 'which provides updated data security best practices. ' \
241
+ 'See documentation for Aws::S3::EncryptionV2::Client.'
242
+
226
243
 
227
244
  # @return [S3::Client]
228
245
  attr_reader :client
@@ -253,7 +270,9 @@ module Aws
253
270
  envelope_location: @envelope_location,
254
271
  instruction_file_suffix: @instruction_file_suffix,
255
272
  }
256
- req.send_request
273
+ Aws::Plugins::UserAgent.metric('S3_CRYPTO_V1N') do
274
+ req.send_request
275
+ end
257
276
  end
258
277
 
259
278
  # Gets an object from Amazon S3, decrypting data locally.
@@ -281,7 +300,9 @@ module Aws
281
300
  envelope_location: envelope_location,
282
301
  instruction_file_suffix: instruction_file_suffix,
283
302
  }
284
- req.send_request(target: block)
303
+ Aws::Plugins::UserAgent.metric('S3_CRYPTO_V1N') do
304
+ req.send_request(target: block)
305
+ end
285
306
  end
286
307
 
287
308
  private
@@ -327,7 +348,7 @@ module Aws
327
348
  elsif options[:encryption_key]
328
349
  DefaultKeyProvider.new(options)
329
350
  else
330
- msg = "you must pass a :kms_key_id, :key_provider, or :encryption_key"
351
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
331
352
  raise ArgumentError, msg
332
353
  end
333
354
  end
@@ -347,8 +368,8 @@ module Aws
347
368
  if [:metadata, :instruction_file].include?(location)
348
369
  location
349
370
  else
350
- msg = ":envelope_location must be :metadata or :instruction_file "
351
- msg << "got #{location.inspect}"
371
+ msg = ':envelope_location must be :metadata or :instruction_file '\
372
+ "got #{location.inspect}"
352
373
  raise ArgumentError, msg
353
374
  end
354
375
  end
@@ -358,7 +379,7 @@ module Aws
358
379
  if String === suffix
359
380
  suffix
360
381
  else
361
- msg = ":instruction_file_suffix must be a String"
382
+ msg = ':instruction_file_suffix must be a String'
362
383
  raise ArgumentError, msg
363
384
  end
364
385
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -5,6 +7,7 @@ module Aws
5
7
  module Encryption
6
8
  # @api private
7
9
  class DecryptHandler < Seahorse::Client::Handler
10
+ @@warned_response_target_proc = false
8
11
 
9
12
  V1_ENVELOPE_KEYS = %w(
10
13
  x-amz-key
@@ -20,7 +23,17 @@ module Aws
20
23
  x-amz-matdesc
21
24
  )
22
25
 
23
- POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS + V2_ENVELOPE_KEYS).uniq
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
+ )
24
37
 
25
38
  POSSIBLE_ENCRYPTION_FORMATS = %w(
26
39
  AES/GCM/NoPadding
@@ -28,8 +41,21 @@ module Aws
28
41
  AES/CBC/PKCS7Padding
29
42
  )
30
43
 
44
+ AUTH_REQUIRED_CEK_ALGS = %w(AES/GCM/NoPadding)
45
+
31
46
  def call(context)
32
47
  attach_http_event_listeners(context)
48
+ apply_cse_user_agent(context)
49
+
50
+ if context[:response_target].is_a?(Proc) && !@@warned_response_target_proc
51
+ @@warned_response_target_proc = true
52
+ warn(':response_target is a Proc, or a block was provided. ' \
53
+ 'Read the entire object to the ' \
54
+ 'end before you start using the decrypted data. This is to ' \
55
+ 'verify that the object has not been modified since it ' \
56
+ 'was encrypted.')
57
+ end
58
+
33
59
  @handler.call(context)
34
60
  end
35
61
 
@@ -38,9 +64,9 @@ module Aws
38
64
  def attach_http_event_listeners(context)
39
65
 
40
66
  context.http_response.on_headers(200) do
41
- cipher = decryption_cipher(context)
42
- decrypter = body_contains_auth_tag?(context) ?
43
- authenticated_decrypter(context, cipher) :
67
+ cipher, envelope = decryption_cipher(context)
68
+ decrypter = body_contains_auth_tag?(envelope) ?
69
+ authenticated_decrypter(context, cipher, envelope) :
44
70
  IODecrypter.new(cipher, context.http_response.body)
45
71
  context.http_response.body = decrypter
46
72
  end
@@ -60,8 +86,13 @@ module Aws
60
86
  end
61
87
 
62
88
  def decryption_cipher(context)
63
- if envelope = get_encryption_envelope(context)
64
- context[:encryption][:cipher_provider].decryption_cipher(envelope)
89
+ if (envelope = get_encryption_envelope(context))
90
+ cipher = context[:encryption][:cipher_provider]
91
+ .decryption_cipher(
92
+ envelope,
93
+ context[:encryption]
94
+ )
95
+ [cipher, envelope]
65
96
  else
66
97
  raise Errors::DecryptionError, "unable to locate encryption envelope"
67
98
  end
@@ -97,13 +128,12 @@ module Aws
97
128
  end
98
129
 
99
130
  def extract_envelope(hash)
131
+ return nil unless hash
100
132
  return v1_envelope(hash) if hash.key?('x-amz-key')
101
133
  return v2_envelope(hash) if hash.key?('x-amz-key-v2')
102
134
  if hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
103
135
  msg = "unsupported envelope encryption version #{$1}"
104
136
  raise Errors::DecryptionError, msg
105
- else
106
- nil # no envelope found
107
137
  end
108
138
  end
109
139
 
@@ -117,39 +147,27 @@ module Aws
117
147
  msg = "unsupported content encrypting key (cek) format: #{alg}"
118
148
  raise Errors::DecryptionError, msg
119
149
  end
120
- unless envelope['x-amz-wrap-alg'] == 'kms'
121
- # possible to support
122
- # RSA/ECB/OAEPWithSHA-256AndMGF1Padding
150
+ unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
123
151
  alg = envelope['x-amz-wrap-alg'].inspect
124
152
  msg = "unsupported key wrapping algorithm: #{alg}"
125
153
  raise Errors::DecryptionError, msg
126
154
  end
127
- unless V2_ENVELOPE_KEYS.sort == envelope.keys.sort
155
+ unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
128
156
  msg = "incomplete v2 encryption envelope:\n"
129
- msg += " expected: #{V2_ENVELOPE_KEYS.join(',')}\n"
130
- msg += " got: #{envelope_keys.join(', ')}"
157
+ msg += " missing: #{missing_keys.join(',')}\n"
131
158
  raise Errors::DecryptionError, msg
132
159
  end
133
160
  envelope
134
161
  end
135
162
 
136
- # When the x-amz-meta-x-amz-tag-len header is present, it indicates
137
- # that the body of this object has a trailing auth tag. The header
138
- # indicates the length of that tag.
139
- #
140
163
  # This method fetches the tag from the end of the object by
141
164
  # making a GET Object w/range request. This auth tag is used
142
165
  # to initialize the cipher, and the decrypter truncates the
143
166
  # auth tag from the body when writing the final bytes.
144
- def authenticated_decrypter(context, cipher)
145
- if RUBY_VERSION.match(/1.9/)
146
- raise "authenticated decryption not supported by OpeenSSL in Ruby version ~> 1.9"
147
- raise Aws::Errors::NonSupportedRubyVersionError, msg
148
- end
167
+ def authenticated_decrypter(context, cipher, envelope)
149
168
  http_resp = context.http_response
150
169
  content_length = http_resp.headers['content-length'].to_i
151
- auth_tag_length = http_resp.headers['x-amz-meta-x-amz-tag-len']
152
- auth_tag_length = auth_tag_length.to_i / 8
170
+ auth_tag_length = auth_tag_length(envelope)
153
171
 
154
172
  auth_tag = context.client.get_object(
155
173
  bucket: context.params[:bucket],
@@ -161,16 +179,40 @@ module Aws
161
179
  cipher.auth_data = ''
162
180
 
163
181
  # The encrypted object contains both the cipher text
164
- # plus a trailing auth tag. This decrypter will the body
165
- # expect for the trailing auth tag.
182
+ # plus a trailing auth tag.
166
183
  IOAuthDecrypter.new(
167
184
  io: http_resp.body,
168
185
  encrypted_content_length: content_length - auth_tag_length,
169
186
  cipher: cipher)
170
187
  end
171
188
 
172
- def body_contains_auth_tag?(context)
173
- context.http_response.headers['x-amz-meta-x-amz-tag-len']
189
+ def body_contains_auth_tag?(envelope)
190
+ AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
191
+ end
192
+
193
+ # Determine the auth tag length from the algorithm
194
+ # Validate it against the value provided in the x-amz-tag-len
195
+ # Return the tag length in bytes
196
+ def auth_tag_length(envelope)
197
+ tag_length =
198
+ case envelope['x-amz-cek-alg']
199
+ when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
200
+ else
201
+ raise ArgumentError, 'Unsupported cek-alg: ' \
202
+ "#{envelope['x-amz-cek-alg']}"
203
+ end
204
+ if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
205
+ raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
206
+ end
207
+ tag_length
208
+ end
209
+
210
+ def apply_cse_user_agent(context)
211
+ if context.config.user_agent_suffix.nil?
212
+ context.config.user_agent_suffix = EC_USER_AGENT
213
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
214
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
215
+ end
174
216
  end
175
217
 
176
218
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -24,11 +26,48 @@ module Aws
24
26
 
25
27
  # @return [Cipher] Given an encryption envelope, returns a
26
28
  # decryption cipher.
27
- def decryption_cipher(envelope)
29
+ def decryption_cipher(envelope, options = {})
28
30
  master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
29
- key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
30
- iv = decode64(envelope['x-amz-iv'])
31
- Utils.aes_decryption_cipher(:CBC, key, iv)
31
+ if envelope.key? 'x-amz-key'
32
+ # Support for decryption of legacy objects
33
+ key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
34
+ iv = decode64(envelope['x-amz-iv'])
35
+ Utils.aes_decryption_cipher(:CBC, key, iv)
36
+ else
37
+ if envelope['x-amz-cek-alg'] != 'AES/GCM/NoPadding'
38
+ raise ArgumentError, 'Unsupported cek-alg: ' \
39
+ "#{envelope['x-amz-cek-alg']}"
40
+ end
41
+ key =
42
+ case envelope['x-amz-wrap-alg']
43
+ when 'AES/GCM'
44
+ if master_key.is_a? OpenSSL::PKey::RSA
45
+ raise ArgumentError, 'Key mismatch - Client is configured' \
46
+ ' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
47
+ end
48
+ Utils.decrypt_aes_gcm(master_key,
49
+ decode64(envelope['x-amz-key-v2']),
50
+ envelope['x-amz-cek-alg'])
51
+ when 'RSA-OAEP-SHA1'
52
+ unless master_key.is_a? OpenSSL::PKey::RSA
53
+ raise ArgumentError, 'Key mismatch - Client is configured' \
54
+ ' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
55
+ end
56
+ key, cek_alg = Utils.decrypt_rsa(master_key, decode64(envelope['x-amz-key-v2']))
57
+ raise Errors::DecryptionError unless cek_alg == envelope['x-amz-cek-alg']
58
+ key
59
+ when 'kms+context'
60
+ raise ArgumentError, 'Key mismatch - Client is configured' \
61
+ ' with a user provided key and the x-amz-wrap-alg is' \
62
+ ' kms+context. Please configure the client with the' \
63
+ ' required kms_key_id'
64
+ else
65
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
66
+ "#{envelope['x-amz-wrap-alg']}"
67
+ end
68
+ iv = decode64(envelope['x-amz-iv'])
69
+ Utils.aes_decryption_cipher(:GCM, key, iv)
70
+ end
32
71
  end
33
72
 
34
73
  private
@@ -56,7 +95,6 @@ module Aws
56
95
  def decode64(str)
57
96
  Base64.decode64(str)
58
97
  end
59
-
60
98
  end
61
99
  end
62
100
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -10,6 +12,7 @@ module Aws
10
12
  envelope, cipher = context[:encryption][:cipher_provider].encryption_cipher
11
13
  apply_encryption_envelope(context, envelope, cipher)
12
14
  apply_encryption_cipher(context, cipher)
15
+ apply_cse_user_agent(context)
13
16
  @handler.call(context)
14
17
  end
15
18
 
@@ -36,14 +39,22 @@ module Aws
36
39
  context.params[:body] = IOEncrypter.new(cipher, io)
37
40
  context.params[:metadata] ||= {}
38
41
  context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
39
- if md5 = context.params.delete(:content_md5)
40
- context.params[:metadata]['x-amz-unencrypted-content-md5'] = md5
42
+ if context.params.delete(:content_md5)
43
+ warn('Setting content_md5 on client side encrypted objects is deprecated')
41
44
  end
42
45
  context.http_response.on_headers do
43
46
  context.params[:body].close
44
47
  end
45
48
  end
46
49
 
50
+ def apply_cse_user_agent(context)
51
+ if context.config.user_agent_suffix.nil?
52
+ context.config.user_agent_suffix = EC_USER_AGENT
53
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
54
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
55
+ end
56
+ end
57
+
47
58
  end
48
59
  end
49
60
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -7,8 +9,10 @@ module Aws
7
9
  # @param [OpenSSL::Cipher] cipher
8
10
  # @param [IO#write] io An IO-like object that responds to `#write`.
9
11
  def initialize(cipher, io)
10
- @cipher = cipher.clone
11
- @io = io
12
+ @cipher = cipher
13
+ # Ensure that IO is reset between retries
14
+ @io = io.tap { |io| io.truncate(0) if io.respond_to?(:truncate) }
15
+ @cipher_buffer = String.new
12
16
  end
13
17
 
14
18
  # @return [#write]
@@ -16,7 +20,11 @@ module Aws
16
20
 
17
21
  def write(chunk)
18
22
  # decrypt and write
19
- @io.write(@cipher.update(chunk))
23
+ if @cipher.method(:update).arity == 1
24
+ @io.write(@cipher.update(chunk))
25
+ else
26
+ @io.write(@cipher.update(chunk, @cipher_buffer))
27
+ end
20
28
  end
21
29
 
22
30
  def finalize
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require 'tempfile'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -15,11 +17,13 @@ module Aws
15
17
  # envelope and encryption cipher.
16
18
  def encryption_cipher
17
19
  encryption_context = { "kms_cmk_id" => @kms_key_id }
18
- key_data = @kms_client.generate_data_key(
19
- key_id: @kms_key_id,
20
- encryption_context: encryption_context,
21
- key_spec: 'AES_256',
22
- )
20
+ key_data = Aws::Plugins::UserAgent.metric('S3_CRYPTO_V1N') do
21
+ @kms_client.generate_data_key(
22
+ key_id: @kms_key_id,
23
+ encryption_context: encryption_context,
24
+ key_spec: 'AES_256'
25
+ )
26
+ end
23
27
  cipher = Utils.aes_encryption_cipher(:CBC)
24
28
  cipher.key = key_data.plaintext
25
29
  envelope = {
@@ -34,15 +38,38 @@ module Aws
34
38
 
35
39
  # @return [Cipher] Given an encryption envelope, returns a
36
40
  # decryption cipher.
37
- def decryption_cipher(envelope)
41
+ def decryption_cipher(envelope, options = {})
38
42
  encryption_context = Json.load(envelope['x-amz-matdesc'])
39
- key = @kms_client.decrypt(
40
- ciphertext_blob: decode64(envelope['x-amz-key-v2']),
41
- encryption_context: encryption_context,
42
- ).plaintext
43
+ cek_alg = envelope['x-amz-cek-alg']
44
+
45
+ case envelope['x-amz-wrap-alg']
46
+ when 'kms'; # NO OP
47
+ when 'kms+context'
48
+ if cek_alg != encryption_context['aws:x-amz-cek-alg']
49
+ raise Errors::DecryptionError, 'Value of cek-alg from envelope'\
50
+ ' does not match the value in the encryption context'
51
+ end
52
+ when 'AES/GCM'
53
+ raise ArgumentError, 'Key mismatch - Client is configured' \
54
+ ' with a KMS key and the x-amz-wrap-alg is AES/GCM.'
55
+ when 'RSA-OAEP-SHA1'
56
+ raise ArgumentError, 'Key mismatch - Client is configured' \
57
+ ' with a KMS key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
58
+ else
59
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
60
+ "#{envelope['x-amz-wrap-alg']}"
61
+ end
62
+
63
+ key = Aws::Plugins::UserAgent.metric('S3_CRYPTO_V1N') do
64
+ @kms_client.decrypt(
65
+ ciphertext_blob: decode64(envelope['x-amz-key-v2']),
66
+ encryption_context: encryption_context
67
+ ).plaintext
68
+ end
69
+
43
70
  iv = decode64(envelope['x-amz-iv'])
44
71
  block_mode =
45
- case envelope['x-amz-cek-alg']
72
+ case cek_alg
46
73
  when 'AES/CBC/PKCS5Padding'
47
74
  :CBC
48
75
  when 'AES/CBC/PKCS7Padding'
@@ -59,6 +86,14 @@ module Aws
59
86
 
60
87
  private
61
88
 
89
+ def build_encryption_context(cek_alg, options = {})
90
+ kms_context = (options[:kms_encryption_context] || {})
91
+ .each_with_object({}) { |(k, v), h| h[k.to_s] = v }
92
+ {
93
+ 'aws:x-amz-cek-alg' => cek_alg
94
+ }.merge(kms_context)
95
+ end
96
+
62
97
  def encode64(str)
63
98
  Base64.encode64(str).split("\n") * ""
64
99
  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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'aws-sdk-s3/encryption/client'
2
4
  require 'aws-sdk-s3/encryption/decrypt_handler'
3
5
  require 'aws-sdk-s3/encryption/default_cipher_provider'
@@ -15,5 +17,7 @@ require 'aws-sdk-s3/encryption/default_key_provider'
15
17
  module Aws
16
18
  module S3
17
19
  module Encryption; end
20
+ AES_GCM_TAG_LEN_BYTES = 16
21
+ EC_USER_AGENT = 'S3CryptoV1n'
18
22
  end
19
23
  end