aws-sdk-s3 1.74.0 → 1.79.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aws-sdk-s3.rb +2 -2
- data/lib/aws-sdk-s3/bucket.rb +2 -2
- data/lib/aws-sdk-s3/client.rb +148 -120
- 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 +3 -1
- 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/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
|