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
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV2
8
+ # @api private
9
+ class DecryptHandler < Seahorse::Client::Handler
10
+ @@warned_response_target_proc = false
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
+ def call(context)
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
+
58
+ end
59
+
60
+ @handler.call(context)
61
+ end
62
+
63
+ private
64
+
65
+ def attach_http_event_listeners(context)
66
+
67
+ 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)
72
+ context.http_response.body = decrypter
73
+ end
74
+
75
+ context.http_response.on_success(200) do
76
+ decrypter = context.http_response.body
77
+ decrypter.finalize
78
+ decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
79
+ context.http_response.body = decrypter.io
80
+ end
81
+
82
+ context.http_response.on_error do
83
+ if context.http_response.body.respond_to?(:io)
84
+ context.http_response.body = context.http_response.body.io
85
+ end
86
+ end
87
+ end
88
+
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
+ def apply_cse_user_agent(context)
213
+ if context.config.user_agent_suffix.nil?
214
+ context.config.user_agent_suffix = EC_USER_AGENT
215
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
216
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
217
+ end
218
+ end
219
+
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV2
8
+ # @api private
9
+ class DefaultCipherProvider
10
+
11
+ def initialize(options = {})
12
+ @key_provider = options[:key_provider]
13
+ @key_wrap_schema = validate_key_wrap(
14
+ options[:key_wrap_schema],
15
+ @key_provider.encryption_materials.key
16
+ )
17
+ @content_encryption_schema = validate_cek(
18
+ options[:content_encryption_schema]
19
+ )
20
+ end
21
+
22
+ # @return [Array<Hash,Cipher>] Creates an returns a new encryption
23
+ # envelope and encryption cipher.
24
+ def encryption_cipher(options = {})
25
+ validate_options(options)
26
+ cipher = Utils.aes_encryption_cipher(:GCM)
27
+ if @key_provider.encryption_materials.key.is_a? OpenSSL::PKey::RSA
28
+ enc_key = encode64(
29
+ encrypt_rsa(envelope_key(cipher), @content_encryption_schema)
30
+ )
31
+ else
32
+ enc_key = encode64(
33
+ encrypt_aes_gcm(envelope_key(cipher), @content_encryption_schema)
34
+ )
35
+ end
36
+ envelope = {
37
+ 'x-amz-key-v2' => enc_key,
38
+ 'x-amz-cek-alg' => @content_encryption_schema,
39
+ 'x-amz-tag-len' => (AES_GCM_TAG_LEN_BYTES * 8).to_s,
40
+ 'x-amz-wrap-alg' => @key_wrap_schema,
41
+ 'x-amz-iv' => encode64(envelope_iv(cipher)),
42
+ 'x-amz-matdesc' => materials_description
43
+ }
44
+ cipher.auth_data = '' # auth_data must be set after key and iv
45
+ [envelope, cipher]
46
+ end
47
+
48
+ # @return [Cipher] Given an encryption envelope, returns a
49
+ # decryption cipher.
50
+ def decryption_cipher(envelope, options = {})
51
+ validate_options(options)
52
+ master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
53
+ if envelope.key? 'x-amz-key'
54
+ unless options[:security_profile] == :v2_and_legacy
55
+ raise Errors::LegacyDecryptionError
56
+ end
57
+ # Support for decryption of legacy objects
58
+ key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
59
+ iv = decode64(envelope['x-amz-iv'])
60
+ Utils.aes_decryption_cipher(:CBC, key, iv)
61
+ else
62
+ if envelope['x-amz-cek-alg'] != 'AES/GCM/NoPadding'
63
+ raise ArgumentError, 'Unsupported cek-alg: ' \
64
+ "#{envelope['x-amz-cek-alg']}"
65
+ end
66
+ key =
67
+ case envelope['x-amz-wrap-alg']
68
+ when 'AES/GCM'
69
+ if master_key.is_a? OpenSSL::PKey::RSA
70
+ raise ArgumentError, 'Key mismatch - Client is configured' \
71
+ ' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
72
+ end
73
+ Utils.decrypt_aes_gcm(master_key,
74
+ decode64(envelope['x-amz-key-v2']),
75
+ envelope['x-amz-cek-alg'])
76
+ when 'RSA-OAEP-SHA1'
77
+ unless master_key.is_a? OpenSSL::PKey::RSA
78
+ raise ArgumentError, 'Key mismatch - Client is configured' \
79
+ ' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
80
+ end
81
+ key, cek_alg = Utils.decrypt_rsa(master_key, decode64(envelope['x-amz-key-v2']))
82
+ raise Errors::CEKAlgMismatchError unless cek_alg == envelope['x-amz-cek-alg']
83
+ key
84
+ when 'kms+context'
85
+ raise ArgumentError, 'Key mismatch - Client is configured' \
86
+ ' with a user provided key and the x-amz-wrap-alg is' \
87
+ ' kms+context. Please configure the client with the' \
88
+ ' required kms_key_id'
89
+ else
90
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
91
+ "#{envelope['x-amz-wrap-alg']}"
92
+ end
93
+ iv = decode64(envelope['x-amz-iv'])
94
+ Utils.aes_decryption_cipher(:GCM, key, iv)
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ # Validate that the key_wrap_schema
101
+ # is valid, supported and matches the provided key.
102
+ # Returns the string version for the x-amz-key-wrap-alg
103
+ def validate_key_wrap(key_wrap_schema, key)
104
+ if key.is_a? OpenSSL::PKey::RSA
105
+ unless key_wrap_schema == :rsa_oaep_sha1
106
+ raise ArgumentError, ':key_wrap_schema must be set to :rsa_oaep_sha1 for RSA keys.'
107
+ end
108
+ else
109
+ unless key_wrap_schema == :aes_gcm
110
+ raise ArgumentError, ':key_wrap_schema must be set to :aes_gcm for AES keys.'
111
+ end
112
+ end
113
+
114
+ case key_wrap_schema
115
+ when :rsa_oaep_sha1 then 'RSA-OAEP-SHA1'
116
+ when :aes_gcm then 'AES/GCM'
117
+ when :kms_context
118
+ raise ArgumentError, 'A kms_key_id is required when using :kms_context.'
119
+ else
120
+ raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
121
+ end
122
+ end
123
+
124
+ def validate_cek(content_encryption_schema)
125
+ case content_encryption_schema
126
+ when :aes_gcm_no_padding
127
+ "AES/GCM/NoPadding"
128
+ else
129
+ raise ArgumentError, "Unsupported content_encryption_schema: #{content_encryption_schema}"
130
+ end
131
+ end
132
+
133
+ def envelope_key(cipher)
134
+ cipher.key = cipher.random_key
135
+ end
136
+
137
+ def envelope_iv(cipher)
138
+ cipher.iv = cipher.random_iv
139
+ end
140
+
141
+ def encrypt_aes_gcm(data, auth_data)
142
+ Utils.encrypt_aes_gcm(@key_provider.encryption_materials.key, data, auth_data)
143
+ end
144
+
145
+ def encrypt_rsa(data, auth_data)
146
+ Utils.encrypt_rsa(@key_provider.encryption_materials.key, data, auth_data)
147
+ end
148
+
149
+ def materials_description
150
+ @key_provider.encryption_materials.description
151
+ end
152
+
153
+ def encode64(str)
154
+ Base64.encode64(str).split("\n") * ''
155
+ end
156
+
157
+ def decode64(str)
158
+ Base64.decode64(str)
159
+ end
160
+
161
+ def validate_options(options)
162
+ if !options[:kms_encryption_context].nil?
163
+ raise ArgumentError, 'Cannot provide :kms_encryption_context ' \
164
+ 'with non KMS client.'
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ module EncryptionV2
6
+
7
+ # The default key provider is constructed with a single key
8
+ # that is used for both encryption and decryption, ignoring
9
+ # the possible per-object envelope encryption materials description.
10
+ # @api private
11
+ class DefaultKeyProvider
12
+
13
+ include KeyProvider
14
+
15
+ # @option options [required, OpenSSL::PKey::RSA, String] :encryption_key
16
+ # The master key to use for encrypting objects.
17
+ # @option options [String<JSON>] :materials_description ('{}')
18
+ # A description of the encryption key.
19
+ def initialize(options = {})
20
+ @encryption_materials = Materials.new(
21
+ key: options[:encryption_key],
22
+ description: options[:materials_description] || '{}'
23
+ )
24
+ end
25
+
26
+ # @return [Materials]
27
+ def encryption_materials
28
+ @encryption_materials
29
+ end
30
+
31
+ # @param [String<JSON>] materials_description
32
+ # @return Returns the key given in the constructor.
33
+ def key_for(materials_description)
34
+ @encryption_materials.key
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV2
8
+ # @api private
9
+ class EncryptHandler < Seahorse::Client::Handler
10
+
11
+ def call(context)
12
+ envelope, cipher = context[:encryption][:cipher_provider]
13
+ .encryption_cipher(
14
+ kms_encryption_context: context[:encryption][:kms_encryption_context]
15
+ )
16
+ context[:encryption][:cipher] = cipher
17
+ apply_encryption_envelope(context, envelope)
18
+ apply_encryption_cipher(context, cipher)
19
+ apply_cse_user_agent(context)
20
+ @handler.call(context)
21
+ end
22
+
23
+ private
24
+
25
+ def apply_encryption_envelope(context, envelope)
26
+ if context[:encryption][:envelope_location] == :instruction_file
27
+ suffix = context[:encryption][:instruction_file_suffix]
28
+ context.client.put_object(
29
+ bucket: context.params[:bucket],
30
+ key: context.params[:key] + suffix,
31
+ body: Json.dump(envelope)
32
+ )
33
+ else # :metadata
34
+ context.params[:metadata] ||= {}
35
+ context.params[:metadata].update(envelope)
36
+ end
37
+ end
38
+
39
+ def apply_encryption_cipher(context, cipher)
40
+ io = context.params[:body] || ''
41
+ io = StringIO.new(io) if io.is_a? String
42
+ context.params[:body] = IOEncrypter.new(cipher, io)
43
+ context.params[:metadata] ||= {}
44
+ context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
45
+ if context.params.delete(:content_md5)
46
+ raise ArgumentError, 'Setting content_md5 on client side '\
47
+ 'encrypted objects is deprecated.'
48
+ end
49
+ context.http_response.on_headers do
50
+ context.params[:body].close
51
+ end
52
+ end
53
+
54
+ def apply_cse_user_agent(context)
55
+ if context.config.user_agent_suffix.nil?
56
+ context.config.user_agent_suffix = EC_USER_AGENT
57
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
58
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ module EncryptionV2
6
+ module Errors
7
+
8
+ # Generic DecryptionError
9
+ class DecryptionError < RuntimeError; end
10
+
11
+ class EncryptionError < RuntimeError; end
12
+
13
+ # Raised when attempting to decrypt a legacy (V1) encrypted object
14
+ # when using a security_profile that does not support it.
15
+ class LegacyDecryptionError < DecryptionError
16
+ def initialize(*args)
17
+ msg = 'The requested object is ' \
18
+ 'encrypted with V1 encryption schemas that have been disabled ' \
19
+ 'by client configuration security_profile = :v2. Retry with ' \
20
+ ':v2_and_legacy or re-encrypt the object.'
21
+ super(msg)
22
+ end
23
+ end
24
+
25
+ class CEKAlgMismatchError < DecryptionError
26
+ def initialize(*args)
27
+ msg = 'The content encryption algorithm used at encryption time ' \
28
+ 'does not match the algorithm stored for decryption time. ' \
29
+ 'The object may be altered or corrupted.'
30
+ super(msg)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ module EncryptionV2
6
+ # @api private
7
+ class IOAuthDecrypter
8
+
9
+ # @option options [required, IO#write] :io
10
+ # An IO-like object that responds to {#write}.
11
+ # @option options [required, Integer] :encrypted_content_length
12
+ # The number of bytes to decrypt from the `:io` object.
13
+ # This should be the total size of `:io` minus the length of
14
+ # the cipher auth tag.
15
+ # @option options [required, OpenSSL::Cipher] :cipher An initialized
16
+ # cipher that can be used to decrypt the bytes as they are
17
+ # written to the `:io` object. The cipher should already have
18
+ # its `#auth_tag` set.
19
+ def initialize(options = {})
20
+ @decrypter = IODecrypter.new(options[:cipher], options[:io])
21
+ @max_bytes = options[:encrypted_content_length]
22
+ @bytes_written = 0
23
+ end
24
+
25
+ def write(chunk)
26
+ chunk = truncate_chunk(chunk)
27
+ if chunk.bytesize > 0
28
+ @bytes_written += chunk.bytesize
29
+ @decrypter.write(chunk)
30
+ end
31
+ end
32
+
33
+ def finalize
34
+ @decrypter.finalize
35
+ end
36
+
37
+ def io
38
+ @decrypter.io
39
+ end
40
+
41
+ private
42
+
43
+ def truncate_chunk(chunk)
44
+ if chunk.bytesize + @bytes_written <= @max_bytes
45
+ chunk
46
+ elsif @bytes_written < @max_bytes
47
+ chunk[0..(@max_bytes - @bytes_written - 1)]
48
+ else
49
+ # If the tag was sent over after the full body has been read,
50
+ # we don't want to accidentally append it.
51
+ ""
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ module EncryptionV2
6
+ # @api private
7
+ class IODecrypter
8
+
9
+ # @param [OpenSSL::Cipher] cipher
10
+ # @param [IO#write] io An IO-like object that responds to `#write`.
11
+ def initialize(cipher, 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
16
+ end
17
+
18
+ # @return [#write]
19
+ attr_reader :io
20
+
21
+ def write(chunk)
22
+ # decrypt and write
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
28
+ end
29
+
30
+ def finalize
31
+ @io.write(@cipher.final)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end