aws-sdk-s3 1.206.0 → 1.208.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/client.rb +4 -4
- data/lib/aws-sdk-s3/customizations.rb +1 -0
- data/lib/aws-sdk-s3/encryption/client.rb +2 -2
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +98 -23
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +8 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
- data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
- data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
- data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
- data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
- data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
- data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
- data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
- data/lib/aws-sdk-s3.rb +1 -1
- data/sig/client.rbs +1 -1
- data/sig/types.rbs +1 -1
- metadata +17 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
module EncryptionV2
|
|
8
|
+
# @api private
|
|
9
|
+
class Decryption
|
|
10
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
11
|
+
##% - The mapkey "x-amz-key" MUST be present for V1 format objects.
|
|
12
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
13
|
+
##% - The mapkey "x-amz-iv" MUST be present for V1 format objects.
|
|
14
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
15
|
+
##% - The mapkey "x-amz-matdesc" MUST be present for V1 format objects.
|
|
16
|
+
V1_ENVELOPE_KEYS = %w[
|
|
17
|
+
x-amz-key
|
|
18
|
+
x-amz-iv
|
|
19
|
+
x-amz-matdesc
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
23
|
+
##% - The mapkey "x-amz-key-v2" MUST be present for V2 format objects.
|
|
24
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
25
|
+
##% - The mapkey "x-amz-iv" MUST be present for V2 format objects.
|
|
26
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
27
|
+
##% - The mapkey "x-amz-cek-alg" MUST be present for V2 format objects.
|
|
28
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
29
|
+
##% - The mapkey "x-amz-wrap-alg" MUST be present for V2 format objects.
|
|
30
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
31
|
+
##% - The mapkey "x-amz-matdesc" MUST be present for V2 format objects.
|
|
32
|
+
V2_ENVELOPE_KEYS = %w[
|
|
33
|
+
x-amz-key-v2
|
|
34
|
+
x-amz-iv
|
|
35
|
+
x-amz-cek-alg
|
|
36
|
+
x-amz-wrap-alg
|
|
37
|
+
x-amz-matdesc
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
41
|
+
##= type=exception
|
|
42
|
+
##= reason=The implementation treats this as optional, but verifies its value.
|
|
43
|
+
##% - The mapkey "x-amz-tag-len" MUST be present for V2 format objects.
|
|
44
|
+
V2_OPTIONAL_KEYS = %w[x-amz-tag-len].freeze
|
|
45
|
+
|
|
46
|
+
POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS + V2_ENVELOPE_KEYS + V2_OPTIONAL_KEYS).uniq
|
|
47
|
+
|
|
48
|
+
POSSIBLE_WRAPPING_FORMATS = %w[
|
|
49
|
+
AES/GCM
|
|
50
|
+
kms
|
|
51
|
+
kms+context
|
|
52
|
+
RSA-OAEP-SHA1
|
|
53
|
+
].freeze
|
|
54
|
+
|
|
55
|
+
POSSIBLE_ENCRYPTION_FORMATS = %w[
|
|
56
|
+
AES/GCM/NoPadding
|
|
57
|
+
AES/CBC/PKCS5Padding
|
|
58
|
+
AES/CBC/PKCS7Padding
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
AUTH_REQUIRED_CEK_ALGS = %w[AES/GCM/NoPadding].freeze
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
def decryption_cipher(context)
|
|
65
|
+
if (envelope = get_encryption_envelope(context))
|
|
66
|
+
cipher = context[:encryption][:cipher_provider]
|
|
67
|
+
.decryption_cipher(
|
|
68
|
+
envelope,
|
|
69
|
+
context[:encryption]
|
|
70
|
+
)
|
|
71
|
+
[cipher, envelope]
|
|
72
|
+
else
|
|
73
|
+
raise Errors::DecryptionError, 'unable to locate encryption envelope'
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_decrypter(context, cipher, envelope)
|
|
78
|
+
if body_contains_auth_tag?(envelope)
|
|
79
|
+
authenticated_decrypter(context, cipher, envelope)
|
|
80
|
+
else
|
|
81
|
+
IODecrypter.new(cipher, context.http_response.body)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def get_encryption_envelope(context)
|
|
86
|
+
if context[:encryption][:envelope_location] == :metadata
|
|
87
|
+
envelope_from_metadata(context) || envelope_from_instr_file(context)
|
|
88
|
+
else
|
|
89
|
+
envelope_from_instr_file(context) || envelope_from_metadata(context)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def envelope_from_metadata(context)
|
|
94
|
+
possible_envelope = {}
|
|
95
|
+
POSSIBLE_ENVELOPE_KEYS.each do |suffix|
|
|
96
|
+
if (value = context.http_response.headers["x-amz-meta-#{suffix}"])
|
|
97
|
+
possible_envelope[suffix] = value
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
extract_envelope(possible_envelope)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def envelope_from_instr_file(context)
|
|
104
|
+
suffix = context[:encryption][:instruction_file_suffix]
|
|
105
|
+
possible_envelope = Json.load(context.client.get_object(
|
|
106
|
+
bucket: context.params[:bucket],
|
|
107
|
+
key: context.params[:key] + suffix
|
|
108
|
+
).body.read)
|
|
109
|
+
extract_envelope(possible_envelope)
|
|
110
|
+
rescue S3::Errors::ServiceError, Json::ParseError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def extract_envelope(hash)
|
|
115
|
+
return nil unless hash
|
|
116
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
|
|
117
|
+
##% - If the metadata contains "x-amz-iv" and "x-amz-key" then the object MUST be considered as an S3EC-encrypted object using the V1 format.
|
|
118
|
+
return v1_envelope(hash) if hash.key?('x-amz-key')
|
|
119
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#determining-s3ec-object-status
|
|
120
|
+
##% - If the metadata contains "x-amz-iv" and "x-amz-metadata-x-amz-key-v2" then the object MUST be considered as an S3EC-encrypted object using the V2 format.
|
|
121
|
+
return v2_envelope(hash) if hash.key?('x-amz-key-v2')
|
|
122
|
+
|
|
123
|
+
return unless hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
|
|
124
|
+
|
|
125
|
+
msg = "unsupported envelope encryption version #{::Regexp.last_match(1)}"
|
|
126
|
+
raise Errors::DecryptionError, msg
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def v1_envelope(envelope)
|
|
130
|
+
envelope
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def v2_envelope(envelope)
|
|
134
|
+
unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
|
|
135
|
+
alg = envelope['x-amz-cek-alg'].inspect
|
|
136
|
+
msg = "unsupported content encrypting key (cek) format: #{alg}"
|
|
137
|
+
raise Errors::DecryptionError, msg
|
|
138
|
+
end
|
|
139
|
+
unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
|
|
140
|
+
alg = envelope['x-amz-wrap-alg'].inspect
|
|
141
|
+
msg = "unsupported key wrapping algorithm: #{alg}"
|
|
142
|
+
raise Errors::DecryptionError, msg
|
|
143
|
+
end
|
|
144
|
+
unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
|
|
145
|
+
msg = "incomplete v2 encryption envelope:\n"
|
|
146
|
+
msg += " missing: #{missing_keys.join(',')}\n"
|
|
147
|
+
raise Errors::DecryptionError, msg
|
|
148
|
+
end
|
|
149
|
+
envelope
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def body_contains_auth_tag?(envelope)
|
|
153
|
+
AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# This method fetches the tag from the end of the object by
|
|
157
|
+
# making a GET Object w/range request. This auth tag is used
|
|
158
|
+
# to initialize the cipher, and the decrypter truncates the
|
|
159
|
+
# auth tag from the body when writing the final bytes.
|
|
160
|
+
def authenticated_decrypter(context, cipher, envelope)
|
|
161
|
+
http_resp = context.http_response
|
|
162
|
+
content_length = http_resp.headers['content-length'].to_i
|
|
163
|
+
auth_tag_length = auth_tag_length(envelope)
|
|
164
|
+
|
|
165
|
+
auth_tag = context.client.get_object(
|
|
166
|
+
bucket: context.params[:bucket],
|
|
167
|
+
key: context.params[:key],
|
|
168
|
+
version_id: context.params[:version_id],
|
|
169
|
+
range: "bytes=-#{auth_tag_length}"
|
|
170
|
+
).body.read
|
|
171
|
+
|
|
172
|
+
cipher.auth_tag = auth_tag
|
|
173
|
+
cipher.auth_data = ''
|
|
174
|
+
|
|
175
|
+
# The encrypted object contains both the cipher text
|
|
176
|
+
# plus a trailing auth tag.
|
|
177
|
+
IOAuthDecrypter.new(
|
|
178
|
+
io: http_resp.body,
|
|
179
|
+
encrypted_content_length: content_length - auth_tag_length,
|
|
180
|
+
cipher: cipher
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Determine the auth tag length from the algorithm
|
|
185
|
+
# Validate it against the value provided in the x-amz-tag-len
|
|
186
|
+
# Return the tag length in bytes
|
|
187
|
+
def auth_tag_length(envelope)
|
|
188
|
+
tag_length =
|
|
189
|
+
case envelope['x-amz-cek-alg']
|
|
190
|
+
when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
|
|
191
|
+
else
|
|
192
|
+
raise ArgumentError, 'Unsupported cek-alg: ' \
|
|
193
|
+
"#{envelope['x-amz-cek-alg']}"
|
|
194
|
+
end
|
|
195
|
+
if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
|
|
196
|
+
raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
tag_length
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -14,11 +14,15 @@ module Aws
|
|
|
14
14
|
options[:key_wrap_schema],
|
|
15
15
|
@key_provider.encryption_materials.key
|
|
16
16
|
)
|
|
17
|
+
##= ../specification/s3-encryption/encryption.md#content-encryption
|
|
18
|
+
##% The S3EC MUST use the encryption algorithm configured during [client](./client.md) initialization.
|
|
17
19
|
@content_encryption_schema = validate_cek(
|
|
18
20
|
options[:content_encryption_schema]
|
|
19
21
|
)
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
attr_reader :key_provider
|
|
25
|
+
|
|
22
26
|
# @return [Array<Hash,Cipher>] Creates an returns a new encryption
|
|
23
27
|
# envelope and encryption cipher.
|
|
24
28
|
def encryption_cipher(options = {})
|
|
@@ -30,9 +34,14 @@ module Aws
|
|
|
30
34
|
)
|
|
31
35
|
else
|
|
32
36
|
enc_key = encode64(
|
|
37
|
+
##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
|
|
38
|
+
##% The client MUST NOT provide any AAD when encrypting with ALG_AES_256_GCM_IV12_TAG16_NO_KDF.
|
|
33
39
|
encrypt_aes_gcm(envelope_key(cipher), @content_encryption_schema)
|
|
34
40
|
)
|
|
35
41
|
end
|
|
42
|
+
|
|
43
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
|
|
44
|
+
##% Objects encrypted with ALG_AES_256_GCM_IV12_TAG16_NO_KDF MUST use the V2 message format version only.
|
|
36
45
|
envelope = {
|
|
37
46
|
'x-amz-key-v2' => enc_key,
|
|
38
47
|
'x-amz-cek-alg' => @content_encryption_schema,
|
|
@@ -52,8 +61,16 @@ module Aws
|
|
|
52
61
|
master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
|
|
53
62
|
if envelope.key? 'x-amz-key'
|
|
54
63
|
unless options[:security_profile] == :v2_and_legacy
|
|
64
|
+
##= ../specification/s3-encryption/decryption.md#legacy-decryption
|
|
65
|
+
##% If the S3EC is not configured to enable legacy unauthenticated content decryption, the client MUST throw an exception when attempting to decrypt an object encrypted with a legacy unauthenticated algorithm suite.
|
|
66
|
+
##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
|
|
67
|
+
##% When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy content encryption algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy content encryption algorithm.
|
|
55
68
|
raise Errors::LegacyDecryptionError
|
|
56
69
|
end
|
|
70
|
+
##= ../specification/s3-encryption/decryption.md#legacy-decryption
|
|
71
|
+
##% The S3EC MUST NOT decrypt objects encrypted using legacy unauthenticated algorithm suites unless specifically configured to do so.
|
|
72
|
+
##= ../specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes
|
|
73
|
+
##% When enabled, the S3EC MUST be able to decrypt objects encrypted with all content encryption algorithms (both legacy and fully supported).
|
|
57
74
|
# Support for decryption of legacy objects
|
|
58
75
|
key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
|
|
59
76
|
iv = decode64(envelope['x-amz-iv'])
|
|
@@ -28,6 +28,8 @@ module Aws
|
|
|
28
28
|
context.client.put_object(
|
|
29
29
|
bucket: context.params[:bucket],
|
|
30
30
|
key: context.params[:key] + suffix,
|
|
31
|
+
##= ../specification/s3-encryption/data-format/metadata-strategy.md#v1-v2-instruction-files
|
|
32
|
+
##% In the V1/V2 message format, all of the content metadata MUST be stored in the Instruction File.
|
|
31
33
|
body: Json.dump(envelope)
|
|
32
34
|
)
|
|
33
35
|
else # :metadata
|
|
@@ -44,6 +44,8 @@ module Aws
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
def encrypt_to_stringio(cipher, plain_text)
|
|
47
|
+
##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
|
|
48
|
+
##% The client MUST append the GCM auth tag to the ciphertext if the underlying crypto provider does not do so automatically.
|
|
47
49
|
if plain_text.empty?
|
|
48
50
|
StringIO.new(cipher.final + cipher.auth_tag)
|
|
49
51
|
else
|
|
@@ -33,6 +33,8 @@ module Aws
|
|
|
33
33
|
end
|
|
34
34
|
cipher = Utils.aes_encryption_cipher(:GCM)
|
|
35
35
|
cipher.key = key_data.plaintext
|
|
36
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
|
|
37
|
+
##% Objects encrypted with ALG_AES_256_GCM_IV12_TAG16_NO_KDF MUST use the V2 message format version only.
|
|
36
38
|
envelope = {
|
|
37
39
|
'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
|
|
38
40
|
'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
|
|
@@ -53,9 +55,15 @@ module Aws
|
|
|
53
55
|
|
|
54
56
|
case envelope['x-amz-wrap-alg']
|
|
55
57
|
when 'kms'
|
|
58
|
+
##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
|
|
59
|
+
##% The S3EC MUST support the option to enable or disable legacy wrapping algorithms.
|
|
56
60
|
unless options[:security_profile] == :v2_and_legacy
|
|
61
|
+
##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
|
|
62
|
+
##% When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy wrapping algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy wrapping algorithm.
|
|
57
63
|
raise Errors::LegacyDecryptionError
|
|
58
64
|
end
|
|
65
|
+
##= ../specification/s3-encryption/client.md#enable-legacy-wrapping-algorithms
|
|
66
|
+
##% When enabled, the S3EC MUST be able to decrypt objects encrypted with all supported wrapping algorithms (both legacy and fully supported).
|
|
59
67
|
when 'kms+context'
|
|
60
68
|
if cek_alg != encryption_context['aws:x-amz-cek-alg']
|
|
61
69
|
raise Errors::CEKAlgMismatchError
|
|
@@ -80,6 +80,11 @@ module Aws
|
|
|
80
80
|
# @param [OpenSSL::PKey::RSA, String, nil] key
|
|
81
81
|
# @param [String, nil] iv The initialization vector
|
|
82
82
|
def aes_cipher(mode, block_mode, key, iv)
|
|
83
|
+
##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-iv12-tag16-no-kdf
|
|
84
|
+
##% The client MUST initialize the cipher,
|
|
85
|
+
##% or call an AES-GCM encryption API, with the plaintext data key, the generated IV,
|
|
86
|
+
##% and the tag length defined in the Algorithm Suite
|
|
87
|
+
##% when encrypting with ALG_AES_256_GCM_IV12_TAG16_NO_KDF.
|
|
83
88
|
cipher = key ?
|
|
84
89
|
OpenSSL::Cipher.new("aes-#{cipher_size(key)}-#{block_mode.downcase}") :
|
|
85
90
|
OpenSSL::Cipher.new("aes-256-#{block_mode.downcase}")
|