aws-sdk-s3 1.73.0 → 1.78.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 +127 -114
- data/lib/aws-sdk-s3/customizations/object.rb +12 -1
- 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 +64 -29
- 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/io_decrypter.rb +7 -6
- 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 +201 -23
- 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/file_uploader.rb +11 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +37 -2
- data/lib/aws-sdk-s3/multipart_upload_part.rb +1 -1
- data/lib/aws-sdk-s3/object.rb +1 -1
- data/lib/aws-sdk-s3/object_summary.rb +19 -3
- data/lib/aws-sdk-s3/plugins/accelerate.rb +27 -38
- data/lib/aws-sdk-s3/plugins/dualstack.rb +3 -1
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +1 -1
- data/lib/aws-sdk-s3/presigned_post.rb +61 -28
- data/lib/aws-sdk-s3/presigner.rb +2 -2
- data/lib/aws-sdk-s3/resource.rb +1 -1
- data/lib/aws-sdk-s3/types.rb +25 -8
- metadata +4 -4
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module Aws
|
@@ -8,27 +10,36 @@ module Aws
|
|
8
10
|
|
9
11
|
def initialize(options = {})
|
10
12
|
@key_provider = options[:key_provider]
|
13
|
+
@key_wrap_schema = validate_key_wrap(
|
14
|
+
options[:key_wrap_schema],
|
15
|
+
@key_provider.encryption_materials.key
|
16
|
+
)
|
17
|
+
@content_encryption_schema = validate_cek(
|
18
|
+
options[:content_encryption_schema]
|
19
|
+
)
|
11
20
|
end
|
12
21
|
|
13
22
|
# @return [Array<Hash,Cipher>] Creates an returns a new encryption
|
14
23
|
# envelope and encryption cipher.
|
15
24
|
def encryption_cipher(options = {})
|
25
|
+
validate_options(options)
|
16
26
|
cipher = Utils.aes_encryption_cipher(:GCM)
|
17
|
-
cek_alg = 'AES/GCM/NoPadding'
|
18
27
|
if @key_provider.encryption_materials.key.is_a? OpenSSL::PKey::RSA
|
19
|
-
|
20
|
-
|
28
|
+
enc_key = encode64(
|
29
|
+
encrypt_rsa(envelope_key(cipher), @content_encryption_schema)
|
30
|
+
)
|
21
31
|
else
|
22
|
-
|
23
|
-
|
32
|
+
enc_key = encode64(
|
33
|
+
encrypt_aes_gcm(envelope_key(cipher), @content_encryption_schema)
|
34
|
+
)
|
24
35
|
end
|
25
36
|
envelope = {
|
26
37
|
'x-amz-key-v2' => enc_key,
|
27
|
-
'x-amz-cek-alg' =>
|
28
|
-
'x-amz-tag-len' =>
|
29
|
-
'x-amz-wrap-alg' =>
|
38
|
+
'x-amz-cek-alg' => @content_encryption_schema,
|
39
|
+
'x-amz-tag-len' => (AES_GCM_TAG_LEN_BYTES * 8).to_s,
|
40
|
+
'x-amz-wrap-alg' => @key_wrap_schema,
|
30
41
|
'x-amz-iv' => encode64(envelope_iv(cipher)),
|
31
|
-
'x-amz-matdesc' => materials_description
|
42
|
+
'x-amz-matdesc' => materials_description
|
32
43
|
}
|
33
44
|
cipher.auth_data = '' # auth_data must be set after key and iv
|
34
45
|
[envelope, cipher]
|
@@ -37,8 +48,12 @@ module Aws
|
|
37
48
|
# @return [Cipher] Given an encryption envelope, returns a
|
38
49
|
# decryption cipher.
|
39
50
|
def decryption_cipher(envelope, options = {})
|
51
|
+
validate_options(options)
|
40
52
|
master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
|
41
53
|
if envelope.key? 'x-amz-key'
|
54
|
+
unless options[:security_profile] == :v2_and_legacy
|
55
|
+
raise Errors::LegacyDecryptionError
|
56
|
+
end
|
42
57
|
# Support for decryption of legacy objects
|
43
58
|
key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
|
44
59
|
iv = decode64(envelope['x-amz-iv'])
|
@@ -51,13 +66,26 @@ module Aws
|
|
51
66
|
key =
|
52
67
|
case envelope['x-amz-wrap-alg']
|
53
68
|
when 'AES/GCM'
|
69
|
+
if master_key.is_a? OpenSSL::PKey::RSA
|
70
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
71
|
+
' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
|
72
|
+
end
|
54
73
|
Utils.decrypt_aes_gcm(master_key,
|
55
74
|
decode64(envelope['x-amz-key-v2']),
|
56
75
|
envelope['x-amz-cek-alg'])
|
57
76
|
when 'RSA-OAEP-SHA1'
|
77
|
+
unless master_key.is_a? OpenSSL::PKey::RSA
|
78
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
79
|
+
' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
|
80
|
+
end
|
58
81
|
key, cek_alg = Utils.decrypt_rsa(master_key, decode64(envelope['x-amz-key-v2']))
|
59
|
-
raise Errors::
|
82
|
+
raise Errors::CEKAlgMismatchError unless cek_alg == envelope['x-amz-cek-alg']
|
60
83
|
key
|
84
|
+
when 'kms+context'
|
85
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
86
|
+
' with a user provided key and the x-amz-wrap-alg is' \
|
87
|
+
' kms+context. Please configure the client with the' \
|
88
|
+
' required kms_key_id'
|
61
89
|
else
|
62
90
|
raise ArgumentError, 'Unsupported wrap-alg: ' \
|
63
91
|
"#{envelope['x-amz-wrap-alg']}"
|
@@ -69,6 +97,39 @@ module Aws
|
|
69
97
|
|
70
98
|
private
|
71
99
|
|
100
|
+
# Validate that the key_wrap_schema
|
101
|
+
# is valid, supported and matches the provided key.
|
102
|
+
# Returns the string version for the x-amz-key-wrap-alg
|
103
|
+
def validate_key_wrap(key_wrap_schema, key)
|
104
|
+
if key.is_a? OpenSSL::PKey::RSA
|
105
|
+
unless key_wrap_schema == :rsa_oaep_sha1
|
106
|
+
raise ArgumentError, ':key_wrap_schema must be set to :rsa_oaep_sha1 for RSA keys.'
|
107
|
+
end
|
108
|
+
else
|
109
|
+
unless key_wrap_schema == :aes_gcm
|
110
|
+
raise ArgumentError, ':key_wrap_schema must be set to :aes_gcm for AES keys.'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
case key_wrap_schema
|
115
|
+
when :rsa_oaep_sha1 then 'RSA-OAEP-SHA1'
|
116
|
+
when :aes_gcm then 'AES/GCM'
|
117
|
+
when :kms_context
|
118
|
+
raise ArgumentError, 'A kms_key_id is required when using :kms_context.'
|
119
|
+
else
|
120
|
+
raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def validate_cek(content_encryption_schema)
|
125
|
+
case content_encryption_schema
|
126
|
+
when :aes_gcm_no_padding
|
127
|
+
"AES/GCM/NoPadding"
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Unsupported content_encryption_schema: #{content_encryption_schema}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
72
133
|
def envelope_key(cipher)
|
73
134
|
cipher.key = cipher.random_key
|
74
135
|
end
|
@@ -97,6 +158,12 @@ module Aws
|
|
97
158
|
Base64.decode64(str)
|
98
159
|
end
|
99
160
|
|
161
|
+
def validate_options(options)
|
162
|
+
if !options[:kms_encryption_context].nil?
|
163
|
+
raise ArgumentError, 'Cannot provide :kms_encryption_context ' \
|
164
|
+
'with non KMS client.'
|
165
|
+
end
|
166
|
+
end
|
100
167
|
end
|
101
168
|
end
|
102
169
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module Aws
|
@@ -45,7 +47,8 @@ module Aws
|
|
45
47
|
context.params[:metadata] ||= {}
|
46
48
|
context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
|
47
49
|
if context.params.delete(:content_md5)
|
48
|
-
raise ArgumentError, 'content_md5
|
50
|
+
raise ArgumentError, 'Setting content_md5 on client side '\
|
51
|
+
'encrypted objects is deprecated.'
|
49
52
|
end
|
50
53
|
context.http_response.on_headers do
|
51
54
|
context.params[:body].close
|
@@ -54,9 +57,9 @@ module Aws
|
|
54
57
|
|
55
58
|
def apply_cse_user_agent(context)
|
56
59
|
if context.config.user_agent_suffix.nil?
|
57
|
-
context.config.user_agent_suffix =
|
58
|
-
elsif !context.config.user_agent_suffix.include?
|
59
|
-
context.config.user_agent_suffix +=
|
60
|
+
context.config.user_agent_suffix = EC_USER_AGENT
|
61
|
+
elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
|
62
|
+
context.config.user_agent_suffix += " #{EC_USER_AGENT}"
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
@@ -1,12 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aws
|
2
4
|
module S3
|
3
5
|
module EncryptionV2
|
4
6
|
module Errors
|
5
7
|
|
8
|
+
# Generic DecryptionError
|
6
9
|
class DecryptionError < RuntimeError; end
|
7
10
|
|
8
11
|
class EncryptionError < RuntimeError; end
|
9
12
|
|
13
|
+
# Raised when attempting to decrypt a legacy (V1) encrypted object
|
14
|
+
# when using a security_profile that does not support it.
|
15
|
+
class LegacyDecryptionError < DecryptionError
|
16
|
+
def initialize(*args)
|
17
|
+
msg = 'The requested object is ' \
|
18
|
+
'encrypted with V1 encryption schemas that have been disabled ' \
|
19
|
+
'by client configuration security_profile = :v2. Retry with ' \
|
20
|
+
':v2_and_legacy or re-encrypt the object.'
|
21
|
+
super(msg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class CEKAlgMismatchError < DecryptionError
|
26
|
+
def initialize(*args)
|
27
|
+
msg = 'The content encryption algorithm used at encryption time ' \
|
28
|
+
'does not match the algorithm stored for decryption time. ' \
|
29
|
+
'The object may be altered or corrupted.'
|
30
|
+
super(msg)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
10
34
|
end
|
11
35
|
end
|
12
36
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module Aws
|
@@ -7,15 +9,21 @@ module Aws
|
|
7
9
|
class KmsCipherProvider
|
8
10
|
|
9
11
|
def initialize(options = {})
|
10
|
-
@kms_key_id = options[:kms_key_id]
|
12
|
+
@kms_key_id = validate_kms_key(options[:kms_key_id])
|
11
13
|
@kms_client = options[:kms_client]
|
14
|
+
@key_wrap_schema = validate_key_wrap(
|
15
|
+
options[:key_wrap_schema]
|
16
|
+
)
|
17
|
+
@content_encryption_schema = validate_cek(
|
18
|
+
options[:content_encryption_schema]
|
19
|
+
)
|
12
20
|
end
|
13
21
|
|
14
22
|
# @return [Array<Hash,Cipher>] Creates and returns a new encryption
|
15
23
|
# envelope and encryption cipher.
|
16
24
|
def encryption_cipher(options = {})
|
17
|
-
|
18
|
-
encryption_context = build_encryption_context(
|
25
|
+
validate_key_for_encryption
|
26
|
+
encryption_context = build_encryption_context(@content_encryption_schema, options)
|
19
27
|
key_data = @kms_client.generate_data_key(
|
20
28
|
key_id: @kms_key_id,
|
21
29
|
encryption_context: encryption_context,
|
@@ -26,9 +34,9 @@ module Aws
|
|
26
34
|
envelope = {
|
27
35
|
'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
|
28
36
|
'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
|
29
|
-
'x-amz-cek-alg' =>
|
30
|
-
'x-amz-tag-len' =>
|
31
|
-
'x-amz-wrap-alg' =>
|
37
|
+
'x-amz-cek-alg' => @content_encryption_schema,
|
38
|
+
'x-amz-tag-len' => (AES_GCM_TAG_LEN_BYTES * 8).to_s,
|
39
|
+
'x-amz-wrap-alg' => @key_wrap_schema,
|
32
40
|
'x-amz-matdesc' => Json.dump(encryption_context)
|
33
41
|
}
|
34
42
|
cipher.auth_data = '' # auth_data must be set after key and iv
|
@@ -37,27 +45,45 @@ module Aws
|
|
37
45
|
|
38
46
|
# @return [Cipher] Given an encryption envelope, returns a
|
39
47
|
# decryption cipher.
|
40
|
-
def decryption_cipher(envelope, options={})
|
48
|
+
def decryption_cipher(envelope, options = {})
|
41
49
|
encryption_context = Json.load(envelope['x-amz-matdesc'])
|
42
|
-
cek_alg = envelope['x-amz-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
50
|
+
cek_alg = envelope['x-amz-cek-alg']
|
51
|
+
|
52
|
+
case envelope['x-amz-wrap-alg']
|
53
|
+
when 'kms'
|
54
|
+
unless options[:security_profile] == :v2_and_legacy
|
55
|
+
raise Errors::LegacyDecryptionError
|
56
|
+
end
|
57
|
+
when 'kms+context'
|
58
|
+
if cek_alg != encryption_context['aws:x-amz-cek-alg']
|
59
|
+
raise Errors::CEKAlgMismatchError
|
60
|
+
end
|
48
61
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
62
|
+
if encryption_context != build_encryption_context(cek_alg, options)
|
63
|
+
raise Errors::DecryptionError, 'Value of encryption context from'\
|
64
|
+
' envelope does not match the provided encryption context'
|
65
|
+
end
|
66
|
+
when 'AES/GCM'
|
67
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
68
|
+
' with a KMS key and the x-amz-wrap-alg is AES/GCM.'
|
69
|
+
when 'RSA-OAEP-SHA1'
|
70
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
71
|
+
' with a KMS key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
|
72
|
+
else
|
73
|
+
raise ArgumentError, 'Unsupported wrap-alg: ' \
|
74
|
+
"#{envelope['x-amz-wrap-alg']}"
|
53
75
|
end
|
54
76
|
|
55
|
-
|
77
|
+
any_cmk_mode = false || options[:kms_allow_decrypt_with_any_cmk]
|
78
|
+
decrypt_options = {
|
56
79
|
ciphertext_blob: decode64(envelope['x-amz-key-v2']),
|
57
80
|
encryption_context: encryption_context
|
58
|
-
|
59
|
-
|
81
|
+
}
|
82
|
+
unless any_cmk_mode
|
83
|
+
decrypt_options[:key_id] = @kms_key_id
|
84
|
+
end
|
60
85
|
|
86
|
+
key = @kms_client.decrypt(decrypt_options).plaintext
|
61
87
|
iv = decode64(envelope['x-amz-iv'])
|
62
88
|
block_mode =
|
63
89
|
case cek_alg
|
@@ -77,9 +103,46 @@ module Aws
|
|
77
103
|
|
78
104
|
private
|
79
105
|
|
106
|
+
def validate_key_wrap(key_wrap_schema)
|
107
|
+
case key_wrap_schema
|
108
|
+
when :kms_context then 'kms+context'
|
109
|
+
else
|
110
|
+
raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_cek(content_encryption_schema)
|
115
|
+
case content_encryption_schema
|
116
|
+
when :aes_gcm_no_padding
|
117
|
+
"AES/GCM/NoPadding"
|
118
|
+
else
|
119
|
+
raise ArgumentError, "Unsupported content_encryption_schema: #{content_encryption_schema}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_kms_key(kms_key_id)
|
124
|
+
if kms_key_id.nil? || kms_key_id.length.zero?
|
125
|
+
raise ArgumentError, 'KMS CMK ID was not specified. ' \
|
126
|
+
'Please specify a CMK ID, ' \
|
127
|
+
'or set kms_key_id: :kms_allow_decrypt_with_any_cmk to use ' \
|
128
|
+
'any valid CMK from the object.'
|
129
|
+
end
|
130
|
+
|
131
|
+
if kms_key_id.is_a?(Symbol) && kms_key_id != :kms_allow_decrypt_with_any_cmk
|
132
|
+
raise ArgumentError, 'kms_key_id must be a valid KMS CMK or be ' \
|
133
|
+
'set to :kms_allow_decrypt_with_any_cmk'
|
134
|
+
end
|
135
|
+
kms_key_id
|
136
|
+
end
|
137
|
+
|
80
138
|
def build_encryption_context(cek_alg, options = {})
|
81
139
|
kms_context = (options[:kms_encryption_context] || {})
|
82
140
|
.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
|
141
|
+
if kms_context.include? 'aws:x-amz-cek-alg'
|
142
|
+
raise ArgumentError, 'Conflict in reserved KMS Encryption Context ' \
|
143
|
+
'key aws:x-amz-cek-alg. This value is reserved for the S3 ' \
|
144
|
+
'Encryption Client and cannot be set by the user.'
|
145
|
+
end
|
83
146
|
{
|
84
147
|
'aws:x-amz-cek-alg' => cek_alg
|
85
148
|
}.merge(kms_context)
|
@@ -93,6 +156,13 @@ module Aws
|
|
93
156
|
Base64.decode64(str)
|
94
157
|
end
|
95
158
|
|
159
|
+
def validate_key_for_encryption
|
160
|
+
if @kms_key_id == :kms_allow_decrypt_with_any_cmk
|
161
|
+
raise ArgumentError, 'Unable to encrypt/write objects with '\
|
162
|
+
'kms_key_id = :kms_allow_decrypt_with_any_cmk. Provide ' \
|
163
|
+
'a valid kms_key_id on client construction.'
|
164
|
+
end
|
165
|
+
end
|
96
166
|
end
|
97
167
|
end
|
98
168
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'openssl'
|
2
4
|
|
3
5
|
module Aws
|
@@ -6,24 +8,9 @@ module Aws
|
|
6
8
|
# @api private
|
7
9
|
module Utils
|
8
10
|
|
9
|
-
UNSAFE_MSG = "unsafe encryption, data is longer than key length"
|
10
|
-
|
11
11
|
class << self
|
12
12
|
|
13
|
-
def encrypt(key, data)
|
14
|
-
case key
|
15
|
-
when OpenSSL::PKey::RSA # asymmetric encryption
|
16
|
-
warn(UNSAFE_MSG) if key.public_key.n.num_bits < cipher_size(data)
|
17
|
-
key.public_encrypt(data)
|
18
|
-
when String # symmetric encryption
|
19
|
-
warn(UNSAFE_MSG) if cipher_size(key) < cipher_size(data)
|
20
|
-
cipher = aes_encryption_cipher(:ECB, key)
|
21
|
-
cipher.update(data) + cipher.final
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
13
|
def encrypt_aes_gcm(key, data, auth_data)
|
26
|
-
warn(UNSAFE_MSG) if cipher_size(key) < cipher_size(data)
|
27
14
|
cipher = aes_encryption_cipher(:GCM, key)
|
28
15
|
cipher.iv = (iv = cipher.random_iv)
|
29
16
|
cipher.auth_data = auth_data
|