aws-sdk-s3 1.207.0 → 1.208.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-s3/client.rb +1 -1
  5. data/lib/aws-sdk-s3/customizations.rb +1 -0
  6. data/lib/aws-sdk-s3/encryption/client.rb +2 -2
  7. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
  8. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
  9. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -0
  10. data/lib/aws-sdk-s3/encryptionV2/client.rb +98 -23
  11. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
  12. data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
  13. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
  14. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
  15. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
  16. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +8 -0
  17. data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
  18. data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
  19. data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
  20. data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
  21. data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
  22. data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
  23. data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
  24. data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
  25. data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
  26. data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
  27. data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
  28. data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
  29. data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
  30. data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
  31. data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
  32. data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
  33. data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
  34. data/lib/aws-sdk-s3.rb +1 -1
  35. metadata +17 -1
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ require 'logger'
6
+
7
+ module Aws
8
+ module S3
9
+ module EncryptionV3
10
+ # @api private
11
+ class DecryptHandler < Seahorse::Client::Handler
12
+ @@warned_response_target_proc = false
13
+
14
+ def call(context)
15
+ attach_http_event_listeners(context)
16
+ apply_cse_user_agent(context)
17
+
18
+ if context[:response_target].is_a?(Proc) && !@@warned_response_target_proc
19
+ @@warned_response_target_proc = true
20
+ warn(':response_target is a Proc, or a block was provided. ' \
21
+ 'Read the entire object to the ' \
22
+ 'end before you start using the decrypted data. This is to ' \
23
+ 'verify that the object has not been modified since it ' \
24
+ 'was encrypted.')
25
+
26
+ end
27
+
28
+ @handler.call(context)
29
+ end
30
+
31
+ private
32
+
33
+ def attach_http_event_listeners(context)
34
+ context.http_response.on_headers(200) do
35
+ ##= ../specification/s3-encryption/decryption.md#key-commitment
36
+ ##% The S3EC MUST validate the algorithm suite used for decryption
37
+ ##% against the key commitment policy before attempting to decrypt the content ciphertext.
38
+ # This is because the commitment policy _always_ allows decrypting committing algorithms.
39
+ # In the else branch we check to see if
40
+ decrypter =
41
+ if Aws::S3::EncryptionV3::Decryption.v3?(context)
42
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
43
+ ##% - If the metadata contains "x-amz-3" and "x-amz-d" and "x-amz-i" then the object MUST be considered an S3EC-encrypted object using the V3 format.
44
+ cipher, envelope = Aws::S3::EncryptionV3::Decryption.decryption_cipher(context)
45
+ Aws::S3::EncryptionV3::Decryption.get_decrypter(context, cipher, envelope)
46
+ else
47
+ if context[:encryption][:commitment_policy] == :require_encrypt_require_decrypt
48
+ ##= ../specification/s3-encryption/decryption.md#key-commitment
49
+ ##% If the commitment policy requires decryption using a committing algorithm suite,
50
+ ##% and the algorithm suite associated with the object does not support key commitment, then the S3EC MUST throw an exception.
51
+ ##= ../specification/s3-encryption/key-commitment.md#commitment-policy
52
+ ##% When the commitment policy is REQUIRE_ENCRYPT_REQUIRE_DECRYPT, the S3EC MUST NOT allow decryption using algorithm suites which do not support key commitment.
53
+ raise Errors::NonCommittingDecryptionError
54
+ end
55
+
56
+ ##= ../specification/s3-encryption/key-commitment.md#commitment-policy
57
+ ##% When the commitment policy is FORBID_ENCRYPT_ALLOW_DECRYPT, the S3EC MUST allow decryption using algorithm suites which do not support key commitment.
58
+ ##= ../specification/s3-encryption/key-commitment.md#commitment-policy
59
+ ##% When the commitment policy is REQUIRE_ENCRYPT_ALLOW_DECRYPT, the S3EC MUST allow decryption using algorithm suites which do not support key commitment.
60
+ cipher, envelope = Aws::S3::EncryptionV2::Decryption.decryption_cipher(context)
61
+ Aws::S3::EncryptionV2::Decryption.get_decrypter(context, cipher, envelope)
62
+ end
63
+ context.http_response.body = decrypter
64
+ end
65
+
66
+ context.http_response.on_success(200) do
67
+ decrypter = context.http_response.body
68
+ decrypter.finalize
69
+ decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
70
+ context.http_response.body = decrypter.io
71
+ end
72
+
73
+ context.http_response.on_error do
74
+ context.http_response.body = context.http_response.body.io if context.http_response.body.respond_to?(:io)
75
+ end
76
+ end
77
+
78
+ def apply_cse_user_agent(context)
79
+ if context.config.user_agent_suffix.nil?
80
+ context.config.user_agent_suffix = EC_USER_AGENT
81
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
82
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
91
+ ##= type=exception
92
+ ##= reason=This has never been supported in Ruby
93
+ ##% This material description string MAY be encoded by the esoteric double-encoding scheme used by the S3 web server.
94
+
95
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
96
+ ##= type=exception
97
+ ##= reason=This has never been supported in Ruby
98
+ ##% This encryption context string MAY be encoded by the esoteric double-encoding scheme used by the S3 web server.
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV3
8
+ # @api private
9
+ class Decryption
10
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
11
+ ##= type=implication
12
+ ##% The "x-amz-" prefix denotes that the metadata is owned by an Amazon product and MUST be prepended to all S3EC metadata mapkeys.
13
+
14
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
15
+ ##% - The mapkey "x-amz-3" MUST be present for V3 format objects.
16
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
17
+ ##% - The mapkey "x-amz-w" MUST be present for V3 format objects.
18
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
19
+ ##= type=implication
20
+ ##% - This mapkey ("x-amz-3") SHOULD be represented by a constant named "ENCRYPTED_DATA_KEY_V3" or similar in the implementation code.
21
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
22
+ ##= type=implication
23
+ ##% - This mapkey ("x-amz-w") SHOULD be represented by a constant named "ENCRYPTED_DATA_KEY_ALGORITHM_V3" or similar in the implementation code.
24
+ ENVELOP_KEY = %w[
25
+ x-amz-3
26
+ x-amz-w
27
+ ].freeze
28
+
29
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
30
+ ##% - The mapkey "x-amz-m" SHOULD be present for V3 format objects that use Raw Keyring Material Description.
31
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
32
+ ##% - The mapkey "x-amz-t" SHOULD be present for V3 format objects that use KMS Encryption Context.
33
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
34
+ ##= type=implication
35
+ ##% - This mapkey ("x-amz-m") SHOULD be represented by a constant named "MAT_DESC_V3" or similar in the implementation code.
36
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
37
+ ##= type=implication
38
+ ##% - This mapkey ("x-amz-t") SHOULD be represented by a constant named "ENCRYPTION_CONTEXT_V3" or similar in the implementation code.
39
+ OPTIONAL_ENVELOP_KEY = %w[
40
+ x-amz-m
41
+ x-amz-t
42
+ ].freeze
43
+
44
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
45
+ ##% - The mapkey "x-amz-c" MUST be present for V3 format objects.
46
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
47
+ ##% - The mapkey "x-amz-d" MUST be present for V3 format objects.
48
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
49
+ ##% - The mapkey "x-amz-i" MUST be present for V3 format objects.
50
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
51
+ ##= type=implication
52
+ ##% - This mapkey ("x-amz-c") SHOULD be represented by a constant named "CONTENT_CIPHER_V3" or similar in the implementation code.
53
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
54
+ ##= type=implication
55
+ ##% - This mapkey ("x-amz-d") SHOULD be represented by a constant named "KEY_COMMITMENT_V3" or similar in the implementation code.
56
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
57
+ ##= type=implication
58
+ ##% - This mapkey ("x-amz-i") SHOULD be represented by a constant named "MESSAGE_ID_V3" or similar in the implementation code.
59
+ METADATA_KEY = %w[
60
+ x-amz-c
61
+ x-amz-d
62
+ x-amz-i
63
+ ].freeze
64
+
65
+ # Reference V2's envelope keys rather than duplicating them
66
+ LEGACY_POSSIBLE_ENVELOPE_KEYS = Aws::S3::EncryptionV2::Decryption::POSSIBLE_ENVELOPE_KEYS
67
+
68
+ POSSIBLE_ENVELOPE_KEYS = (ENVELOP_KEY + METADATA_KEY + OPTIONAL_ENVELOP_KEY + LEGACY_POSSIBLE_ENVELOPE_KEYS).uniq
69
+ REQUIRED_ENVELOPE_KEYS = (ENVELOP_KEY + METADATA_KEY).uniq
70
+
71
+ POSSIBLE_WRAPPING_FORMATS = %w[
72
+ 01
73
+ 02
74
+ 11
75
+ 12
76
+ 21
77
+ 22
78
+ ].freeze
79
+
80
+ POSSIBLE_ENCRYPTION_FORMATS = %w[
81
+ 115
82
+ ].freeze
83
+
84
+ class << self
85
+ def v3?(context)
86
+ context.http_response.headers.key?('x-amz-meta-x-amz-i')
87
+ end
88
+
89
+ def decryption_cipher(context)
90
+ if (envelope = get_encryption_envelope(context))
91
+ cipher = context[:encryption][:v3_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
+ # This method fetches the tag from the end of the object by
103
+ # making a GET Object w/range request. This auth tag is used
104
+ # to initialize the cipher, and the decrypter truncates the
105
+ # auth tag from the body when writing the final bytes.
106
+ def get_decrypter(context, cipher, _envelope)
107
+ http_resp = context.http_response
108
+ content_length = http_resp.headers['content-length'].to_i
109
+
110
+ # The encrypted object contains both the cipher text
111
+ # plus a trailing auth tag.
112
+ # The trailing auth tag will be accumulated and added to the cipher.auth_tag.
113
+ IOAuthDecrypter.new(
114
+ io: http_resp.body,
115
+ encrypted_content_length: content_length - AES_GCM_TAG_LEN_BYTES,
116
+ cipher: cipher
117
+ )
118
+ end
119
+
120
+ def get_encryption_envelope(context)
121
+ # Get initial envelope data from :envelope_location
122
+ envelope =
123
+ if context[:encryption][:envelope_location] == :metadata
124
+ envelope_from_metadata(context)
125
+ else
126
+ envelope_from_instr_file(context)
127
+ end
128
+
129
+ # If empty or incomplete, get/merge data from secondary source
130
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
131
+ ##% If the object matches none of the V1/V2/V3 formats, the S3EC MUST attempt to get the instruction file.
132
+ if envelope.nil? || envelope.empty? || !complete_envelop?(envelope)
133
+ secondary =
134
+ if context[:encryption][:envelope_location] == :metadata
135
+ envelope_from_instr_file(context)
136
+ else
137
+ envelope_from_metadata(context)
138
+ end
139
+ # If we attempted to read a non-existent instruction file,
140
+ # then envelope would be nil,
141
+ # but we may find the information we need in the metadata.
142
+ if envelope && secondary
143
+ envelope.merge!(secondary)
144
+ elsif secondary
145
+ envelope = secondary
146
+ end
147
+ end
148
+
149
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#object-metadata
150
+ ##% If the S3EC does not support decoding the S3 Server's "double encoding" then it MUST return the content metadata untouched.
151
+ v3_envelope?(envelope)
152
+ end
153
+
154
+ def complete_envelop?(possible_envelope)
155
+ # V3 envelops always store some information in metadata
156
+ # If we look at the metadata, we may still need to check the instruction file
157
+ # Similarly, if we start checking the instruction file,
158
+ # we sill need to get the message id and commitment key from the metadata
159
+ envelop_count = ENVELOP_KEY.count { |key| possible_envelope.key?(key) }
160
+ metadata_count = METADATA_KEY.count { |key| possible_envelope.key?(key) }
161
+
162
+ # If we have all keys, we are done
163
+ (envelop_count == ENVELOP_KEY.size && metadata_count == METADATA_KEY.size) ||
164
+ # If we have 0 keys, then this is done too.
165
+ # Because it means we are not a v3 committing message.
166
+ (envelop_count.zero? && metadata_count.zero?)
167
+ end
168
+
169
+ def envelope_from_metadata(context)
170
+ POSSIBLE_ENVELOPE_KEYS.filter_map do |suffix|
171
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
172
+ ##= type=exception
173
+ ##= reason=Ruby is reading the headers directly
174
+ ##% The "x-amz-meta-" prefix is automatically added by the S3 server and MUST NOT be included in implementation code.
175
+ if (value = context.http_response.headers["x-amz-meta-#{suffix}"])
176
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#object-metadata
177
+ ##= type=exception
178
+ ##= reason=This has never been supported in Ruby
179
+ ##% The S3EC SHOULD support decoding the S3 Server's "double encoding".
180
+
181
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#object-metadata
182
+ ##% If the S3EC does not support decoding the S3 Server's "double encoding" then it MUST return the content metadata untouched.
183
+ [suffix, value]
184
+ end
185
+ end.to_h
186
+ end
187
+
188
+ def envelope_from_instr_file(context)
189
+ suffix = context[:encryption][:instruction_file_suffix]
190
+ possible_envelope = Json.load(context.client.get_object(
191
+ bucket: context.params[:bucket],
192
+ key: context.params[:key] + suffix
193
+ ).body.read)
194
+ unless (keys = possible_envelope.keys & METADATA_KEY).empty?
195
+ msg = "unsupported metadata key found in instruction file: #{keys.join(', ')}"
196
+ raise Errors::DecryptionError, msg
197
+ end
198
+ possible_envelope
199
+ rescue S3::Errors::ServiceError, Json::ParseError
200
+ nil
201
+ end
202
+
203
+ def v3_envelope?(possible_envelope)
204
+ unless (keys = possible_envelope.keys & LEGACY_POSSIBLE_ENVELOPE_KEYS).empty?
205
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
206
+ ##% If there are multiple mapkeys which are meant to be exclusive, such as "x-amz-key", "x-amz-key-v2", and "x-amz-3" then the S3EC SHOULD throw an exception.
207
+ msg = "legacy metadata key found: #{keys.join(', ')}"
208
+ raise Errors::DecryptionError, msg
209
+ end
210
+
211
+ unless POSSIBLE_ENCRYPTION_FORMATS.include? possible_envelope['x-amz-c']
212
+ alg = possible_envelope['x-amz-c'].inspect
213
+ msg = "unsupported content encrypting key (cek) format: #{alg} #{possible_envelope.inspect}"
214
+ raise Errors::DecryptionError, msg
215
+ end
216
+ unless POSSIBLE_WRAPPING_FORMATS.include? possible_envelope['x-amz-w']
217
+ alg = possible_envelope['x-amz-w'].inspect
218
+ msg = "unsupported key wrapping algorithm: #{alg}"
219
+ raise Errors::DecryptionError, msg
220
+ end
221
+ unless (missing_keys = REQUIRED_ENVELOPE_KEYS - possible_envelope.keys).empty?
222
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
223
+ ##% In general, if there is any deviation from the above format, with the exception of additional unrelated mapkeys, then the S3EC SHOULD throw an exception.
224
+ msg = "incomplete v3 encryption envelope:\n"
225
+ msg += " missing: #{missing_keys.join(',')}\n"
226
+ raise Errors::DecryptionError, msg
227
+ end
228
+ possible_envelope
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
237
+ ##= type=exception
238
+ ##= reason=This has never been supported in Ruby
239
+ ##% This material description string MAY be encoded by the esoteric double-encoding scheme used by the S3 web server.
240
+
241
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
242
+ ##= type=exception
243
+ ##= reason=This has never been supported in Ruby
244
+ ##% This encryption context string MAY be encoded by the esoteric double-encoding scheme used by the S3 web server.
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV3
8
+ # @api private
9
+ class DefaultCipherProvider
10
+ def initialize(options = {})
11
+ @key_provider = options[:key_provider]
12
+ @key_wrap_schema = validate_key_wrap(
13
+ options[:key_wrap_schema],
14
+ @key_provider.encryption_materials.key
15
+ )
16
+ ##= ../specification/s3-encryption/encryption.md#content-encryption
17
+ ##% The S3EC MUST use the encryption algorithm configured during [client](./client.md) initialization.
18
+ @content_encryption_schema = Utils.validate_cek(
19
+ options[:content_encryption_schema]
20
+ )
21
+ end
22
+
23
+ attr_reader :key_provider
24
+
25
+ # @return [Array<Hash,Cipher>] Creates an returns a new encryption
26
+ # envelope and encryption cipher.
27
+ def encryption_cipher(options = {})
28
+ validate_options(options)
29
+ data_key = Utils.generate_data_key
30
+ cipher, message_id, commitment_key = Utils.generate_alg_aes_256_gcm_hkdf_sha512_commit_key_cipher(data_key)
31
+ enc_key =
32
+ if @key_provider.encryption_materials.key.is_a? OpenSSL::PKey::RSA
33
+ encode64(
34
+ encrypt_rsa(data_key, @content_encryption_schema)
35
+ )
36
+ else
37
+ encode64(
38
+ encrypt_aes_gcm(data_key, @content_encryption_schema)
39
+ )
40
+ end
41
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
42
+ ##% Objects encrypted with ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY MUST use the V3 message format version only.
43
+ envelope = {
44
+ 'x-amz-3' => enc_key,
45
+ 'x-amz-c' => @content_encryption_schema,
46
+ 'x-amz-w' => @key_wrap_schema,
47
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
48
+ ##% The Material Description MUST be used for wrapping algorithms `AES/GCM` (`02`) and `RSA-OAEP-SHA1` (`22`).
49
+ 'x-amz-m' => materials_description,
50
+ 'x-amz-d' => encode64(commitment_key),
51
+ 'x-amz-i' => encode64(message_id)
52
+ }
53
+
54
+ [envelope, cipher]
55
+ end
56
+
57
+ # @return [Cipher] Given an encryption envelope, returns a
58
+ # decryption cipher.
59
+ def decryption_cipher(envelope, options = {})
60
+ validate_options(options)
61
+ wrapping_key = @key_provider.key_for(envelope['x-amz-m'])
62
+
63
+ data_key =
64
+ case envelope['x-amz-w']
65
+ when '02'
66
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
67
+ ##% - The wrapping algorithm value "02" MUST be translated to AES/GCM upon retrieval, and vice versa on write.
68
+ if wrapping_key.is_a? OpenSSL::PKey::RSA
69
+ raise ArgumentError, 'Key mismatch - Client is configured' \
70
+ ' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
71
+ end
72
+ Utils.decrypt_aes_gcm(wrapping_key,
73
+ decode64(envelope['x-amz-3']),
74
+ @content_encryption_schema)
75
+ when '22'
76
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
77
+ ##% - The wrapping algorithm value "22" MUST be translated to RSA-OAEP-SHA1 upon retrieval, and vice versa on write.
78
+ unless wrapping_key.is_a? OpenSSL::PKey::RSA
79
+ raise ArgumentError, 'Key mismatch - Client is configured' \
80
+ ' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
81
+ end
82
+ key, cek_alg = Utils.decrypt_rsa(wrapping_key, decode64(envelope['x-amz-3']))
83
+ raise Errors::CEKAlgMismatchError unless cek_alg == @content_encryption_schema
84
+
85
+ key
86
+ when '12'
87
+ raise ArgumentError, 'Key mismatch - Client is configured' \
88
+ ' with a user provided key and the x-amz-w is' \
89
+ ' kms+context. Please configure the client with the' \
90
+ ' required kms_key_id'
91
+ else
92
+ raise ArgumentError, 'Unsupported wrapping algorithm: ' \
93
+ "#{envelope['x-amz-w']}"
94
+ end
95
+
96
+ message_id = decode64(envelope['x-amz-i'])
97
+ commitment_key = decode64(envelope['x-amz-d'])
98
+
99
+ Utils.derive_alg_aes_256_gcm_hkdf_sha512_commit_key_cipher(data_key, message_id, commitment_key)
100
+ end
101
+
102
+ private
103
+
104
+ # Validate that the key_wrap_schema
105
+ # is valid, supported and matches the provided key.
106
+ # Returns the string version for the x-amz-key-wrap-alg
107
+ def validate_key_wrap(key_wrap_schema, key)
108
+ if key.is_a? OpenSSL::PKey::RSA
109
+ unless key_wrap_schema == :rsa_oaep_sha1
110
+ raise ArgumentError, ':key_wrap_schema must be set to :rsa_oaep_sha1 for RSA keys.'
111
+ end
112
+ else
113
+ unless key_wrap_schema == :aes_gcm
114
+ raise ArgumentError, ':key_wrap_schema must be set to :aes_gcm for AES keys.'
115
+ end
116
+ end
117
+
118
+ case key_wrap_schema
119
+ when :rsa_oaep_sha1 then '22'
120
+ when :aes_gcm then '02'
121
+ when :kms_context
122
+ raise ArgumentError, 'A kms_key_id is required when using :kms_context.'
123
+ else
124
+ raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
125
+ end
126
+ end
127
+
128
+ def encrypt_aes_gcm(data, auth_data)
129
+ Utils.encrypt_aes_gcm(@key_provider.encryption_materials.key, data, auth_data)
130
+ end
131
+
132
+ def encrypt_rsa(data, auth_data)
133
+ Utils.encrypt_rsa(@key_provider.encryption_materials.key, data, auth_data)
134
+ end
135
+
136
+ def materials_description
137
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
138
+ ##% If the mapkey x-amz-m is not present, the default Material Description value MUST be set to an empty map (`{}`).
139
+ @key_provider.encryption_materials.description || {}
140
+ end
141
+
142
+ def encode64(str)
143
+ Base64.encode64(str).split("\n") * ''
144
+ end
145
+
146
+ def decode64(str)
147
+ Base64.decode64(str)
148
+ end
149
+
150
+ def validate_options(options)
151
+ return if options[:kms_encryption_context].nil?
152
+
153
+ raise ArgumentError, 'Cannot provide :kms_encryption_context ' \
154
+ 'with non KMS client.'
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ module EncryptionV3
6
+ # The default key provider is constructed with a single key
7
+ # that is used for both encryption and decryption, ignoring
8
+ # the possible per-object envelope encryption materials description.
9
+ # @api private
10
+ class DefaultKeyProvider
11
+ include KeyProvider
12
+
13
+ # @option options [required, OpenSSL::PKey::RSA, String] :encryption_key
14
+ # The master key to use for encrypting objects.
15
+ # @option options [String<JSON>] :materials_description ('{}')
16
+ # A description of the encryption key.
17
+ def initialize(options = {})
18
+ @encryption_materials = Materials.new(
19
+ key: options[:encryption_key],
20
+ description: options[:materials_description] || '{}'
21
+ )
22
+ end
23
+
24
+ # @return [Materials]
25
+ attr_reader :encryption_materials
26
+
27
+ # @param [String<JSON>] materials_description
28
+ # @return Returns the key given in the constructor.
29
+ def key_for(_materials_description)
30
+ @encryption_materials.key
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV3
8
+ # @api private
9
+ class EncryptHandler < Seahorse::Client::Handler
10
+ def call(context)
11
+ envelope, cipher = context[:encryption][:cipher_provider]
12
+ .encryption_cipher(
13
+ kms_encryption_context: context[:encryption][:kms_encryption_context]
14
+ )
15
+ context[:encryption][:cipher] = cipher
16
+ apply_encryption_envelope(context, envelope)
17
+ apply_encryption_cipher(context, cipher)
18
+ apply_cse_user_agent(context)
19
+ @handler.call(context)
20
+ end
21
+
22
+ private
23
+
24
+ def apply_encryption_envelope(context, envelope)
25
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
26
+ ##% The S3EC MUST support writing some or all (depending on format) content metadata to an Instruction File.
27
+ if context[:encryption][:envelope_location] == :instruction_file
28
+ suffix = context[:encryption][:instruction_file_suffix]
29
+ instruction_envelop, metadata_envelop = split_for_instruction_file(envelope)
30
+
31
+ context.client.put_object(
32
+ bucket: context.params[:bucket],
33
+ key: context.params[:key] + suffix,
34
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#instruction-file
35
+ ##% The content metadata stored in the Instruction File MUST be serialized to a JSON string.
36
+ ##% The serialized JSON string MUST be the only contents of the Instruction File.
37
+ body: Json.dump(instruction_envelop)
38
+ )
39
+ context.params[:metadata] ||= {}
40
+ context.params[:metadata].update(metadata_envelop)
41
+ else
42
+ context.params[:metadata] ||= {}
43
+ context.params[:metadata].update(envelope)
44
+ end
45
+ end
46
+
47
+ def apply_encryption_cipher(context, cipher)
48
+ io = context.params[:body] || ''
49
+ io = StringIO.new(io) if io.is_a? String
50
+ context.params[:body] = IOEncrypter.new(cipher, io)
51
+ context.params[:metadata] ||= {}
52
+ # Leaving this in because even though this is years old
53
+ # it is still important to *not* MD5 the plaintext
54
+ # If there exists any old integration points still doing this
55
+ # that upgrade from 1 to 3 this needs to still fail.
56
+ if context.params.delete(:content_md5)
57
+ raise ArgumentError, 'Setting content_md5 on client side '\
58
+ 'encrypted objects is deprecated.'
59
+ end
60
+ context.http_response.on_headers do
61
+ context.params[:body].close
62
+ end
63
+ end
64
+
65
+ def apply_cse_user_agent(context)
66
+ if context.config.user_agent_suffix.nil?
67
+ context.config.user_agent_suffix = EC_USER_AGENT
68
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
69
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
70
+ end
71
+ end
72
+
73
+ def split_for_instruction_file(envelop)
74
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
75
+ ##% In the V3 format, the mapkeys "x-amz-c", "x-amz-d", and "x-amz-i" MUST be stored exclusively in the Object Metadata.
76
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#v3-instruction-files
77
+ ##% - The V3 message format MUST store the mapkey "x-amz-c" and its value in the Object Metadata when writing with an Instruction File.
78
+ ##% - The V3 message format MUST NOT store the mapkey "x-amz-c" and its value in the Instruction File.
79
+ ##% - The V3 message format MUST store the mapkey "x-amz-d" and its value in the Object Metadata when writing with an Instruction File.
80
+ ##% - The V3 message format MUST NOT store the mapkey "x-amz-d" and its value in the Instruction File.
81
+ ##% - The V3 message format MUST store the mapkey "x-amz-i" and its value in the Object Metadata when writing with an Instruction File.
82
+ ##% - The V3 message format MUST NOT store the mapkey "x-amz-i" and its value in the Instruction File.
83
+ metadata_envelop = envelop.select { |k, _v| Decryption::METADATA_KEY.include?(k) }
84
+ # Exclude the metadata keys rather than include the envelop keys
85
+ # because there might be additional information
86
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#v3-instruction-files
87
+ ##% - The V3 message format MUST store the mapkey "x-amz-3" and its value in the Instruction File.
88
+ ##% - The V3 message format MUST store the mapkey "x-amz-w" and its value in the Instruction File.
89
+ ##% - The V3 message format MUST store the mapkey "x-amz-m" and its value (when present in the content metadata) in the Instruction File.
90
+ ##% - The V3 message format MUST store the mapkey "x-amz-t" and its value (when present in the content metadata) in the Instruction File.
91
+ instruction_envelop = envelop.reject { |k, _v| Decryption::METADATA_KEY.include?(k) }
92
+
93
+ [instruction_envelop, metadata_envelop]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end