aws-sdk-s3 1.10.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 (153) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1517 -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 +1062 -99
  8. data/lib/aws-sdk-s3/bucket_acl.rb +67 -17
  9. data/lib/aws-sdk-s3/bucket_cors.rb +80 -17
  10. data/lib/aws-sdk-s3/bucket_lifecycle.rb +71 -19
  11. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +126 -20
  12. data/lib/aws-sdk-s3/bucket_logging.rb +68 -18
  13. data/lib/aws-sdk-s3/bucket_notification.rb +56 -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 -17
  18. data/lib/aws-sdk-s3/bucket_versioning.rb +166 -17
  19. data/lib/aws-sdk-s3/bucket_website.rb +78 -17
  20. data/lib/aws-sdk-s3/client.rb +20068 -3879
  21. data/lib/aws-sdk-s3/client_api.rb +1957 -209
  22. data/lib/aws-sdk-s3/customizations/bucket.rb +57 -38
  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 +338 -68
  26. data/lib/aws-sdk-s3/customizations/object_summary.rb +17 -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 +30 -27
  31. data/lib/aws-sdk-s3/default_executor.rb +103 -0
  32. data/lib/aws-sdk-s3/encryption/client.rb +29 -8
  33. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
  34. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +45 -5
  35. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  36. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +15 -2
  37. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  38. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +11 -3
  39. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  40. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  41. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  42. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +48 -11
  43. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  44. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  45. data/lib/aws-sdk-s3/encryption.rb +4 -0
  46. data/lib/aws-sdk-s3/encryptionV2/client.rb +645 -0
  47. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +68 -0
  48. data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
  49. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +187 -0
  50. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  51. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +67 -0
  52. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  53. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  54. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  55. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +75 -0
  56. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  57. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +181 -0
  58. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  59. data/lib/aws-sdk-s3/encryptionV2/utils.rb +108 -0
  60. data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
  61. data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
  62. data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
  63. data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
  64. data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
  65. data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
  66. data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
  67. data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
  68. data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
  69. data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
  70. data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
  71. data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
  72. data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
  73. data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
  74. data/lib/aws-sdk-s3/encryption_v2.rb +24 -0
  75. data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
  76. data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
  77. data/lib/aws-sdk-s3/endpoint_provider.rb +886 -0
  78. data/lib/aws-sdk-s3/endpoints.rb +1544 -0
  79. data/lib/aws-sdk-s3/errors.rb +181 -1
  80. data/lib/aws-sdk-s3/event_streams.rb +69 -0
  81. data/lib/aws-sdk-s3/express_credentials.rb +55 -0
  82. data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
  83. data/lib/aws-sdk-s3/file_downloader.rb +261 -82
  84. data/lib/aws-sdk-s3/file_part.rb +16 -13
  85. data/lib/aws-sdk-s3/file_uploader.rb +37 -22
  86. data/lib/aws-sdk-s3/legacy_signer.rb +19 -26
  87. data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
  88. data/lib/aws-sdk-s3/multipart_file_uploader.rb +142 -80
  89. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +191 -0
  90. data/lib/aws-sdk-s3/multipart_upload.rb +342 -31
  91. data/lib/aws-sdk-s3/multipart_upload_error.rb +5 -4
  92. data/lib/aws-sdk-s3/multipart_upload_part.rb +387 -47
  93. data/lib/aws-sdk-s3/object.rb +2733 -204
  94. data/lib/aws-sdk-s3/object_acl.rb +112 -25
  95. data/lib/aws-sdk-s3/object_copier.rb +9 -5
  96. data/lib/aws-sdk-s3/object_multipart_copier.rb +50 -23
  97. data/lib/aws-sdk-s3/object_summary.rb +2265 -181
  98. data/lib/aws-sdk-s3/object_version.rb +542 -74
  99. data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
  100. data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
  101. data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
  102. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +7 -43
  103. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +20 -3
  104. data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
  105. data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
  106. data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
  107. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
  108. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
  109. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  110. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +62 -17
  111. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
  112. data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
  113. data/lib/aws-sdk-s3/plugins/md5s.rb +14 -67
  114. data/lib/aws-sdk-s3/plugins/redirects.rb +5 -1
  115. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  116. data/lib/aws-sdk-s3/plugins/s3_signer.rb +67 -93
  117. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  118. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +137 -0
  119. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +4 -1
  120. data/lib/aws-sdk-s3/presigned_post.rb +160 -99
  121. data/lib/aws-sdk-s3/presigner.rb +178 -81
  122. data/lib/aws-sdk-s3/resource.rb +164 -15
  123. data/lib/aws-sdk-s3/transfer_manager.rb +303 -0
  124. data/lib/aws-sdk-s3/types.rb +15981 -4168
  125. data/lib/aws-sdk-s3/waiters.rb +67 -1
  126. data/lib/aws-sdk-s3.rb +46 -31
  127. data/sig/bucket.rbs +231 -0
  128. data/sig/bucket_acl.rbs +78 -0
  129. data/sig/bucket_cors.rbs +69 -0
  130. data/sig/bucket_lifecycle.rbs +88 -0
  131. data/sig/bucket_lifecycle_configuration.rbs +115 -0
  132. data/sig/bucket_logging.rbs +76 -0
  133. data/sig/bucket_notification.rbs +114 -0
  134. data/sig/bucket_policy.rbs +59 -0
  135. data/sig/bucket_request_payment.rbs +54 -0
  136. data/sig/bucket_tagging.rbs +65 -0
  137. data/sig/bucket_versioning.rbs +77 -0
  138. data/sig/bucket_website.rbs +93 -0
  139. data/sig/client.rbs +2612 -0
  140. data/sig/customizations/bucket.rbs +19 -0
  141. data/sig/customizations/object.rbs +38 -0
  142. data/sig/customizations/object_summary.rbs +35 -0
  143. data/sig/errors.rbs +44 -0
  144. data/sig/multipart_upload.rbs +120 -0
  145. data/sig/multipart_upload_part.rbs +109 -0
  146. data/sig/object.rbs +464 -0
  147. data/sig/object_acl.rbs +86 -0
  148. data/sig/object_summary.rbs +347 -0
  149. data/sig/object_version.rbs +143 -0
  150. data/sig/resource.rbs +141 -0
  151. data/sig/types.rbs +2899 -0
  152. data/sig/waiters.rbs +95 -0
  153. metadata +97 -14
@@ -0,0 +1,68 @@
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
+ def call(context)
13
+ attach_http_event_listeners(context)
14
+ apply_cse_user_agent(context)
15
+
16
+ if context[:response_target].is_a?(Proc) && !@@warned_response_target_proc
17
+ @@warned_response_target_proc = true
18
+ warn(':response_target is a Proc, or a block was provided. ' \
19
+ 'Read the entire object to the ' \
20
+ 'end before you start using the decrypted data. This is to ' \
21
+ 'verify that the object has not been modified since it ' \
22
+ 'was encrypted.')
23
+
24
+ end
25
+
26
+ @handler.call(context)
27
+ end
28
+
29
+ private
30
+
31
+ def attach_http_event_listeners(context)
32
+ context.http_response.on_headers(200) do
33
+ decrypter = if Aws::S3::EncryptionV3::Decryption.v3?(context)
34
+ cipher, envelope = Aws::S3::EncryptionV3::Decryption.decryption_cipher(context)
35
+ Aws::S3::EncryptionV3::Decryption.get_decrypter(context, cipher, envelope)
36
+ else
37
+ cipher, envelope = Aws::S3::EncryptionV2::Decryption.decryption_cipher(context)
38
+ Aws::S3::EncryptionV2::Decryption.get_decrypter(context, cipher, envelope)
39
+ end
40
+ context.http_response.body = decrypter
41
+ end
42
+
43
+ context.http_response.on_success(200) do
44
+ decrypter = context.http_response.body
45
+ decrypter.finalize
46
+ decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
47
+ context.http_response.body = decrypter.io
48
+ end
49
+
50
+ context.http_response.on_error do
51
+ if context.http_response.body.respond_to?(:io)
52
+ context.http_response.body = context.http_response.body.io
53
+ end
54
+ end
55
+ end
56
+
57
+ def apply_cse_user_agent(context)
58
+ if context.config.user_agent_suffix.nil?
59
+ context.config.user_agent_suffix = EC_USER_AGENT
60
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
61
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module S3
7
+ module EncryptionV2
8
+ # @api private
9
+ class Decryption
10
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
11
+ ##% - The mapkey "x-amz-key" MUST be present for V1 format objects.
12
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
13
+ ##% - The mapkey "x-amz-iv" MUST be present for V1 format objects.
14
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
15
+ ##% - The mapkey "x-amz-matdesc" MUST be present for V1 format objects.
16
+ V1_ENVELOPE_KEYS = %w[
17
+ x-amz-key
18
+ x-amz-iv
19
+ x-amz-matdesc
20
+ ].freeze
21
+
22
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
23
+ ##% - The mapkey "x-amz-key-v2" MUST be present for V2 format objects.
24
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
25
+ ##% - The mapkey "x-amz-iv" MUST be present for V2 format objects.
26
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
27
+ ##% - The mapkey "x-amz-cek-alg" MUST be present for V2 format objects.
28
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
29
+ ##% - The mapkey "x-amz-wrap-alg" MUST be present for V2 format objects.
30
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
31
+ ##% - The mapkey "x-amz-matdesc" MUST be present for V2 format objects.
32
+ V2_ENVELOPE_KEYS = %w[
33
+ x-amz-key-v2
34
+ x-amz-iv
35
+ x-amz-cek-alg
36
+ x-amz-wrap-alg
37
+ x-amz-matdesc
38
+ ].freeze
39
+
40
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
41
+ ##= type=exception
42
+ ##= reason=The implementation treats this as optional, but verifies its value.
43
+ ##% - The mapkey "x-amz-tag-len" MUST be present for V2 format objects.
44
+ V2_OPTIONAL_KEYS = %w[x-amz-tag-len].freeze
45
+
46
+ POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS + V2_ENVELOPE_KEYS + V2_OPTIONAL_KEYS).uniq
47
+
48
+ POSSIBLE_WRAPPING_FORMATS = %w[
49
+ AES/GCM
50
+ kms
51
+ kms+context
52
+ RSA-OAEP-SHA1
53
+ ].freeze
54
+
55
+ POSSIBLE_ENCRYPTION_FORMATS = %w[
56
+ AES/GCM/NoPadding
57
+ AES/CBC/PKCS5Padding
58
+ AES/CBC/PKCS7Padding
59
+ ].freeze
60
+
61
+ AUTH_REQUIRED_CEK_ALGS = %w[AES/GCM/NoPadding].freeze
62
+
63
+ class << self
64
+ def decryption_cipher(context)
65
+ if (envelope = get_encryption_envelope(context))
66
+ cipher = context[:encryption][:cipher_provider]
67
+ .decryption_cipher(
68
+ envelope,
69
+ context[:encryption]
70
+ )
71
+ [cipher, envelope]
72
+ else
73
+ raise Errors::DecryptionError, 'unable to locate encryption envelope'
74
+ end
75
+ end
76
+
77
+ def get_decrypter(context, cipher, envelope)
78
+ if body_contains_auth_tag?(envelope)
79
+ authenticated_decrypter(context, cipher, envelope)
80
+ else
81
+ IODecrypter.new(cipher, context.http_response.body)
82
+ end
83
+ end
84
+
85
+ def get_encryption_envelope(context)
86
+ if context[:encryption][:envelope_location] == :metadata
87
+ envelope_from_metadata(context) || envelope_from_instr_file(context)
88
+ else
89
+ envelope_from_instr_file(context) || envelope_from_metadata(context)
90
+ end
91
+ end
92
+
93
+ def envelope_from_metadata(context)
94
+ possible_envelope = {}
95
+ POSSIBLE_ENVELOPE_KEYS.each do |suffix|
96
+ if (value = context.http_response.headers["x-amz-meta-#{suffix}"])
97
+ possible_envelope[suffix] = value
98
+ end
99
+ end
100
+ extract_envelope(possible_envelope)
101
+ end
102
+
103
+ def envelope_from_instr_file(context)
104
+ suffix = context[:encryption][:instruction_file_suffix]
105
+ possible_envelope = Json.load(context.client.get_object(
106
+ bucket: context.params[:bucket],
107
+ key: context.params[:key] + suffix
108
+ ).body.read)
109
+ extract_envelope(possible_envelope)
110
+ rescue S3::Errors::ServiceError, Json::ParseError
111
+ nil
112
+ end
113
+
114
+ def extract_envelope(hash)
115
+ return nil unless hash
116
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
117
+ ##% - If the metadata contains "x-amz-iv" and "x-amz-key" then the object MUST be considered as an S3EC-encrypted object using the V1 format.
118
+ return v1_envelope(hash) if hash.key?('x-amz-key')
119
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
120
+ ##% - If the metadata contains "x-amz-iv" and "x-amz-metadata-x-amz-key-v2" then the object MUST be considered as an S3EC-encrypted object using the V2 format.
121
+ return v2_envelope(hash) if hash.key?('x-amz-key-v2')
122
+
123
+ return unless hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
124
+
125
+ msg = "unsupported envelope encryption version #{::Regexp.last_match(1)}"
126
+ raise Errors::DecryptionError, msg
127
+ end
128
+
129
+ def v1_envelope(envelope)
130
+ envelope
131
+ end
132
+
133
+ def v2_envelope(envelope)
134
+ unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
135
+ alg = envelope['x-amz-cek-alg'].inspect
136
+ msg = "unsupported content encrypting key (cek) format: #{alg}"
137
+ raise Errors::DecryptionError, msg
138
+ end
139
+ unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
140
+ alg = envelope['x-amz-wrap-alg'].inspect
141
+ msg = "unsupported key wrapping algorithm: #{alg}"
142
+ raise Errors::DecryptionError, msg
143
+ end
144
+ unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
145
+ msg = "incomplete v2 encryption envelope:\n"
146
+ msg += " missing: #{missing_keys.join(',')}\n"
147
+ raise Errors::DecryptionError, msg
148
+ end
149
+ envelope
150
+ end
151
+
152
+ def body_contains_auth_tag?(envelope)
153
+ AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
154
+ end
155
+
156
+ # This method fetches the tag from the end of the object by
157
+ # making a GET Object w/range request. This auth tag is used
158
+ # to initialize the cipher, and the decrypter truncates the
159
+ # auth tag from the body when writing the final bytes.
160
+ def authenticated_decrypter(context, cipher, envelope)
161
+ http_resp = context.http_response
162
+ content_length = http_resp.headers['content-length'].to_i
163
+ auth_tag_length = auth_tag_length(envelope)
164
+
165
+ auth_tag = context.client.get_object(
166
+ bucket: context.params[:bucket],
167
+ key: context.params[:key],
168
+ version_id: context.params[:version_id],
169
+ range: "bytes=-#{auth_tag_length}"
170
+ ).body.read
171
+
172
+ cipher.auth_tag = auth_tag
173
+ cipher.auth_data = ''
174
+
175
+ # The encrypted object contains both the cipher text
176
+ # plus a trailing auth tag.
177
+ IOAuthDecrypter.new(
178
+ io: http_resp.body,
179
+ encrypted_content_length: content_length - auth_tag_length,
180
+ cipher: cipher
181
+ )
182
+ end
183
+
184
+ # Determine the auth tag length from the algorithm
185
+ # Validate it against the value provided in the x-amz-tag-len
186
+ # Return the tag length in bytes
187
+ def auth_tag_length(envelope)
188
+ tag_length =
189
+ case envelope['x-amz-cek-alg']
190
+ when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
191
+ else
192
+ raise ArgumentError, 'Unsupported cek-alg: ' \
193
+ "#{envelope['x-amz-cek-alg']}"
194
+ end
195
+ if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
196
+ raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
197
+ end
198
+
199
+ tag_length
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,187 @@
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
+ ##= ../specification/s3-encryption/encryption.md#content-encryption
18
+ ##% The S3EC MUST use the encryption algorithm configured during [client](./client.md) initialization.
19
+ @content_encryption_schema = validate_cek(
20
+ options[:content_encryption_schema]
21
+ )
22
+ end
23
+
24
+ attr_reader :key_provider
25
+
26
+ # @return [Array<Hash,Cipher>] Creates an returns a new encryption
27
+ # envelope and encryption cipher.
28
+ def encryption_cipher(options = {})
29
+ validate_options(options)
30
+ cipher = Utils.aes_encryption_cipher(:GCM)
31
+ if @key_provider.encryption_materials.key.is_a? OpenSSL::PKey::RSA
32
+ enc_key = encode64(
33
+ encrypt_rsa(envelope_key(cipher), @content_encryption_schema)
34
+ )
35
+ else
36
+ enc_key = encode64(
37
+ ##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
38
+ ##% The client MUST NOT provide any AAD when encrypting with ALG_AES_256_GCM_IV12_TAG16_NO_KDF.
39
+ encrypt_aes_gcm(envelope_key(cipher), @content_encryption_schema)
40
+ )
41
+ end
42
+
43
+ ##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
44
+ ##% Objects encrypted with ALG_AES_256_GCM_IV12_TAG16_NO_KDF MUST use the V2 message format version only.
45
+ envelope = {
46
+ 'x-amz-key-v2' => enc_key,
47
+ 'x-amz-cek-alg' => @content_encryption_schema,
48
+ 'x-amz-tag-len' => (AES_GCM_TAG_LEN_BYTES * 8).to_s,
49
+ 'x-amz-wrap-alg' => @key_wrap_schema,
50
+ 'x-amz-iv' => encode64(envelope_iv(cipher)),
51
+ 'x-amz-matdesc' => materials_description
52
+ }
53
+ cipher.auth_data = '' # auth_data must be set after key and iv
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
+ master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
62
+ if envelope.key? 'x-amz-key'
63
+ unless options[:security_profile] == :v2_and_legacy
64
+ ##= ../specification/s3-encryption/decryption.md#legacy-decryption
65
+ ##% If the S3EC is not configured to enable legacy unauthenticated content decryption, the client MUST throw an exception when attempting to decrypt an object encrypted with a legacy unauthenticated algorithm suite.
66
+ ##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
67
+ ##% When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy content encryption algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy content encryption algorithm.
68
+ raise Errors::LegacyDecryptionError
69
+ end
70
+ ##= ../specification/s3-encryption/decryption.md#legacy-decryption
71
+ ##% The S3EC MUST NOT decrypt objects encrypted using legacy unauthenticated algorithm suites unless specifically configured to do so.
72
+ ##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
73
+ ##% When enabled, the S3EC MUST be able to decrypt objects encrypted with all content encryption algorithms (both legacy and fully supported).
74
+ # Support for decryption of legacy objects
75
+ key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
76
+ iv = decode64(envelope['x-amz-iv'])
77
+ Utils.aes_decryption_cipher(:CBC, key, iv)
78
+ else
79
+ if envelope['x-amz-cek-alg'] != 'AES/GCM/NoPadding'
80
+ raise ArgumentError, 'Unsupported cek-alg: ' \
81
+ "#{envelope['x-amz-cek-alg']}"
82
+ end
83
+ key =
84
+ case envelope['x-amz-wrap-alg']
85
+ when 'AES/GCM'
86
+ if master_key.is_a? OpenSSL::PKey::RSA
87
+ raise ArgumentError, 'Key mismatch - Client is configured' \
88
+ ' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
89
+ end
90
+ Utils.decrypt_aes_gcm(master_key,
91
+ decode64(envelope['x-amz-key-v2']),
92
+ envelope['x-amz-cek-alg'])
93
+ when 'RSA-OAEP-SHA1'
94
+ unless master_key.is_a? OpenSSL::PKey::RSA
95
+ raise ArgumentError, 'Key mismatch - Client is configured' \
96
+ ' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
97
+ end
98
+ key, cek_alg = Utils.decrypt_rsa(master_key, decode64(envelope['x-amz-key-v2']))
99
+ raise Errors::CEKAlgMismatchError unless cek_alg == envelope['x-amz-cek-alg']
100
+ key
101
+ when 'kms+context'
102
+ raise ArgumentError, 'Key mismatch - Client is configured' \
103
+ ' with a user provided key and the x-amz-wrap-alg is' \
104
+ ' kms+context. Please configure the client with the' \
105
+ ' required kms_key_id'
106
+ else
107
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
108
+ "#{envelope['x-amz-wrap-alg']}"
109
+ end
110
+ iv = decode64(envelope['x-amz-iv'])
111
+ Utils.aes_decryption_cipher(:GCM, key, iv)
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ # Validate that the key_wrap_schema
118
+ # is valid, supported and matches the provided key.
119
+ # Returns the string version for the x-amz-key-wrap-alg
120
+ def validate_key_wrap(key_wrap_schema, key)
121
+ if key.is_a? OpenSSL::PKey::RSA
122
+ unless key_wrap_schema == :rsa_oaep_sha1
123
+ raise ArgumentError, ':key_wrap_schema must be set to :rsa_oaep_sha1 for RSA keys.'
124
+ end
125
+ else
126
+ unless key_wrap_schema == :aes_gcm
127
+ raise ArgumentError, ':key_wrap_schema must be set to :aes_gcm for AES keys.'
128
+ end
129
+ end
130
+
131
+ case key_wrap_schema
132
+ when :rsa_oaep_sha1 then 'RSA-OAEP-SHA1'
133
+ when :aes_gcm then 'AES/GCM'
134
+ when :kms_context
135
+ raise ArgumentError, 'A kms_key_id is required when using :kms_context.'
136
+ else
137
+ raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
138
+ end
139
+ end
140
+
141
+ def validate_cek(content_encryption_schema)
142
+ case content_encryption_schema
143
+ when :aes_gcm_no_padding
144
+ "AES/GCM/NoPadding"
145
+ else
146
+ raise ArgumentError, "Unsupported content_encryption_schema: #{content_encryption_schema}"
147
+ end
148
+ end
149
+
150
+ def envelope_key(cipher)
151
+ cipher.key = cipher.random_key
152
+ end
153
+
154
+ def envelope_iv(cipher)
155
+ cipher.iv = cipher.random_iv
156
+ end
157
+
158
+ def encrypt_aes_gcm(data, auth_data)
159
+ Utils.encrypt_aes_gcm(@key_provider.encryption_materials.key, data, auth_data)
160
+ end
161
+
162
+ def encrypt_rsa(data, auth_data)
163
+ Utils.encrypt_rsa(@key_provider.encryption_materials.key, data, auth_data)
164
+ end
165
+
166
+ def materials_description
167
+ @key_provider.encryption_materials.description
168
+ end
169
+
170
+ def encode64(str)
171
+ Base64.encode64(str).split("\n") * ''
172
+ end
173
+
174
+ def decode64(str)
175
+ Base64.decode64(str)
176
+ end
177
+
178
+ def validate_options(options)
179
+ if !options[:kms_encryption_context].nil?
180
+ raise ArgumentError, 'Cannot provide :kms_encryption_context ' \
181
+ 'with non KMS client.'
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ 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,67 @@
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
+ ##= ../specification/s3-encryption/data-format/metadata-strategy.md#v1-v2-instruction-files
32
+ ##% In the V1/V2 message format, all of the content metadata MUST be stored in the Instruction File.
33
+ body: Json.dump(envelope)
34
+ )
35
+ else # :metadata
36
+ context.params[:metadata] ||= {}
37
+ context.params[:metadata].update(envelope)
38
+ end
39
+ end
40
+
41
+ def apply_encryption_cipher(context, cipher)
42
+ io = context.params[:body] || ''
43
+ io = StringIO.new(io) if io.is_a? String
44
+ context.params[:body] = IOEncrypter.new(cipher, io)
45
+ context.params[:metadata] ||= {}
46
+ context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
47
+ if context.params.delete(:content_md5)
48
+ raise ArgumentError, 'Setting content_md5 on client side '\
49
+ 'encrypted objects is deprecated.'
50
+ end
51
+ context.http_response.on_headers do
52
+ context.params[:body].close
53
+ end
54
+ end
55
+
56
+ def apply_cse_user_agent(context)
57
+ if context.config.user_agent_suffix.nil?
58
+ context.config.user_agent_suffix = EC_USER_AGENT
59
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
60
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ 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