aws-sdk-s3 1.75.0 → 1.76.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-sdk-s3.rb +1 -1
  3. data/lib/aws-sdk-s3/bucket.rb +2 -2
  4. data/lib/aws-sdk-s3/client.rb +791 -505
  5. data/lib/aws-sdk-s3/encryption.rb +2 -0
  6. data/lib/aws-sdk-s3/encryption/client.rb +11 -0
  7. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +52 -28
  8. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +41 -5
  9. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +5 -5
  10. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +32 -3
  11. data/lib/aws-sdk-s3/encryption/utils.rb +23 -0
  12. data/lib/aws-sdk-s3/encryptionV2/client.rb +198 -22
  13. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +40 -12
  14. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +77 -10
  15. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +2 -0
  16. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +7 -4
  17. data/lib/aws-sdk-s3/encryptionV2/errors.rb +24 -0
  18. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +2 -0
  19. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +2 -0
  20. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
  21. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +2 -0
  22. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +90 -20
  23. data/lib/aws-sdk-s3/encryptionV2/materials.rb +2 -0
  24. data/lib/aws-sdk-s3/encryptionV2/utils.rb +2 -15
  25. data/lib/aws-sdk-s3/encryption_v2.rb +4 -1
  26. data/lib/aws-sdk-s3/multipart_upload_part.rb +5 -5
  27. data/lib/aws-sdk-s3/object.rb +10 -9
  28. data/lib/aws-sdk-s3/object_summary.rb +23 -7
  29. data/lib/aws-sdk-s3/object_version.rb +2 -2
  30. data/lib/aws-sdk-s3/presigner.rb +2 -2
  31. data/lib/aws-sdk-s3/types.rb +63 -26
  32. metadata +2 -2
@@ -17,5 +17,7 @@ require 'aws-sdk-s3/encryption/default_key_provider'
17
17
  module Aws
18
18
  module S3
19
19
  module Encryption; end
20
+ AES_GCM_TAG_LEN_BYTES = 16
21
+ EC_USER_AGENT = 'S3CryptoV1n'
20
22
  end
21
23
  end
@@ -5,6 +5,10 @@ require 'forwardable'
5
5
  module Aws
6
6
  module S3
7
7
 
8
+ # [MAINTENANCE MODE] There is a new version of the Encryption Client.
9
+ # AWS strongly recommends upgrading to the {Aws::S3::EncryptionV2::Client},
10
+ # which provides updated data security best practices.
11
+ # See documentation for {Aws::S3::EncryptionV2::Client}.
8
12
  # Provides an encryption client that encrypts and decrypts data client-side,
9
13
  # storing the encrypted data in Amazon S3.
10
14
  #
@@ -229,6 +233,13 @@ module Aws
229
233
  @envelope_location = extract_location(options)
230
234
  @instruction_file_suffix = extract_suffix(options)
231
235
  end
236
+ deprecated :initialize,
237
+ message:
238
+ '[MAINTENANCE MODE] This version of the S3 Encryption client is currently in maintenance mode. ' \
239
+ 'AWS strongly recommends upgrading to the Aws::S3::EncryptionV2::Client, ' \
240
+ 'which provides updated data security best practices. ' \
241
+ 'See documentation for Aws::S3::EncryptionV2::Client.'
242
+
232
243
 
233
244
  # @return [S3::Client]
234
245
  attr_reader :client
@@ -22,7 +22,17 @@ module Aws
22
22
  x-amz-matdesc
23
23
  )
24
24
 
25
- POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS + V2_ENVELOPE_KEYS).uniq
25
+ V2_OPTIONAL_KEYS = %w(x-amz-tag-len)
26
+
27
+ POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS +
28
+ V2_ENVELOPE_KEYS + V2_OPTIONAL_KEYS).uniq
29
+
30
+ POSSIBLE_WRAPPING_FORMATS = %w(
31
+ AES/GCM
32
+ kms
33
+ kms+context
34
+ RSA-OAEP-SHA1
35
+ )
26
36
 
27
37
  POSSIBLE_ENCRYPTION_FORMATS = %w(
28
38
  AES/GCM/NoPadding
@@ -30,6 +40,8 @@ module Aws
30
40
  AES/CBC/PKCS7Padding
31
41
  )
32
42
 
43
+ AUTH_REQUIRED_CEK_ALGS = %w(AES/GCM/NoPadding)
44
+
33
45
  def call(context)
34
46
  attach_http_event_listeners(context)
35
47
  apply_cse_user_agent(context)
@@ -41,9 +53,9 @@ module Aws
41
53
  def attach_http_event_listeners(context)
42
54
 
43
55
  context.http_response.on_headers(200) do
44
- cipher = decryption_cipher(context)
45
- decrypter = body_contains_auth_tag?(context) ?
46
- authenticated_decrypter(context, cipher) :
56
+ cipher, envelope = decryption_cipher(context)
57
+ decrypter = body_contains_auth_tag?(envelope) ?
58
+ authenticated_decrypter(context, cipher, envelope) :
47
59
  IODecrypter.new(cipher, context.http_response.body)
48
60
  context.http_response.body = decrypter
49
61
  end
@@ -64,7 +76,12 @@ module Aws
64
76
 
65
77
  def decryption_cipher(context)
66
78
  if envelope = get_encryption_envelope(context)
67
- context[:encryption][:cipher_provider].decryption_cipher(envelope)
79
+ cipher = context[:encryption][:cipher_provider]
80
+ .decryption_cipher(
81
+ envelope,
82
+ kms_encryption_context: context[:encryption][:kms_encryption_context]
83
+ )
84
+ [cipher, envelope]
68
85
  else
69
86
  raise Errors::DecryptionError, "unable to locate encryption envelope"
70
87
  end
@@ -100,13 +117,12 @@ module Aws
100
117
  end
101
118
 
102
119
  def extract_envelope(hash)
120
+ return nil unless hash
103
121
  return v1_envelope(hash) if hash.key?('x-amz-key')
104
122
  return v2_envelope(hash) if hash.key?('x-amz-key-v2')
105
123
  if hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
106
124
  msg = "unsupported envelope encryption version #{$1}"
107
125
  raise Errors::DecryptionError, msg
108
- else
109
- nil # no envelope found
110
126
  end
111
127
  end
112
128
 
@@ -120,39 +136,31 @@ module Aws
120
136
  msg = "unsupported content encrypting key (cek) format: #{alg}"
121
137
  raise Errors::DecryptionError, msg
122
138
  end
123
- unless envelope['x-amz-wrap-alg'] == 'kms'
124
- # possible to support
125
- # RSA/ECB/OAEPWithSHA-256AndMGF1Padding
139
+ unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
126
140
  alg = envelope['x-amz-wrap-alg'].inspect
127
141
  msg = "unsupported key wrapping algorithm: #{alg}"
128
142
  raise Errors::DecryptionError, msg
129
143
  end
130
- unless V2_ENVELOPE_KEYS.sort == envelope.keys.sort
144
+ unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
131
145
  msg = "incomplete v2 encryption envelope:\n"
132
- msg += " expected: #{V2_ENVELOPE_KEYS.join(',')}\n"
133
- msg += " got: #{envelope_keys.join(', ')}"
146
+ msg += " missing: #{missing_keys.join(',')}\n"
134
147
  raise Errors::DecryptionError, msg
135
148
  end
136
149
  envelope
137
150
  end
138
151
 
139
- # When the x-amz-meta-x-amz-tag-len header is present, it indicates
140
- # that the body of this object has a trailing auth tag. The header
141
- # indicates the length of that tag.
142
- #
143
152
  # This method fetches the tag from the end of the object by
144
153
  # making a GET Object w/range request. This auth tag is used
145
154
  # to initialize the cipher, and the decrypter truncates the
146
155
  # auth tag from the body when writing the final bytes.
147
- def authenticated_decrypter(context, cipher)
156
+ def authenticated_decrypter(context, cipher, envelope)
148
157
  if RUBY_VERSION.match(/1.9/)
149
- raise "authenticated decryption not supported by OpeenSSL in Ruby version ~> 1.9"
158
+ raise "authenticated decryption not supported by OpenSSL in Ruby version ~> 1.9"
150
159
  raise Aws::Errors::NonSupportedRubyVersionError, msg
151
160
  end
152
161
  http_resp = context.http_response
153
162
  content_length = http_resp.headers['content-length'].to_i
154
- auth_tag_length = http_resp.headers['x-amz-meta-x-amz-tag-len']
155
- auth_tag_length = auth_tag_length.to_i / 8
163
+ auth_tag_length = auth_tag_length(envelope)
156
164
 
157
165
  auth_tag = context.client.get_object(
158
166
  bucket: context.params[:bucket],
@@ -164,23 +172,39 @@ module Aws
164
172
  cipher.auth_data = ''
165
173
 
166
174
  # The encrypted object contains both the cipher text
167
- # plus a trailing auth tag. This decrypter will the body
168
- # expect for the trailing auth tag.
175
+ # plus a trailing auth tag.
169
176
  IOAuthDecrypter.new(
170
177
  io: http_resp.body,
171
178
  encrypted_content_length: content_length - auth_tag_length,
172
179
  cipher: cipher)
173
180
  end
174
181
 
175
- def body_contains_auth_tag?(context)
176
- context.http_response.headers['x-amz-meta-x-amz-tag-len']
182
+ def body_contains_auth_tag?(envelope)
183
+ AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
184
+ end
185
+
186
+ # Determine the auth tag length from the algorithm
187
+ # Validate it against the value provided in the x-amz-tag-len
188
+ # Return the tag length in bytes
189
+ def auth_tag_length(envelope)
190
+ tag_length =
191
+ case envelope['x-amz-cek-alg']
192
+ when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
193
+ else
194
+ raise ArgumentError, 'Unsupported cek-alg: ' \
195
+ "#{envelope['x-amz-cek-alg']}"
196
+ end
197
+ if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
198
+ raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
199
+ end
200
+ tag_length
177
201
  end
178
202
 
179
203
  def apply_cse_user_agent(context)
180
204
  if context.config.user_agent_suffix.nil?
181
- context.config.user_agent_suffix = 'CSE_V1'
182
- elsif !context.config.user_agent_suffix.include? 'CSE_V1'
183
- context.config.user_agent_suffix += ' CSE_V1'
205
+ context.config.user_agent_suffix = EC_USER_AGENT
206
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
207
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
184
208
  end
185
209
  end
186
210
 
@@ -26,11 +26,48 @@ module Aws
26
26
 
27
27
  # @return [Cipher] Given an encryption envelope, returns a
28
28
  # decryption cipher.
29
- def decryption_cipher(envelope)
29
+ def decryption_cipher(envelope, options = {})
30
30
  master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
31
- key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
32
- iv = decode64(envelope['x-amz-iv'])
33
- Utils.aes_decryption_cipher(:CBC, key, iv)
31
+ if envelope.key? 'x-amz-key'
32
+ # Support for decryption of legacy objects
33
+ key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
34
+ iv = decode64(envelope['x-amz-iv'])
35
+ Utils.aes_decryption_cipher(:CBC, key, iv)
36
+ else
37
+ if envelope['x-amz-cek-alg'] != 'AES/GCM/NoPadding'
38
+ raise ArgumentError, 'Unsupported cek-alg: ' \
39
+ "#{envelope['x-amz-cek-alg']}"
40
+ end
41
+ key =
42
+ case envelope['x-amz-wrap-alg']
43
+ when 'AES/GCM'
44
+ if master_key.is_a? OpenSSL::PKey::RSA
45
+ raise ArgumentError, 'Key mismatch - Client is configured' \
46
+ ' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
47
+ end
48
+ Utils.decrypt_aes_gcm(master_key,
49
+ decode64(envelope['x-amz-key-v2']),
50
+ envelope['x-amz-cek-alg'])
51
+ when 'RSA-OAEP-SHA1'
52
+ unless master_key.is_a? OpenSSL::PKey::RSA
53
+ raise ArgumentError, 'Key mismatch - Client is configured' \
54
+ ' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
55
+ end
56
+ key, cek_alg = Utils.decrypt_rsa(master_key, decode64(envelope['x-amz-key-v2']))
57
+ raise Errors::DecryptionError unless cek_alg == envelope['x-amz-cek-alg']
58
+ key
59
+ when 'kms+context'
60
+ raise ArgumentError, 'Key mismatch - Client is configured' \
61
+ ' with a user provided key and the x-amz-wrap-alg is' \
62
+ ' kms+context. Please configure the client with the' \
63
+ ' required kms_key_id'
64
+ else
65
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
66
+ "#{envelope['x-amz-wrap-alg']}"
67
+ end
68
+ iv = decode64(envelope['x-amz-iv'])
69
+ Utils.aes_decryption_cipher(:GCM, key, iv)
70
+ end
34
71
  end
35
72
 
36
73
  private
@@ -58,7 +95,6 @@ module Aws
58
95
  def decode64(str)
59
96
  Base64.decode64(str)
60
97
  end
61
-
62
98
  end
63
99
  end
64
100
  end
@@ -39,8 +39,8 @@ module Aws
39
39
  context.params[:body] = IOEncrypter.new(cipher, io)
40
40
  context.params[:metadata] ||= {}
41
41
  context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
42
- if md5 = context.params.delete(:content_md5)
43
- context.params[:metadata]['x-amz-unencrypted-content-md5'] = md5
42
+ if context.params.delete(:content_md5)
43
+ warn('Setting content_md5 on client side encrypted objects is deprecated')
44
44
  end
45
45
  context.http_response.on_headers do
46
46
  context.params[:body].close
@@ -49,9 +49,9 @@ module Aws
49
49
 
50
50
  def apply_cse_user_agent(context)
51
51
  if context.config.user_agent_suffix.nil?
52
- context.config.user_agent_suffix = 'CSE_V1'
53
- elsif !context.config.user_agent_suffix.include? 'CSE_V1'
54
- context.config.user_agent_suffix += ' CSE_V1'
52
+ context.config.user_agent_suffix = EC_USER_AGENT
53
+ elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
54
+ context.config.user_agent_suffix += " #{EC_USER_AGENT}"
55
55
  end
56
56
  end
57
57
 
@@ -36,15 +36,36 @@ module Aws
36
36
 
37
37
  # @return [Cipher] Given an encryption envelope, returns a
38
38
  # decryption cipher.
39
- def decryption_cipher(envelope)
39
+ def decryption_cipher(envelope, options = {})
40
40
  encryption_context = Json.load(envelope['x-amz-matdesc'])
41
+ cek_alg = envelope['x-amz-cek-alg']
42
+
43
+ case envelope['x-amz-wrap-alg']
44
+ when 'kms'; # NO OP
45
+ when 'kms+context'
46
+ if cek_alg != encryption_context['aws:x-amz-cek-alg']
47
+ raise Errors::DecryptionError, 'Value of cek-alg from envelope'\
48
+ ' does not match the value in the encryption context'
49
+ end
50
+ when 'AES/GCM'
51
+ raise ArgumentError, 'Key mismatch - Client is configured' \
52
+ ' with a KMS key and the x-amz-wrap-alg is AES/GCM.'
53
+ when 'RSA-OAEP-SHA1'
54
+ raise ArgumentError, 'Key mismatch - Client is configured' \
55
+ ' with a KMS key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
56
+ else
57
+ raise ArgumentError, 'Unsupported wrap-alg: ' \
58
+ "#{envelope['x-amz-wrap-alg']}"
59
+ end
60
+
41
61
  key = @kms_client.decrypt(
42
62
  ciphertext_blob: decode64(envelope['x-amz-key-v2']),
43
- encryption_context: encryption_context,
63
+ encryption_context: encryption_context
44
64
  ).plaintext
65
+
45
66
  iv = decode64(envelope['x-amz-iv'])
46
67
  block_mode =
47
- case envelope['x-amz-cek-alg']
68
+ case cek_alg
48
69
  when 'AES/CBC/PKCS5Padding'
49
70
  :CBC
50
71
  when 'AES/CBC/PKCS7Padding'
@@ -61,6 +82,14 @@ module Aws
61
82
 
62
83
  private
63
84
 
85
+ def build_encryption_context(cek_alg, options = {})
86
+ kms_context = (options[:kms_encryption_context] || {})
87
+ .each_with_object({}) { |(k, v), h| h[k.to_s] = v }
88
+ {
89
+ 'aws:x-amz-cek-alg' => cek_alg
90
+ }.merge(kms_context)
91
+ end
92
+
64
93
  def encode64(str)
65
94
  Base64.encode64(str).split("\n") * ""
66
95
  end
@@ -39,6 +39,29 @@ module Aws
39
39
  end
40
40
  end
41
41
 
42
+
43
+ def decrypt_aes_gcm(key, data, auth_data)
44
+ # data is iv (12B) + key + tag (16B)
45
+ buf = data.unpack('C*')
46
+ iv = buf[0,12].pack('C*') # iv will always be 12 bytes
47
+ tag = buf[-16, 16].pack('C*') # tag is 16 bytes
48
+ enc_key = buf[12, buf.size - (12+16)].pack('C*')
49
+ cipher = aes_cipher(:decrypt, :GCM, key, iv)
50
+ cipher.auth_tag = tag
51
+ cipher.auth_data = auth_data
52
+ cipher.update(enc_key) + cipher.final
53
+ end
54
+
55
+ # returns the decrypted data + auth_data
56
+ def decrypt_rsa(key, enc_data)
57
+ # Plaintext must be KeyLengthInBytes (1 Byte) + DataKey + AuthData
58
+ buf = key.private_decrypt(enc_data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING).unpack('C*')
59
+ key_length = buf[0]
60
+ data = buf[1, key_length].pack('C*')
61
+ auth_data = buf[key_length+1, buf.length - key_length].pack('C*')
62
+ [data, auth_data]
63
+ end
64
+
42
65
  # @param [String] block_mode "CBC" or "ECB"
43
66
  # @param [OpenSSL::PKey::RSA, String, nil] key
44
67
  # @param [String, nil] iv The initialization vector
@@ -1,16 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Aws
4
6
  module S3
5
7
 
8
+ REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile]
9
+ SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy]
10
+
6
11
  # Provides an encryption client that encrypts and decrypts data client-side,
7
- # storing the encrypted data in Amazon S3. The EncryptionV2::Client
8
- # provides improved security over the Encryption::Client by using more
9
- # modern and secure algorithms. The EncryptionV2::Client maintains
10
- # backwards compatibility: Using the EncryptionV2::Client you can decrypt
11
- # objects encrypted with the Encryption::Client. However, objects you
12
- # encrypt using the EncryptionV2::Client cannot be decrypted using the
13
- # Encryption::Client.
12
+ # storing the encrypted data in Amazon S3. The `EncryptionV2::Client` (V2 Client)
13
+ # provides improved security over the `Encryption::Client` (V1 Client)
14
+ # by using more modern and secure algorithms. You can use the V2 Client
15
+ # to continue decrypting objects encrypted using deprecated algorithms
16
+ # by setting security_profile: :v2_and_legacy. The latest V1 Client also
17
+ # supports reading and decrypting objects encrypted by the V2 Client.
14
18
  #
15
19
  # This client uses a process called "envelope encryption". Your private
16
20
  # encryption keys and your data's plain-text are **never** sent to
@@ -47,7 +51,12 @@ module Aws
47
51
  # key = OpenSSL::PKey::RSA.new(1024)
48
52
  #
49
53
  # # encryption client
50
- # s3 = Aws::S3::EncryptionV2::Client.new(encryption_key: key)
54
+ # s3 = Aws::S3::EncryptionV2::Client.new(
55
+ # encryption_key: key,
56
+ # key_wrap_schema: :rsa_oaep_sha1, # the key_wrap_schema must be rsa_oaep_sha1 for asymmetric keys
57
+ # content_encryption_schema: :aes_gcm_no_padding,
58
+ # security_profile: :v2 # use :v2_and_legacy to allow reading/decrypting objects encrypted by the V1 encryption client
59
+ # )
51
60
  #
52
61
  # # round-trip an object, encrypted/decrypted locally
53
62
  # s3.put_object(bucket:'aws-sdk', key:'secret', body:'handshake')
@@ -59,6 +68,19 @@ module Aws
59
68
  # Aws::S3::Client.new.get_object(bucket:'aws-sdk', key:'secret').body.read
60
69
  # #=> "... cipher text ..."
61
70
  #
71
+ # ## Required Configuration
72
+ #
73
+ # You must configure all of the following:
74
+ # * a key or key provider - See the Keys section below. The key provided determines
75
+ # the key wrapping schema(s) supported for both encryption and decryption.
76
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
77
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`.
78
+ # More options will be added in future releases.
79
+ # * `security_profile` - Determines the support for reading objects written
80
+ # using older key wrap or content encryption schemas. If you need to read
81
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
82
+ # Otherwise, set it to `:v2`
83
+ #
62
84
  # ## Keys
63
85
  #
64
86
  # For client-side encryption to work, you must provide one of the following:
@@ -67,15 +89,25 @@ module Aws
67
89
  # * A {KeyProvider}
68
90
  # * A KMS encryption key id
69
91
  #
92
+ # Additionally, the key wrapping schema must agree with the type of the key:
93
+ # * :aes_gcm: An AES encryption key or a key provider.
94
+ # * :rsa_oaep_sha1: An RSA encryption key or key provider.
95
+ # * :kms_context: A KMS encryption key id
96
+ #
70
97
  # ### An Encryption Key
71
98
  #
72
99
  # You can pass a single encryption key. This is used as a master key
73
100
  # encrypting and decrypting all object keys.
74
101
  #
75
- # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key
76
- # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair
102
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key - used with `key_wrap_schema: :aes_gcm`
103
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair - used with `key_wrap_schema: :rsa_oaep_sha1`
77
104
  #
78
- # s3 = Aws::S3::EncryptionV2::Client.new(encryption_key: key)
105
+ # s3 = Aws::S3::EncryptionV2::Client.new(
106
+ # encryption_key: key,
107
+ # key_wrap_schema: :aes_gcm, # or :rsa_oaep_sha1 if using RSA
108
+ # content_encryption_schema: :aes_gcm_no_padding,
109
+ # security_profile: :v2
110
+ # )
79
111
  #
80
112
  # ### Key Provider
81
113
  #
@@ -84,8 +116,9 @@ module Aws
84
116
  #
85
117
  # ### KMS Encryption Key Id
86
118
  #
87
- # If you pass the id to an AWS Key Management Service (KMS) key,
88
- # then KMS will be used to generate, encrypt and decrypt object keys.
119
+ # If you pass the id of an AWS Key Management Service (KMS) key and
120
+ # use :kms_content for the key_wrap_schema, then KMS will be used to
121
+ # generate, encrypt and decrypt object keys.
89
122
  #
90
123
  # # keep track of the kms key id
91
124
  # kms = Aws::KMS::Client.new
@@ -94,6 +127,9 @@ module Aws
94
127
  # Aws::S3::EncryptionV2::Client.new(
95
128
  # kms_key_id: key_id,
96
129
  # kms_client: kms,
130
+ # key_wrap_schema: :kms_context,
131
+ # content_encryption_schema: :aes_gcm_no_padding,
132
+ # security_profile: :v2
97
133
  # )
98
134
  #
99
135
  # ## Custom Key Providers
@@ -142,7 +178,12 @@ module Aws
142
178
  #
143
179
  # # chooses the key based on the materials description stored
144
180
  # # with the encrypted object
145
- # s3 = Aws::S3::EncryptionV2::Client.new(key_provider: keys)
181
+ # s3 = Aws::S3::EncryptionV2::Client.new(
182
+ # key_provider: keys,
183
+ # key_wrap_schema: ...,
184
+ # content_encryption_schema: :aes_gcm_no_padding,
185
+ # security_profile: :v2
186
+ # )
146
187
  #
147
188
  # ## Materials Description
148
189
  #
@@ -176,6 +217,9 @@ module Aws
176
217
  # key_provider: ...,
177
218
  # envelope_location: :instruction_file,
178
219
  # instruction_file_suffix: '.instruction' # default
220
+ # key_wrap_schema: ...,
221
+ # content_encryption_schema: :aes_gcm_no_padding,
222
+ # security_profile: :v2
179
223
  # )
180
224
  #
181
225
  # When using an instruction file, multiple requests are made when
@@ -189,8 +233,18 @@ module Aws
189
233
  extend Forwardable
190
234
  def_delegators :@client, :config, :delete_object, :head_object, :build_request
191
235
 
192
- # Creates a new encryption client. You must provide one of the following
193
- # options:
236
+ # Creates a new encryption client. You must configure all of the following:
237
+ # * a key or key provider - The key provided also determines the key wrapping
238
+ # schema(s) supported for both encryption and decryption.
239
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
240
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`
241
+ # More options will be added in future releases.
242
+ # * `security_profile` - Determines the support for reading objects written
243
+ # using older key wrap or content encryption schemas. If you need to read
244
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
245
+ # Otherwise, set it to `:v2`
246
+ #
247
+ # To configure the key you must provide one of the following set of options:
194
248
  #
195
249
  # * `:encryption_key`
196
250
  # * `:kms_key_id`
@@ -209,12 +263,36 @@ module Aws
209
263
  # then AWS Key Management Service (KMS) will be used to manage the
210
264
  # object encryption keys. By default a {KMS::Client} will be
211
265
  # constructed for KMS API calls. Alternatively, you can provide
212
- # your own via `:kms_client`.
266
+ # your own via `:kms_client`. To only support decryption/reads, you may
267
+ # provide `:allow_decrypt_with_any_cmk` which will use
268
+ # the implicit CMK associated with the data during reads but will
269
+ # not allow you to encrypt/write objects with this client.
213
270
  #
214
271
  # @option options [#key_for] :key_provider Any object that responds
215
272
  # to `#key_for`. This method should accept a materials description
216
273
  # JSON document string and return return an encryption key.
217
274
  #
275
+ # @option options [required, Symbol] :key_wrap_schema The Key wrapping
276
+ # schema to be used. It must match the type of key configured.
277
+ # Must be one of the following:
278
+ #
279
+ # * :kms_context (Must provide kms_key_id)
280
+ # * :aes_gcm (Must provide an AES (string) key)
281
+ # * :rsa_oaep_sha1 (Must provide an RSA key)
282
+ #
283
+ # @option options [required, Symbol] :content_encryption_schema
284
+ # Must be one of the following:
285
+ #
286
+ # * :aes_gcm_no_padding
287
+ #
288
+ # @option options [Required, Symbol] :security_profile
289
+ # Determines the support for reading objects written using older
290
+ # key wrap or content encryption schemas.
291
+ # Must be one of the following:
292
+ #
293
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
294
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
295
+ #
218
296
  # @option options [Symbol] :envelope_location (:metadata) Where to
219
297
  # store the envelope encryption keys. By default, the envelope is
220
298
  # stored with the encrypted object. If you pass `:instruction_file`,
@@ -228,10 +306,14 @@ module Aws
228
306
  # is constructed when using KMS to manage encryption keys.
229
307
  #
230
308
  def initialize(options = {})
309
+ validate_params(options)
231
310
  @client = extract_client(options)
232
311
  @cipher_provider = cipher_provider(options)
233
312
  @envelope_location = extract_location(options)
234
313
  @instruction_file_suffix = extract_suffix(options)
314
+ @kms_allow_decrypt_with_any_cmk =
315
+ options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
316
+ @security_profile = extract_security_profile(options)
235
317
  end
236
318
 
237
319
  # @return [S3::Client]
@@ -241,6 +323,14 @@ module Aws
241
323
  # AWS Key Management Service (KMS).
242
324
  attr_reader :key_provider
243
325
 
326
+ # @return [Symbol] Determines the support for reading objects written
327
+ # using older key wrap or content encryption schemas.
328
+ attr_reader :security_profile
329
+
330
+ # @return [Boolean] If true the provided KMS key_id will not be used
331
+ # during decrypt, allowing decryption with the key_id from the object.
332
+ attr_reader :kms_allow_decrypt_with_any_cmk
333
+
244
334
  # @return [Symbol<:metadata, :instruction_file>]
245
335
  attr_reader :envelope_location
246
336
 
@@ -272,9 +362,22 @@ module Aws
272
362
  req.send_request
273
363
  end
274
364
 
275
- # Gets an object from Amazon S3, decrypting data locally.
365
+ # Gets an object from Amazon S3, decrypting data locally.
276
366
  # See {S3::Client#get_object} for documentation on accepted
277
367
  # request parameters.
368
+ # Warning: If you provide a block to get_object or set the request
369
+ # parameter :response_target to a Proc, then read the entire object to the
370
+ # end before you start using the decrypted data. This is to verify that
371
+ # the object has not been modified since it was encrypted.
372
+ #
373
+ # @option options [Symbol] :security_profile
374
+ # Determines the support for reading objects written using older
375
+ # key wrap or content encryption schemas. Overrides the value set
376
+ # on client construction if provided.
377
+ # Must be one of the following:
378
+ #
379
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
380
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
278
381
  # @option params [String] :instruction_file_suffix The suffix
279
382
  # used to find the instruction file containing the encryption
280
383
  # envelope. You should not set this option when the envelope
@@ -282,7 +385,10 @@ module Aws
282
385
  # {#instruction_file_suffix}.
283
386
  # @option params [Hash] :kms_encryption_context Additional encryption
284
387
  # context to use with KMS. Applies only when KMS is used.
285
- # @option params [String] :instruction_file_suffix
388
+ # @option options [Boolean] :kms_allow_decrypt_with_any_cmk (false)
389
+ # By default the KMS CMK ID (kms_key_id) will be used during decrypt
390
+ # and will fail if there is a mismatch. Setting this to true
391
+ # will use the implicit CMK associated with the data.
286
392
  # @option (see S3::Client#get_object)
287
393
  # @return (see S3::Client#get_object)
288
394
  # @see S3::Client#get_object
@@ -293,19 +399,44 @@ module Aws
293
399
  end
294
400
  envelope_location, instruction_file_suffix = envelope_options(params)
295
401
  kms_encryption_context = params.delete(:kms_encryption_context)
402
+ kms_any_cmk_mode = kms_any_cmk_mode(params)
403
+ security_profile = security_profile_from_params(params)
404
+
296
405
  req = @client.build_request(:get_object, params)
297
406
  req.handlers.add(DecryptHandler)
298
407
  req.context[:encryption] = {
299
408
  cipher_provider: @cipher_provider,
300
409
  envelope_location: envelope_location,
301
410
  instruction_file_suffix: instruction_file_suffix,
302
- kms_encryption_context: kms_encryption_context
411
+ kms_encryption_context: kms_encryption_context,
412
+ kms_allow_decrypt_with_any_cmk: kms_any_cmk_mode,
413
+ security_profile: security_profile
303
414
  }
304
415
  req.send_request(target: block)
305
416
  end
306
417
 
307
418
  private
308
419
 
420
+ # Validate required parameters exist and don't conflict.
421
+ # The cek_alg and wrap_alg are passed on to the CipherProviders
422
+ # and further validated there
423
+ def validate_params(options)
424
+ unless (missing_params = REQUIRED_PARAMS - options.keys).empty?
425
+ raise ArgumentError, "Missing required parameter(s): "\
426
+ "#{missing_params.map{ |s| ":#{s}" }.join(', ')}"
427
+ end
428
+
429
+ wrap_alg = options[:key_wrap_schema]
430
+
431
+ # validate that the wrap alg matches the type of key given
432
+ case wrap_alg
433
+ when :kms_context
434
+ unless options[:kms_key_id]
435
+ raise ArgumentError, 'You must provide :kms_key_id to use :kms_context'
436
+ end
437
+ end
438
+ end
439
+
309
440
  def extract_client(options)
310
441
  options[:client] || begin
311
442
  options = options.dup
@@ -315,6 +446,7 @@ module Aws
315
446
  options.delete(:encryption_key)
316
447
  options.delete(:envelope_location)
317
448
  options.delete(:instruction_file_suffix)
449
+ REQUIRED_PARAMS.each { |p| options.delete(p) }
318
450
  S3::Client.new(options)
319
451
  end
320
452
  end
@@ -324,7 +456,7 @@ module Aws
324
456
  KMS::Client.new(
325
457
  region: @client.config.region,
326
458
  credentials: @client.config.credentials,
327
- )
459
+ )
328
460
  end
329
461
  end
330
462
 
@@ -333,10 +465,16 @@ module Aws
333
465
  KmsCipherProvider.new(
334
466
  kms_key_id: options[:kms_key_id],
335
467
  kms_client: kms_client(options),
468
+ key_wrap_schema: options[:key_wrap_schema],
469
+ content_encryption_schema: options[:content_encryption_schema]
336
470
  )
337
471
  else
338
472
  @key_provider = extract_key_provider(options)
339
- DefaultCipherProvider.new(key_provider: @key_provider)
473
+ DefaultCipherProvider.new(
474
+ key_provider: @key_provider,
475
+ key_wrap_schema: options[:key_wrap_schema],
476
+ content_encryption_schema: options[:content_encryption_schema]
477
+ )
340
478
  end
341
479
  end
342
480
 
@@ -382,6 +520,44 @@ module Aws
382
520
  end
383
521
  end
384
522
 
523
+ def kms_any_cmk_mode(params)
524
+ if !params[:kms_allow_decrypt_with_any_cmk].nil?
525
+ params.delete(:kms_allow_decrypt_with_any_cmk)
526
+ else
527
+ @kms_allow_decrypt_with_any_cmk
528
+ end
529
+ end
530
+
531
+ def extract_security_profile(options)
532
+ validate_security_profile(options[:security_profile])
533
+ end
534
+
535
+ def security_profile_from_params(params)
536
+ security_profile =
537
+ if !params[:security_profile].nil?
538
+ params.delete(:security_profile)
539
+ else
540
+ @security_profile
541
+ end
542
+ validate_security_profile(security_profile)
543
+ end
544
+
545
+ def validate_security_profile(security_profile)
546
+ unless SUPPORTED_SECURITY_PROFILES.include? security_profile
547
+ raise ArgumentError, "Unsupported security profile: :#{security_profile}. " \
548
+ "Please provide one of: #{SUPPORTED_SECURITY_PROFILES.map { |s| ":#{s}" }.join(', ')}"
549
+ end
550
+ if security_profile == :v2_and_legacy && !@warned_about_legacy
551
+ @warned_about_legacy = true
552
+ warn(
553
+ 'The S3 Encryption Client is configured to read encrypted objects ' \
554
+ "with legacy encryption modes. If you don't have objects " \
555
+ 'encrypted with these legacy modes, you should disable support ' \
556
+ 'for them to enhance security.'
557
+ )
558
+ end
559
+ security_profile
560
+ end
385
561
  end
386
562
  end
387
563
  end