aws-sdk-s3 1.30.0 → 1.114.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +903 -0
  3. data/LICENSE.txt +202 -0
  4. data/VERSION +1 -0
  5. data/lib/aws-sdk-s3/arn/access_point_arn.rb +69 -0
  6. data/lib/aws-sdk-s3/arn/multi_region_access_point_arn.rb +68 -0
  7. data/lib/aws-sdk-s3/arn/object_lambda_arn.rb +69 -0
  8. data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +74 -0
  9. data/lib/aws-sdk-s3/bucket.rb +377 -77
  10. data/lib/aws-sdk-s3/bucket_acl.rb +57 -14
  11. data/lib/aws-sdk-s3/bucket_cors.rb +67 -13
  12. data/lib/aws-sdk-s3/bucket_lifecycle.rb +54 -15
  13. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +56 -15
  14. data/lib/aws-sdk-s3/bucket_logging.rb +52 -15
  15. data/lib/aws-sdk-s3/bucket_notification.rb +44 -15
  16. data/lib/aws-sdk-s3/bucket_policy.rb +51 -13
  17. data/lib/aws-sdk-s3/bucket_region_cache.rb +2 -0
  18. data/lib/aws-sdk-s3/bucket_request_payment.rb +51 -12
  19. data/lib/aws-sdk-s3/bucket_tagging.rb +59 -13
  20. data/lib/aws-sdk-s3/bucket_versioning.rb +118 -12
  21. data/lib/aws-sdk-s3/bucket_website.rb +66 -13
  22. data/lib/aws-sdk-s3/client.rb +9016 -669
  23. data/lib/aws-sdk-s3/client_api.rb +786 -2
  24. data/lib/aws-sdk-s3/customizations/bucket.rb +59 -16
  25. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  26. data/lib/aws-sdk-s3/customizations/object.rb +200 -62
  27. data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
  28. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  29. data/lib/aws-sdk-s3/customizations.rb +4 -1
  30. data/lib/aws-sdk-s3/encryption/client.rb +23 -6
  31. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
  32. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  33. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  34. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  35. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  36. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  38. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  39. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  40. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +34 -3
  41. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  42. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  43. data/lib/aws-sdk-s3/encryption.rb +4 -0
  44. data/lib/aws-sdk-s3/encryptionV2/client.rb +566 -0
  45. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +222 -0
  46. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  47. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  48. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +65 -0
  49. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  50. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  51. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  52. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  53. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  54. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +169 -0
  55. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  56. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  57. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  58. data/lib/aws-sdk-s3/errors.rb +123 -1
  59. data/lib/aws-sdk-s3/event_streams.rb +20 -7
  60. data/lib/aws-sdk-s3/file_downloader.rb +17 -10
  61. data/lib/aws-sdk-s3/file_part.rb +11 -6
  62. data/lib/aws-sdk-s3/file_uploader.rb +33 -14
  63. data/lib/aws-sdk-s3/legacy_signer.rb +17 -25
  64. data/lib/aws-sdk-s3/multipart_file_uploader.rb +78 -19
  65. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +20 -7
  66. data/lib/aws-sdk-s3/multipart_upload.rb +178 -28
  67. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  68. data/lib/aws-sdk-s3/multipart_upload_part.rb +236 -43
  69. data/lib/aws-sdk-s3/object.rb +848 -162
  70. data/lib/aws-sdk-s3/object_acl.rb +81 -20
  71. data/lib/aws-sdk-s3/object_copier.rb +2 -0
  72. data/lib/aws-sdk-s3/object_multipart_copier.rb +2 -0
  73. data/lib/aws-sdk-s3/object_summary.rb +615 -144
  74. data/lib/aws-sdk-s3/object_version.rb +161 -68
  75. data/lib/aws-sdk-s3/plugins/accelerate.rb +38 -38
  76. data/lib/aws-sdk-s3/plugins/arn.rb +254 -0
  77. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +8 -8
  78. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +25 -3
  79. data/lib/aws-sdk-s3/plugins/dualstack.rb +38 -33
  80. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +4 -4
  81. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  82. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +11 -3
  83. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +73 -0
  84. data/lib/aws-sdk-s3/plugins/location_constraint.rb +2 -0
  85. data/lib/aws-sdk-s3/plugins/md5s.rb +34 -30
  86. data/lib/aws-sdk-s3/plugins/object_lambda_endpoint.rb +25 -0
  87. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  88. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  89. data/lib/aws-sdk-s3/plugins/s3_signer.rb +95 -36
  90. data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +31 -0
  91. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  92. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +139 -0
  93. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  94. data/lib/aws-sdk-s3/presigned_post.rb +110 -51
  95. data/lib/aws-sdk-s3/presigner.rb +168 -66
  96. data/lib/aws-sdk-s3/resource.rb +41 -5
  97. data/lib/aws-sdk-s3/types.rb +8652 -1146
  98. data/lib/aws-sdk-s3/waiters.rb +67 -1
  99. data/lib/aws-sdk-s3.rb +12 -6
  100. metadata +38 -13
@@ -0,0 +1,222 @@
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
+ range: "bytes=-#{auth_tag_length}"
177
+ ).body.read
178
+
179
+ cipher.auth_tag = auth_tag
180
+ cipher.auth_data = ''
181
+
182
+ # The encrypted object contains both the cipher text
183
+ # plus a trailing auth tag.
184
+ IOAuthDecrypter.new(
185
+ io: http_resp.body,
186
+ encrypted_content_length: content_length - auth_tag_length,
187
+ cipher: cipher)
188
+ end
189
+
190
+ def body_contains_auth_tag?(envelope)
191
+ AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
192
+ end
193
+
194
+ # Determine the auth tag length from the algorithm
195
+ # Validate it against the value provided in the x-amz-tag-len
196
+ # Return the tag length in bytes
197
+ def auth_tag_length(envelope)
198
+ tag_length =
199
+ case envelope['x-amz-cek-alg']
200
+ when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
201
+ else
202
+ raise ArgumentError, 'Unsupported cek-alg: ' \
203
+ "#{envelope['x-amz-cek-alg']}"
204
+ end
205
+ if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
206
+ raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
207
+ end
208
+ tag_length
209
+ end
210
+
211
+ def apply_cse_user_agent(context)
212
+ if context.config.user_agent_suffix.nil?
213
+ context.config.user_agent_suffix = EC_USER_AGENT
214
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
215
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
216
+ end
217
+ end
218
+
219
+ end
220
+ end
221
+ end
222
+ 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