aws-sdk-s3 1.61.2 → 1.83.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/lib/aws-sdk-s3.rb +5 -2
  3. data/lib/aws-sdk-s3/arn/access_point_arn.rb +62 -0
  4. data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +71 -0
  5. data/lib/aws-sdk-s3/bucket.rb +61 -10
  6. data/lib/aws-sdk-s3/bucket_acl.rb +7 -0
  7. data/lib/aws-sdk-s3/bucket_cors.rb +15 -2
  8. data/lib/aws-sdk-s3/bucket_lifecycle.rb +14 -1
  9. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +14 -1
  10. data/lib/aws-sdk-s3/bucket_logging.rb +7 -0
  11. data/lib/aws-sdk-s3/bucket_notification.rb +7 -0
  12. data/lib/aws-sdk-s3/bucket_policy.rb +14 -1
  13. data/lib/aws-sdk-s3/bucket_region_cache.rb +2 -0
  14. data/lib/aws-sdk-s3/bucket_request_payment.rb +7 -0
  15. data/lib/aws-sdk-s3/bucket_tagging.rb +14 -1
  16. data/lib/aws-sdk-s3/bucket_versioning.rb +17 -0
  17. data/lib/aws-sdk-s3/bucket_website.rb +18 -3
  18. data/lib/aws-sdk-s3/client.rb +2521 -1187
  19. data/lib/aws-sdk-s3/client_api.rb +188 -18
  20. data/lib/aws-sdk-s3/customizations.rb +3 -0
  21. data/lib/aws-sdk-s3/customizations/bucket.rb +11 -4
  22. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  23. data/lib/aws-sdk-s3/customizations/object.rb +23 -5
  24. data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
  25. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  26. data/lib/aws-sdk-s3/encryption.rb +4 -0
  27. data/lib/aws-sdk-s3/encryption/client.rb +18 -5
  28. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +72 -26
  29. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  30. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  31. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  32. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  33. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  34. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  35. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  36. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +34 -3
  38. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  39. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  40. data/lib/aws-sdk-s3/encryptionV2/client.rb +566 -0
  41. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +226 -0
  42. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  43. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  44. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +69 -0
  45. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  46. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  47. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  48. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  49. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  50. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +169 -0
  51. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  52. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  53. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  54. data/lib/aws-sdk-s3/errors.rb +2 -0
  55. data/lib/aws-sdk-s3/event_streams.rb +7 -0
  56. data/lib/aws-sdk-s3/file_downloader.rb +10 -8
  57. data/lib/aws-sdk-s3/file_part.rb +2 -0
  58. data/lib/aws-sdk-s3/file_uploader.rb +14 -1
  59. data/lib/aws-sdk-s3/legacy_signer.rb +2 -0
  60. data/lib/aws-sdk-s3/multipart_file_uploader.rb +39 -2
  61. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +3 -1
  62. data/lib/aws-sdk-s3/multipart_upload.rb +18 -1
  63. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  64. data/lib/aws-sdk-s3/multipart_upload_part.rb +66 -7
  65. data/lib/aws-sdk-s3/object.rb +174 -23
  66. data/lib/aws-sdk-s3/object_acl.rb +15 -0
  67. data/lib/aws-sdk-s3/object_copier.rb +2 -0
  68. data/lib/aws-sdk-s3/object_multipart_copier.rb +2 -0
  69. data/lib/aws-sdk-s3/object_summary.rb +185 -20
  70. data/lib/aws-sdk-s3/object_version.rb +44 -4
  71. data/lib/aws-sdk-s3/plugins/accelerate.rb +29 -38
  72. data/lib/aws-sdk-s3/plugins/arn.rb +187 -0
  73. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +2 -2
  74. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +3 -1
  75. data/lib/aws-sdk-s3/plugins/dualstack.rb +5 -1
  76. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +3 -4
  77. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +2 -0
  78. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +11 -3
  79. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +3 -1
  80. data/lib/aws-sdk-s3/plugins/location_constraint.rb +2 -0
  81. data/lib/aws-sdk-s3/plugins/md5s.rb +26 -25
  82. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  83. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  84. data/lib/aws-sdk-s3/plugins/s3_signer.rb +31 -7
  85. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  86. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +118 -0
  87. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  88. data/lib/aws-sdk-s3/presigned_post.rb +68 -32
  89. data/lib/aws-sdk-s3/presigner.rb +102 -34
  90. data/lib/aws-sdk-s3/resource.rb +4 -2
  91. data/lib/aws-sdk-s3/types.rb +2016 -256
  92. data/lib/aws-sdk-s3/waiters.rb +2 -0
  93. metadata +23 -6
  94. data/lib/aws-sdk-s3/plugins/bucket_arn.rb +0 -211
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -10,6 +12,7 @@ module Aws
10
12
  envelope, cipher = context[:encryption][:cipher_provider].encryption_cipher
11
13
  apply_encryption_envelope(context, envelope, cipher)
12
14
  apply_encryption_cipher(context, cipher)
15
+ apply_cse_user_agent(context)
13
16
  @handler.call(context)
14
17
  end
15
18
 
@@ -36,14 +39,22 @@ module Aws
36
39
  context.params[:body] = IOEncrypter.new(cipher, io)
37
40
  context.params[:metadata] ||= {}
38
41
  context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
39
- if md5 = context.params.delete(:content_md5)
40
- 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')
41
44
  end
42
45
  context.http_response.on_headers do
43
46
  context.params[:body].close
44
47
  end
45
48
  end
46
49
 
50
+ def apply_cse_user_agent(context)
51
+ if context.config.user_agent_suffix.nil?
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
+ end
56
+ end
57
+
47
58
  end
48
59
  end
49
60
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -7,8 +9,10 @@ module Aws
7
9
  # @param [OpenSSL::Cipher] cipher
8
10
  # @param [IO#write] io An IO-like object that responds to `#write`.
9
11
  def initialize(cipher, io)
10
- @cipher = cipher.clone
11
- @io = io
12
+ @cipher = cipher
13
+ # Ensure that IO is reset between retries
14
+ @io = io.tap { |io| io.truncate(0) if io.respond_to?(:truncate) }
15
+ @cipher_buffer = String.new
12
16
  end
13
17
 
14
18
  # @return [#write]
@@ -16,7 +20,11 @@ module Aws
16
20
 
17
21
  def write(chunk)
18
22
  # decrypt and write
19
- @io.write(@cipher.update(chunk))
23
+ if @cipher.method(:update).arity == 1
24
+ @io.write(@cipher.update(chunk))
25
+ else
26
+ @io.write(@cipher.update(chunk, @cipher_buffer))
27
+ end
20
28
  end
21
29
 
22
30
  def finalize
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require 'tempfile'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -34,15 +36,36 @@ module Aws
34
36
 
35
37
  # @return [Cipher] Given an encryption envelope, returns a
36
38
  # decryption cipher.
37
- def decryption_cipher(envelope)
39
+ def decryption_cipher(envelope, options = {})
38
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
+
39
61
  key = @kms_client.decrypt(
40
62
  ciphertext_blob: decode64(envelope['x-amz-key-v2']),
41
- encryption_context: encryption_context,
63
+ encryption_context: encryption_context
42
64
  ).plaintext
65
+
43
66
  iv = decode64(envelope['x-amz-iv'])
44
67
  block_mode =
45
- case envelope['x-amz-cek-alg']
68
+ case cek_alg
46
69
  when 'AES/CBC/PKCS5Padding'
47
70
  :CBC
48
71
  when 'AES/CBC/PKCS7Padding'
@@ -59,6 +82,14 @@ module Aws
59
82
 
60
83
  private
61
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
+
62
93
  def encode64(str)
63
94
  Base64.encode64(str).split("\n") * ""
64
95
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -32,14 +34,14 @@ module Aws
32
34
  if [32, 24, 16].include?(key.bytesize)
33
35
  key
34
36
  else
35
- msg = "invalid key, symmetric key required to be 16, 24, or "
36
- msg << "32 bytes in length, saw length " + key.bytesize.to_s
37
+ msg = 'invalid key, symmetric key required to be 16, 24, or '\
38
+ '32 bytes in length, saw length ' + key.bytesize.to_s
37
39
  raise ArgumentError, msg
38
40
  end
39
41
  else
40
- msg = "invalid encryption key, expected an OpenSSL::PKey::RSA key "
41
- msg << "(for asymmetric encryption) or a String (for symmetric "
42
- msg << "encryption)."
42
+ msg = 'invalid encryption key, expected an OpenSSL::PKey::RSA key '\
43
+ '(for asymmetric encryption) or a String (for symmetric '\
44
+ 'encryption).'
43
45
  raise ArgumentError, msg
44
46
  end
45
47
  end
@@ -48,7 +50,7 @@ module Aws
48
50
  Json.load(description)
49
51
  description
50
52
  rescue Json::ParseError, EncodingError
51
- msg = "expected description to be a valid JSON document string"
53
+ msg = 'expected description to be a valid JSON document string'
52
54
  raise ArgumentError, msg
53
55
  end
54
56
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module Aws
@@ -37,6 +39,29 @@ module Aws
37
39
  end
38
40
  end
39
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
+
40
65
  # @param [String] block_mode "CBC" or "ECB"
41
66
  # @param [OpenSSL::PKey::RSA, String, nil] key
42
67
  # @param [String, nil] iv The initialization vector
@@ -0,0 +1,566 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Aws
6
+ module S3
7
+
8
+ REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile]
9
+ SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy]
10
+
11
+ # Provides an encryption client that encrypts and decrypts data client-side,
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.
18
+ #
19
+ # This client uses a process called "envelope encryption". Your private
20
+ # encryption keys and your data's plain-text are **never** sent to
21
+ # Amazon S3. **If you lose you encryption keys, you will not be able to
22
+ # decrypt your data.**
23
+ #
24
+ # ## Envelope Encryption Overview
25
+ #
26
+ # The goal of envelope encryption is to combine the performance of
27
+ # fast symmetric encryption while maintaining the secure key management
28
+ # that asymmetric keys provide.
29
+ #
30
+ # A one-time-use symmetric key (envelope key) is generated client-side.
31
+ # This is used to encrypt the data client-side. This key is then
32
+ # encrypted by your master key and stored alongside your data in Amazon
33
+ # S3.
34
+ #
35
+ # When accessing your encrypted data with the encryption client,
36
+ # the encrypted envelope key is retrieved and decrypted client-side
37
+ # with your master key. The envelope key is then used to decrypt the
38
+ # data client-side.
39
+ #
40
+ # One of the benefits of envelope encryption is that if your master key
41
+ # is compromised, you have the option of just re-encrypting the stored
42
+ # envelope symmetric keys, instead of re-encrypting all of the
43
+ # data in your account.
44
+ #
45
+ # ## Basic Usage
46
+ #
47
+ # The encryption client requires an {Aws::S3::Client}. If you do not
48
+ # provide a `:client`, then a client will be constructed for you.
49
+ #
50
+ # require 'openssl'
51
+ # key = OpenSSL::PKey::RSA.new(1024)
52
+ #
53
+ # # encryption client
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
+ # )
60
+ #
61
+ # # round-trip an object, encrypted/decrypted locally
62
+ # s3.put_object(bucket:'aws-sdk', key:'secret', body:'handshake')
63
+ # s3.get_object(bucket:'aws-sdk', key:'secret').body.read
64
+ # #=> 'handshake'
65
+ #
66
+ # # reading encrypted object without the encryption client
67
+ # # results in the getting the cipher text
68
+ # Aws::S3::Client.new.get_object(bucket:'aws-sdk', key:'secret').body.read
69
+ # #=> "... cipher text ..."
70
+ #
71
+ # ## Required Configuration
72
+ #
73
+ # You must configure all of the following:
74
+ #
75
+ # * a key or key provider - See the Keys section below. The key provided determines
76
+ # the key wrapping schema(s) supported for both encryption and decryption.
77
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
78
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`.
79
+ # More options will be added in future releases.
80
+ # * `security_profile` - Determines the support for reading objects written
81
+ # using older key wrap or content encryption schemas. If you need to read
82
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
83
+ # Otherwise, set it to `:v2`
84
+ #
85
+ # ## Keys
86
+ #
87
+ # For client-side encryption to work, you must provide one of the following:
88
+ #
89
+ # * An encryption key
90
+ # * A {KeyProvider}
91
+ # * A KMS encryption key id
92
+ #
93
+ # Additionally, the key wrapping schema must agree with the type of the key:
94
+ # * :aes_gcm: An AES encryption key or a key provider.
95
+ # * :rsa_oaep_sha1: An RSA encryption key or key provider.
96
+ # * :kms_context: A KMS encryption key id
97
+ #
98
+ # ### An Encryption Key
99
+ #
100
+ # You can pass a single encryption key. This is used as a master key
101
+ # encrypting and decrypting all object keys.
102
+ #
103
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key - used with `key_wrap_schema: :aes_gcm`
104
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair - used with `key_wrap_schema: :rsa_oaep_sha1`
105
+ #
106
+ # s3 = Aws::S3::EncryptionV2::Client.new(
107
+ # encryption_key: key,
108
+ # key_wrap_schema: :aes_gcm, # or :rsa_oaep_sha1 if using RSA
109
+ # content_encryption_schema: :aes_gcm_no_padding,
110
+ # security_profile: :v2
111
+ # )
112
+ #
113
+ # ### Key Provider
114
+ #
115
+ # Alternatively, you can use a {KeyProvider}. A key provider makes
116
+ # it easy to work with multiple keys and simplifies key rotation.
117
+ #
118
+ # ### KMS Encryption Key Id
119
+ #
120
+ # If you pass the id of an AWS Key Management Service (KMS) key and
121
+ # use :kms_content for the key_wrap_schema, then KMS will be used to
122
+ # generate, encrypt and decrypt object keys.
123
+ #
124
+ # # keep track of the kms key id
125
+ # kms = Aws::KMS::Client.new
126
+ # key_id = kms.create_key.key_metadata.key_id
127
+ #
128
+ # Aws::S3::EncryptionV2::Client.new(
129
+ # kms_key_id: key_id,
130
+ # kms_client: kms,
131
+ # key_wrap_schema: :kms_context,
132
+ # content_encryption_schema: :aes_gcm_no_padding,
133
+ # security_profile: :v2
134
+ # )
135
+ #
136
+ # ## Custom Key Providers
137
+ #
138
+ # A {KeyProvider} is any object that responds to:
139
+ #
140
+ # * `#encryption_materials`
141
+ # * `#key_for(materials_description)`
142
+ #
143
+ # Here is a trivial implementation of an in-memory key provider.
144
+ # This is provided as a demonstration of the key provider interface,
145
+ # and should not be used in production:
146
+ #
147
+ # class KeyProvider
148
+ #
149
+ # def initialize(default_key_name, keys)
150
+ # @keys = keys
151
+ # @encryption_materials = Aws::S3::EncryptionV2::Materials.new(
152
+ # key: @keys[default_key_name],
153
+ # description: JSON.dump(key: default_key_name),
154
+ # )
155
+ # end
156
+ #
157
+ # attr_reader :encryption_materials
158
+ #
159
+ # def key_for(matdesc)
160
+ # key_name = JSON.load(matdesc)['key']
161
+ # if key = @keys[key_name]
162
+ # key
163
+ # else
164
+ # raise "encryption key not found for: #{matdesc.inspect}"
165
+ # end
166
+ # end
167
+ # end
168
+ #
169
+ # Given the above key provider, you can create an encryption client that
170
+ # chooses the key to use based on the materials description stored with
171
+ # the encrypted object. This makes it possible to use multiple keys
172
+ # and simplifies key rotation.
173
+ #
174
+ # # uses "new-key" for encrypting objects, uses either for decrypting
175
+ # keys = KeyProvider.new('new-key', {
176
+ # "old-key" => Base64.decode64("kM5UVbhE/4rtMZJfsadYEdm2vaKFsmV2f5+URSeUCV4="),
177
+ # "new-key" => Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
178
+ # }),
179
+ #
180
+ # # chooses the key based on the materials description stored
181
+ # # with the encrypted object
182
+ # s3 = Aws::S3::EncryptionV2::Client.new(
183
+ # key_provider: keys,
184
+ # key_wrap_schema: ...,
185
+ # content_encryption_schema: :aes_gcm_no_padding,
186
+ # security_profile: :v2
187
+ # )
188
+ #
189
+ # ## Materials Description
190
+ #
191
+ # A materials description is JSON document string that is stored
192
+ # in the metadata (or instruction file) of an encrypted object.
193
+ # The {DefaultKeyProvider} uses the empty JSON document `"{}"`.
194
+ #
195
+ # When building a key provider, you are free to store whatever
196
+ # information you need to identify the master key that was used
197
+ # to encrypt the object.
198
+ #
199
+ # ## Envelope Location
200
+ #
201
+ # By default, the encryption client store the encryption envelope
202
+ # with the object, as metadata. You can choose to have the envelope
203
+ # stored in a separate "instruction file". An instruction file
204
+ # is an object, with the key of the encrypted object, suffixed with
205
+ # `".instruction"`.
206
+ #
207
+ # Specify the `:envelope_location` option as `:instruction_file` to
208
+ # use an instruction file for storing the envelope.
209
+ #
210
+ # # default behavior
211
+ # s3 = Aws::S3::EncryptionV2::Client.new(
212
+ # key_provider: ...,
213
+ # envelope_location: :metadata,
214
+ # )
215
+ #
216
+ # # store envelope in a separate object
217
+ # s3 = Aws::S3::EncryptionV2::Client.new(
218
+ # key_provider: ...,
219
+ # envelope_location: :instruction_file,
220
+ # instruction_file_suffix: '.instruction' # default
221
+ # key_wrap_schema: ...,
222
+ # content_encryption_schema: :aes_gcm_no_padding,
223
+ # security_profile: :v2
224
+ # )
225
+ #
226
+ # When using an instruction file, multiple requests are made when
227
+ # putting and getting the object. **This may cause issues if you are
228
+ # issuing concurrent PUT and GET requests to an encrypted object.**
229
+ #
230
+ module EncryptionV2
231
+ class Client
232
+
233
+ extend Deprecations
234
+ extend Forwardable
235
+ def_delegators :@client, :config, :delete_object, :head_object, :build_request
236
+
237
+ # Creates a new encryption client. You must configure all of the following:
238
+ #
239
+ # * a key or key provider - The key provided also determines the key wrapping
240
+ # schema(s) supported for both encryption and decryption.
241
+ # * `key_wrap_schema` - The key wrapping schema. It must match the type of key configured.
242
+ # * `content_encryption_schema` - The only supported value currently is `:aes_gcm_no_padding`
243
+ # More options will be added in future releases.
244
+ # * `security_profile` - Determines the support for reading objects written
245
+ # using older key wrap or content encryption schemas. If you need to read
246
+ # legacy objects encrypted by an existing V1 Client, then set this to `:v2_and_legacy`.
247
+ # Otherwise, set it to `:v2`
248
+ #
249
+ # To configure the key you must provide one of the following set of options:
250
+ #
251
+ # * `:encryption_key`
252
+ # * `:kms_key_id`
253
+ # * `:key_provider`
254
+ #
255
+ # You may also pass any other options accepted by `Client#initialize`.
256
+ #
257
+ # @option options [S3::Client] :client A basic S3 client that is used
258
+ # to make api calls. If a `:client` is not provided, a new {S3::Client}
259
+ # will be constructed.
260
+ #
261
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key The master
262
+ # key to use for encrypting/decrypting all objects.
263
+ #
264
+ # @option options [String] :kms_key_id When you provide a `:kms_key_id`,
265
+ # then AWS Key Management Service (KMS) will be used to manage the
266
+ # object encryption keys. By default a {KMS::Client} will be
267
+ # constructed for KMS API calls. Alternatively, you can provide
268
+ # your own via `:kms_client`. To only support decryption/reads, you may
269
+ # provide `:allow_decrypt_with_any_cmk` which will use
270
+ # the implicit CMK associated with the data during reads but will
271
+ # not allow you to encrypt/write objects with this client.
272
+ #
273
+ # @option options [#key_for] :key_provider Any object that responds
274
+ # to `#key_for`. This method should accept a materials description
275
+ # JSON document string and return return an encryption key.
276
+ #
277
+ # @option options [required, Symbol] :key_wrap_schema The Key wrapping
278
+ # schema to be used. It must match the type of key configured.
279
+ # Must be one of the following:
280
+ #
281
+ # * :kms_context (Must provide kms_key_id)
282
+ # * :aes_gcm (Must provide an AES (string) key)
283
+ # * :rsa_oaep_sha1 (Must provide an RSA key)
284
+ #
285
+ # @option options [required, Symbol] :content_encryption_schema
286
+ # Must be one of the following:
287
+ #
288
+ # * :aes_gcm_no_padding
289
+ #
290
+ # @option options [Required, Symbol] :security_profile
291
+ # Determines the support for reading objects written using older
292
+ # key wrap or content encryption schemas.
293
+ # Must be one of the following:
294
+ #
295
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
296
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
297
+ #
298
+ # @option options [Symbol] :envelope_location (:metadata) Where to
299
+ # store the envelope encryption keys. By default, the envelope is
300
+ # stored with the encrypted object. If you pass `:instruction_file`,
301
+ # then the envelope is stored in a separate object in Amazon S3.
302
+ #
303
+ # @option options [String] :instruction_file_suffix ('.instruction')
304
+ # When `:envelope_location` is `:instruction_file` then the
305
+ # instruction file uses the object key with this suffix appended.
306
+ #
307
+ # @option options [KMS::Client] :kms_client A default {KMS::Client}
308
+ # is constructed when using KMS to manage encryption keys.
309
+ #
310
+ def initialize(options = {})
311
+ validate_params(options)
312
+ @client = extract_client(options)
313
+ @cipher_provider = cipher_provider(options)
314
+ @envelope_location = extract_location(options)
315
+ @instruction_file_suffix = extract_suffix(options)
316
+ @kms_allow_decrypt_with_any_cmk =
317
+ options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
318
+ @security_profile = extract_security_profile(options)
319
+ end
320
+
321
+ # @return [S3::Client]
322
+ attr_reader :client
323
+
324
+ # @return [KeyProvider, nil] Returns `nil` if you are using
325
+ # AWS Key Management Service (KMS).
326
+ attr_reader :key_provider
327
+
328
+ # @return [Symbol] Determines the support for reading objects written
329
+ # using older key wrap or content encryption schemas.
330
+ attr_reader :security_profile
331
+
332
+ # @return [Boolean] If true the provided KMS key_id will not be used
333
+ # during decrypt, allowing decryption with the key_id from the object.
334
+ attr_reader :kms_allow_decrypt_with_any_cmk
335
+
336
+ # @return [Symbol<:metadata, :instruction_file>]
337
+ attr_reader :envelope_location
338
+
339
+ # @return [String] When {#envelope_location} is `:instruction_file`,
340
+ # the envelope is stored in the object with the object key suffixed
341
+ # by this string.
342
+ attr_reader :instruction_file_suffix
343
+
344
+ # Uploads an object to Amazon S3, encrypting data client-side.
345
+ # See {S3::Client#put_object} for documentation on accepted
346
+ # request parameters.
347
+ # @option params [Hash] :kms_encryption_context Additional encryption
348
+ # context to use with KMS. Applies only when KMS is used. In order
349
+ # to decrypt the object you will need to provide the identical
350
+ # :kms_encryption_context to `get_object`.
351
+ # @option (see S3::Client#put_object)
352
+ # @return (see S3::Client#put_object)
353
+ # @see S3::Client#put_object
354
+ def put_object(params = {})
355
+ kms_encryption_context = params.delete(:kms_encryption_context)
356
+ req = @client.build_request(:put_object, params)
357
+ req.handlers.add(EncryptHandler, priority: 95)
358
+ req.context[:encryption] = {
359
+ cipher_provider: @cipher_provider,
360
+ envelope_location: @envelope_location,
361
+ instruction_file_suffix: @instruction_file_suffix,
362
+ kms_encryption_context: kms_encryption_context
363
+ }
364
+ req.send_request
365
+ end
366
+
367
+ # Gets an object from Amazon S3, decrypting data locally.
368
+ # See {S3::Client#get_object} for documentation on accepted
369
+ # request parameters.
370
+ # Warning: If you provide a block to get_object or set the request
371
+ # parameter :response_target to a Proc, then read the entire object to the
372
+ # end before you start using the decrypted data. This is to verify that
373
+ # the object has not been modified since it was encrypted.
374
+ #
375
+ # @option options [Symbol] :security_profile
376
+ # Determines the support for reading objects written using older
377
+ # key wrap or content encryption schemas. Overrides the value set
378
+ # on client construction if provided.
379
+ # Must be one of the following:
380
+ #
381
+ # * :v2 - Reads of legacy (v1) objects are NOT allowed
382
+ # * :v2_and_legacy - Enables reading of legacy (V1) schemas.
383
+ # @option params [String] :instruction_file_suffix The suffix
384
+ # used to find the instruction file containing the encryption
385
+ # envelope. You should not set this option when the envelope
386
+ # is stored in the object metadata. Defaults to
387
+ # {#instruction_file_suffix}.
388
+ # @option params [Hash] :kms_encryption_context Additional encryption
389
+ # context to use with KMS. Applies only when KMS is used.
390
+ # @option options [Boolean] :kms_allow_decrypt_with_any_cmk (false)
391
+ # By default the KMS CMK ID (kms_key_id) will be used during decrypt
392
+ # and will fail if there is a mismatch. Setting this to true
393
+ # will use the implicit CMK associated with the data.
394
+ # @option (see S3::Client#get_object)
395
+ # @return (see S3::Client#get_object)
396
+ # @see S3::Client#get_object
397
+ # @note The `:range` request parameter is not supported.
398
+ def get_object(params = {}, &block)
399
+ if params[:range]
400
+ raise NotImplementedError, '#get_object with :range not supported'
401
+ end
402
+ envelope_location, instruction_file_suffix = envelope_options(params)
403
+ kms_encryption_context = params.delete(:kms_encryption_context)
404
+ kms_any_cmk_mode = kms_any_cmk_mode(params)
405
+ security_profile = security_profile_from_params(params)
406
+
407
+ req = @client.build_request(:get_object, params)
408
+ req.handlers.add(DecryptHandler)
409
+ req.context[:encryption] = {
410
+ cipher_provider: @cipher_provider,
411
+ envelope_location: envelope_location,
412
+ instruction_file_suffix: instruction_file_suffix,
413
+ kms_encryption_context: kms_encryption_context,
414
+ kms_allow_decrypt_with_any_cmk: kms_any_cmk_mode,
415
+ security_profile: security_profile
416
+ }
417
+ req.send_request(target: block)
418
+ end
419
+
420
+ private
421
+
422
+ # Validate required parameters exist and don't conflict.
423
+ # The cek_alg and wrap_alg are passed on to the CipherProviders
424
+ # and further validated there
425
+ def validate_params(options)
426
+ unless (missing_params = REQUIRED_PARAMS - options.keys).empty?
427
+ raise ArgumentError, "Missing required parameter(s): "\
428
+ "#{missing_params.map{ |s| ":#{s}" }.join(', ')}"
429
+ end
430
+
431
+ wrap_alg = options[:key_wrap_schema]
432
+
433
+ # validate that the wrap alg matches the type of key given
434
+ case wrap_alg
435
+ when :kms_context
436
+ unless options[:kms_key_id]
437
+ raise ArgumentError, 'You must provide :kms_key_id to use :kms_context'
438
+ end
439
+ end
440
+ end
441
+
442
+ def extract_client(options)
443
+ options[:client] || begin
444
+ options = options.dup
445
+ options.delete(:kms_key_id)
446
+ options.delete(:kms_client)
447
+ options.delete(:key_provider)
448
+ options.delete(:encryption_key)
449
+ options.delete(:envelope_location)
450
+ options.delete(:instruction_file_suffix)
451
+ REQUIRED_PARAMS.each { |p| options.delete(p) }
452
+ S3::Client.new(options)
453
+ end
454
+ end
455
+
456
+ def kms_client(options)
457
+ options[:kms_client] || begin
458
+ KMS::Client.new(
459
+ region: @client.config.region,
460
+ credentials: @client.config.credentials,
461
+ )
462
+ end
463
+ end
464
+
465
+ def cipher_provider(options)
466
+ if options[:kms_key_id]
467
+ KmsCipherProvider.new(
468
+ kms_key_id: options[:kms_key_id],
469
+ kms_client: kms_client(options),
470
+ key_wrap_schema: options[:key_wrap_schema],
471
+ content_encryption_schema: options[:content_encryption_schema]
472
+ )
473
+ else
474
+ @key_provider = extract_key_provider(options)
475
+ DefaultCipherProvider.new(
476
+ key_provider: @key_provider,
477
+ key_wrap_schema: options[:key_wrap_schema],
478
+ content_encryption_schema: options[:content_encryption_schema]
479
+ )
480
+ end
481
+ end
482
+
483
+ def extract_key_provider(options)
484
+ if options[:key_provider]
485
+ options[:key_provider]
486
+ elsif options[:encryption_key]
487
+ DefaultKeyProvider.new(options)
488
+ else
489
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
490
+ raise ArgumentError, msg
491
+ end
492
+ end
493
+
494
+ def envelope_options(params)
495
+ location = params.delete(:envelope_location) || @envelope_location
496
+ suffix = params.delete(:instruction_file_suffix)
497
+ if suffix
498
+ [:instruction_file, suffix]
499
+ else
500
+ [location, @instruction_file_suffix]
501
+ end
502
+ end
503
+
504
+ def extract_location(options)
505
+ location = options[:envelope_location] || :metadata
506
+ if [:metadata, :instruction_file].include?(location)
507
+ location
508
+ else
509
+ msg = ':envelope_location must be :metadata or :instruction_file '\
510
+ "got #{location.inspect}"
511
+ raise ArgumentError, msg
512
+ end
513
+ end
514
+
515
+ def extract_suffix(options)
516
+ suffix = options[:instruction_file_suffix] || '.instruction'
517
+ if suffix.is_a? String
518
+ suffix
519
+ else
520
+ msg = ':instruction_file_suffix must be a String'
521
+ raise ArgumentError, msg
522
+ end
523
+ end
524
+
525
+ def kms_any_cmk_mode(params)
526
+ if !params[:kms_allow_decrypt_with_any_cmk].nil?
527
+ params.delete(:kms_allow_decrypt_with_any_cmk)
528
+ else
529
+ @kms_allow_decrypt_with_any_cmk
530
+ end
531
+ end
532
+
533
+ def extract_security_profile(options)
534
+ validate_security_profile(options[:security_profile])
535
+ end
536
+
537
+ def security_profile_from_params(params)
538
+ security_profile =
539
+ if !params[:security_profile].nil?
540
+ params.delete(:security_profile)
541
+ else
542
+ @security_profile
543
+ end
544
+ validate_security_profile(security_profile)
545
+ end
546
+
547
+ def validate_security_profile(security_profile)
548
+ unless SUPPORTED_SECURITY_PROFILES.include? security_profile
549
+ raise ArgumentError, "Unsupported security profile: :#{security_profile}. " \
550
+ "Please provide one of: #{SUPPORTED_SECURITY_PROFILES.map { |s| ":#{s}" }.join(', ')}"
551
+ end
552
+ if security_profile == :v2_and_legacy && !@warned_about_legacy
553
+ @warned_about_legacy = true
554
+ warn(
555
+ 'The S3 Encryption Client is configured to read encrypted objects ' \
556
+ "with legacy encryption modes. If you don't have objects " \
557
+ 'encrypted with these legacy modes, you should disable support ' \
558
+ 'for them to enhance security.'
559
+ )
560
+ end
561
+ security_profile
562
+ end
563
+ end
564
+ end
565
+ end
566
+ end