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.
- checksums.yaml +4 -4
- data/lib/aws-sdk-s3.rb +1 -1
- data/lib/aws-sdk-s3/bucket.rb +2 -2
- data/lib/aws-sdk-s3/client.rb +791 -505
- data/lib/aws-sdk-s3/encryption.rb +2 -0
- data/lib/aws-sdk-s3/encryption/client.rb +11 -0
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +52 -28
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +41 -5
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +5 -5
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +32 -3
- data/lib/aws-sdk-s3/encryption/utils.rb +23 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +198 -22
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +40 -12
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +77 -10
- data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +7 -4
- data/lib/aws-sdk-s3/encryptionV2/errors.rb +24 -0
- data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +90 -20
- data/lib/aws-sdk-s3/encryptionV2/materials.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +2 -15
- data/lib/aws-sdk-s3/encryption_v2.rb +4 -1
- data/lib/aws-sdk-s3/multipart_upload_part.rb +5 -5
- data/lib/aws-sdk-s3/object.rb +10 -9
- data/lib/aws-sdk-s3/object_summary.rb +23 -7
- data/lib/aws-sdk-s3/object_version.rb +2 -2
- data/lib/aws-sdk-s3/presigner.rb +2 -2
- data/lib/aws-sdk-s3/types.rb +63 -26
- metadata +2 -2
@@ -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
|
-
|
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?(
|
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]
|
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']
|
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
|
144
|
+
unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
|
131
145
|
msg = "incomplete v2 encryption envelope:\n"
|
132
|
-
msg += "
|
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
|
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 =
|
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.
|
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?(
|
176
|
-
|
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 =
|
182
|
-
elsif !context.config.user_agent_suffix.include?
|
183
|
-
context.config.user_agent_suffix +=
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
43
|
-
|
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 =
|
53
|
-
elsif !context.config.user_agent_suffix.include?
|
54
|
-
context.config.user_agent_suffix +=
|
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
|
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
|
9
|
-
# modern and secure algorithms.
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
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(
|
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(
|
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
|
88
|
-
# then KMS will be used to
|
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(
|
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
|
193
|
-
#
|
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
|
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
|
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(
|
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
|