aws-sdk-s3 1.205.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 +15 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/bucket.rb +1 -1
- data/lib/aws-sdk-s3/bucket_acl.rb +1 -1
- data/lib/aws-sdk-s3/client.rb +52 -182
- 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/object.rb +4 -4
- data/lib/aws-sdk-s3/object_acl.rb +1 -1
- data/lib/aws-sdk-s3/object_summary.rb +4 -4
- data/lib/aws-sdk-s3/types.rb +23 -102
- data/lib/aws-sdk-s3.rb +1 -1
- data/sig/bucket.rbs +1 -1
- data/sig/client.rbs +11 -11
- data/sig/multipart_upload.rbs +1 -1
- data/sig/object.rbs +5 -5
- data/sig/object_summary.rbs +5 -5
- data/sig/types.rbs +14 -14
- metadata +17 -1
|
@@ -5,9 +5,17 @@ require 'forwardable'
|
|
|
5
5
|
module Aws
|
|
6
6
|
module S3
|
|
7
7
|
|
|
8
|
-
REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile]
|
|
9
|
-
SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy]
|
|
8
|
+
REQUIRED_PARAMS = [:key_wrap_schema, :content_encryption_schema, :security_profile].freeze
|
|
9
|
+
SUPPORTED_SECURITY_PROFILES = [:v2, :v2_and_legacy].freeze
|
|
10
|
+
SUPPORTED_COMMITMENT_POLICIES = [:forbid_encrypt_allow_decrypt].freeze
|
|
10
11
|
|
|
12
|
+
# [MAINTENANCE MODE] There is a new version of the Encryption Client.
|
|
13
|
+
# AWS strongly recommends upgrading to the {Aws::S3::EncryptionV3::Client},
|
|
14
|
+
# which provides updated data security best practices.
|
|
15
|
+
# For migration guidance, see: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-encryption-migration-v2-v3.html
|
|
16
|
+
# Provides an encryption client that encrypts and decrypts data client-side,
|
|
17
|
+
# storing the encrypted data in Amazon S3.
|
|
18
|
+
#
|
|
11
19
|
# Provides an encryption client that encrypts and decrypts data client-side,
|
|
12
20
|
# storing the encrypted data in Amazon S3. The `EncryptionV2::Client` (V2 Client)
|
|
13
21
|
# provides improved security over the `Encryption::Client` (V1 Client)
|
|
@@ -307,15 +315,29 @@ module Aws
|
|
|
307
315
|
# @option options [KMS::Client] :kms_client A default {KMS::Client}
|
|
308
316
|
# is constructed when using KMS to manage encryption keys.
|
|
309
317
|
#
|
|
318
|
+
# @option options [Symbol] :commitment_policy (nil)
|
|
319
|
+
# Optional parameter for migration from V2 to V3. When set to
|
|
320
|
+
# :forbid_encrypt_allow_decrypt, this explicitly indicates you are
|
|
321
|
+
# maintaining V2 encryption behavior while preparing for migration.
|
|
322
|
+
# This allows the V2 client to decrypt V3-encrypted objects while
|
|
323
|
+
# continuing to encrypt new objects using V2 algorithms.
|
|
324
|
+
# Only :forbid_encrypt_allow_decrypt is supported.
|
|
325
|
+
# For migration guidance, see: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-encryption-migration-v2-v3.html
|
|
326
|
+
#
|
|
310
327
|
def initialize(options = {})
|
|
311
328
|
validate_params(options)
|
|
312
329
|
@client = extract_client(options)
|
|
313
|
-
@cipher_provider =
|
|
330
|
+
@cipher_provider = build_cipher_provider(options)
|
|
331
|
+
@key_provider = @cipher_provider.key_provider if @cipher_provider.is_a?(DefaultCipherProvider)
|
|
314
332
|
@envelope_location = extract_location(options)
|
|
315
333
|
@instruction_file_suffix = extract_suffix(options)
|
|
316
334
|
@kms_allow_decrypt_with_any_cmk =
|
|
317
335
|
options[:kms_key_id] == :kms_allow_decrypt_with_any_cmk
|
|
318
336
|
@security_profile = extract_security_profile(options)
|
|
337
|
+
@commitment_policy = extract_commitment_policy(options)
|
|
338
|
+
# The v3 cipher is only used for decrypt.
|
|
339
|
+
# Therefore any configured v2 `content_encryption_schema` is going to be incorrect.
|
|
340
|
+
@v3_cipher_provider = build_v3_cipher_provider_for_decrypt(options.reject { |k, _| k == :content_encryption_schema })
|
|
319
341
|
end
|
|
320
342
|
|
|
321
343
|
# @return [S3::Client]
|
|
@@ -341,6 +363,11 @@ module Aws
|
|
|
341
363
|
# by this string.
|
|
342
364
|
attr_reader :instruction_file_suffix
|
|
343
365
|
|
|
366
|
+
# @return [Symbol, nil] Optional commitment policy for V2 to V3 migration.
|
|
367
|
+
# When set to :forbid_encrypt_allow_decrypt, explicitly indicates
|
|
368
|
+
# maintaining V2 encryption behavior while preparing for migration.
|
|
369
|
+
attr_reader :commitment_policy
|
|
370
|
+
|
|
344
371
|
# Uploads an object to Amazon S3, encrypting data client-side.
|
|
345
372
|
# See {S3::Client#put_object} for documentation on accepted
|
|
346
373
|
# request parameters.
|
|
@@ -410,6 +437,7 @@ module Aws
|
|
|
410
437
|
req.handlers.add(DecryptHandler)
|
|
411
438
|
req.context[:encryption] = {
|
|
412
439
|
cipher_provider: @cipher_provider,
|
|
440
|
+
v3_cipher_provider: @v3_cipher_provider,
|
|
413
441
|
envelope_location: envelope_location,
|
|
414
442
|
instruction_file_suffix: instruction_file_suffix,
|
|
415
443
|
kms_encryption_context: kms_encryption_context,
|
|
@@ -423,6 +451,50 @@ module Aws
|
|
|
423
451
|
|
|
424
452
|
private
|
|
425
453
|
|
|
454
|
+
def build_cipher_provider(options)
|
|
455
|
+
if options[:kms_key_id]
|
|
456
|
+
KmsCipherProvider.new(
|
|
457
|
+
kms_key_id: options[:kms_key_id],
|
|
458
|
+
kms_client: kms_client(options),
|
|
459
|
+
key_wrap_schema: options[:key_wrap_schema],
|
|
460
|
+
content_encryption_schema: options[:content_encryption_schema]
|
|
461
|
+
)
|
|
462
|
+
else
|
|
463
|
+
key_provider = extract_key_provider(options)
|
|
464
|
+
DefaultCipherProvider.new(
|
|
465
|
+
key_provider: key_provider,
|
|
466
|
+
key_wrap_schema: options[:key_wrap_schema],
|
|
467
|
+
content_encryption_schema: options[:content_encryption_schema]
|
|
468
|
+
)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def build_v3_cipher_provider_for_decrypt(options)
|
|
473
|
+
if options[:kms_key_id]
|
|
474
|
+
Aws::S3::EncryptionV3::KmsCipherProvider.new(
|
|
475
|
+
kms_key_id: options[:kms_key_id],
|
|
476
|
+
kms_client: kms_client(options),
|
|
477
|
+
key_wrap_schema: options[:key_wrap_schema],
|
|
478
|
+
content_encryption_schema: options[:content_encryption_schema]
|
|
479
|
+
)
|
|
480
|
+
else
|
|
481
|
+
# Create V3 key provider explicitly for proper namespace consistency
|
|
482
|
+
key_provider = if options[:key_provider]
|
|
483
|
+
options[:key_provider]
|
|
484
|
+
elsif options[:encryption_key]
|
|
485
|
+
Aws::S3::EncryptionV3::DefaultKeyProvider.new(options)
|
|
486
|
+
else
|
|
487
|
+
msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
|
|
488
|
+
raise ArgumentError, msg
|
|
489
|
+
end
|
|
490
|
+
Aws::S3::EncryptionV3::DefaultCipherProvider.new(
|
|
491
|
+
key_provider: key_provider,
|
|
492
|
+
key_wrap_schema: options[:key_wrap_schema],
|
|
493
|
+
content_encryption_schema: options[:content_encryption_schema]
|
|
494
|
+
)
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
426
498
|
# Validate required parameters exist and don't conflict.
|
|
427
499
|
# The cek_alg and wrap_alg are passed on to the CipherProviders
|
|
428
500
|
# and further validated there
|
|
@@ -452,36 +524,19 @@ module Aws
|
|
|
452
524
|
options.delete(:encryption_key)
|
|
453
525
|
options.delete(:envelope_location)
|
|
454
526
|
options.delete(:instruction_file_suffix)
|
|
527
|
+
options.delete(:commitment_policy)
|
|
455
528
|
REQUIRED_PARAMS.each { |p| options.delete(p) }
|
|
456
529
|
S3::Client.new(options)
|
|
457
530
|
end
|
|
458
531
|
end
|
|
459
532
|
|
|
460
533
|
def kms_client(options)
|
|
461
|
-
options[:kms_client] ||
|
|
534
|
+
options[:kms_client] || (@kms_client ||=
|
|
462
535
|
KMS::Client.new(
|
|
463
536
|
region: @client.config.region,
|
|
464
537
|
credentials: @client.config.credentials,
|
|
465
538
|
)
|
|
466
|
-
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
def cipher_provider(options)
|
|
470
|
-
if options[:kms_key_id]
|
|
471
|
-
KmsCipherProvider.new(
|
|
472
|
-
kms_key_id: options[:kms_key_id],
|
|
473
|
-
kms_client: kms_client(options),
|
|
474
|
-
key_wrap_schema: options[:key_wrap_schema],
|
|
475
|
-
content_encryption_schema: options[:content_encryption_schema]
|
|
476
|
-
)
|
|
477
|
-
else
|
|
478
|
-
@key_provider = extract_key_provider(options)
|
|
479
|
-
DefaultCipherProvider.new(
|
|
480
|
-
key_provider: @key_provider,
|
|
481
|
-
key_wrap_schema: options[:key_wrap_schema],
|
|
482
|
-
content_encryption_schema: options[:content_encryption_schema]
|
|
483
|
-
)
|
|
484
|
-
end
|
|
539
|
+
)
|
|
485
540
|
end
|
|
486
541
|
|
|
487
542
|
def extract_key_provider(options)
|
|
@@ -564,7 +619,27 @@ module Aws
|
|
|
564
619
|
end
|
|
565
620
|
security_profile
|
|
566
621
|
end
|
|
622
|
+
|
|
623
|
+
def extract_commitment_policy(options)
|
|
624
|
+
validate_commitment_policy(options[:commitment_policy])
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def validate_commitment_policy(commitment_policy)
|
|
628
|
+
return nil if commitment_policy.nil?
|
|
629
|
+
|
|
630
|
+
unless SUPPORTED_COMMITMENT_POLICIES.include? commitment_policy
|
|
631
|
+
raise ArgumentError, "Unsupported commitment policy: :#{commitment_policy}. " \
|
|
632
|
+
"The V2 client only supports :forbid_encrypt_allow_decrypt for migration purposes. " \
|
|
633
|
+
"For migration guidance, see: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-encryption-migration-v2-v3.html"
|
|
634
|
+
end
|
|
635
|
+
commitment_policy
|
|
636
|
+
end
|
|
567
637
|
end
|
|
568
638
|
end
|
|
569
639
|
end
|
|
570
640
|
end
|
|
641
|
+
|
|
642
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#v1-v2-shared
|
|
643
|
+
##= type=exception
|
|
644
|
+
##= reason=This has never been supported in Ruby
|
|
645
|
+
##% This string MAY be encoded by the esoteric double-encoding scheme used by the S3 web server.
|
|
@@ -9,40 +9,6 @@ module Aws
|
|
|
9
9
|
class DecryptHandler < Seahorse::Client::Handler
|
|
10
10
|
@@warned_response_target_proc = false
|
|
11
11
|
|
|
12
|
-
V1_ENVELOPE_KEYS = %w(
|
|
13
|
-
x-amz-key
|
|
14
|
-
x-amz-iv
|
|
15
|
-
x-amz-matdesc
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
V2_ENVELOPE_KEYS = %w(
|
|
19
|
-
x-amz-key-v2
|
|
20
|
-
x-amz-iv
|
|
21
|
-
x-amz-cek-alg
|
|
22
|
-
x-amz-wrap-alg
|
|
23
|
-
x-amz-matdesc
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
V2_OPTIONAL_KEYS = %w(x-amz-tag-len)
|
|
27
|
-
|
|
28
|
-
POSSIBLE_ENVELOPE_KEYS = (V1_ENVELOPE_KEYS +
|
|
29
|
-
V2_ENVELOPE_KEYS + V2_OPTIONAL_KEYS).uniq
|
|
30
|
-
|
|
31
|
-
POSSIBLE_WRAPPING_FORMATS = %w(
|
|
32
|
-
AES/GCM
|
|
33
|
-
kms
|
|
34
|
-
kms+context
|
|
35
|
-
RSA-OAEP-SHA1
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
POSSIBLE_ENCRYPTION_FORMATS = %w(
|
|
39
|
-
AES/GCM/NoPadding
|
|
40
|
-
AES/CBC/PKCS5Padding
|
|
41
|
-
AES/CBC/PKCS7Padding
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
AUTH_REQUIRED_CEK_ALGS = %w(AES/GCM/NoPadding)
|
|
45
|
-
|
|
46
12
|
def call(context)
|
|
47
13
|
attach_http_event_listeners(context)
|
|
48
14
|
apply_cse_user_agent(context)
|
|
@@ -63,12 +29,14 @@ module Aws
|
|
|
63
29
|
private
|
|
64
30
|
|
|
65
31
|
def attach_http_event_listeners(context)
|
|
66
|
-
|
|
67
32
|
context.http_response.on_headers(200) do
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
33
|
+
decrypter = if Aws::S3::EncryptionV3::Decryption.v3?(context)
|
|
34
|
+
cipher, envelope = Aws::S3::EncryptionV3::Decryption.decryption_cipher(context)
|
|
35
|
+
Aws::S3::EncryptionV3::Decryption.get_decrypter(context, cipher, envelope)
|
|
36
|
+
else
|
|
37
|
+
cipher, envelope = Aws::S3::EncryptionV2::Decryption.decryption_cipher(context)
|
|
38
|
+
Aws::S3::EncryptionV2::Decryption.get_decrypter(context, cipher, envelope)
|
|
39
|
+
end
|
|
72
40
|
context.http_response.body = decrypter
|
|
73
41
|
end
|
|
74
42
|
|
|
@@ -86,129 +54,6 @@ module Aws
|
|
|
86
54
|
end
|
|
87
55
|
end
|
|
88
56
|
|
|
89
|
-
def decryption_cipher(context)
|
|
90
|
-
if (envelope = get_encryption_envelope(context))
|
|
91
|
-
cipher = context[:encryption][:cipher_provider]
|
|
92
|
-
.decryption_cipher(
|
|
93
|
-
envelope,
|
|
94
|
-
context[:encryption]
|
|
95
|
-
)
|
|
96
|
-
[cipher, envelope]
|
|
97
|
-
else
|
|
98
|
-
raise Errors::DecryptionError, "unable to locate encryption envelope"
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def get_encryption_envelope(context)
|
|
103
|
-
if context[:encryption][:envelope_location] == :metadata
|
|
104
|
-
envelope_from_metadata(context) || envelope_from_instr_file(context)
|
|
105
|
-
else
|
|
106
|
-
envelope_from_instr_file(context) || envelope_from_metadata(context)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def envelope_from_metadata(context)
|
|
111
|
-
possible_envelope = {}
|
|
112
|
-
POSSIBLE_ENVELOPE_KEYS.each do |suffix|
|
|
113
|
-
if value = context.http_response.headers["x-amz-meta-#{suffix}"]
|
|
114
|
-
possible_envelope[suffix] = value
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
extract_envelope(possible_envelope)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def envelope_from_instr_file(context)
|
|
121
|
-
suffix = context[:encryption][:instruction_file_suffix]
|
|
122
|
-
possible_envelope = Json.load(context.client.get_object(
|
|
123
|
-
bucket: context.params[:bucket],
|
|
124
|
-
key: context.params[:key] + suffix
|
|
125
|
-
).body.read)
|
|
126
|
-
extract_envelope(possible_envelope)
|
|
127
|
-
rescue S3::Errors::ServiceError, Json::ParseError
|
|
128
|
-
nil
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def extract_envelope(hash)
|
|
132
|
-
return nil unless hash
|
|
133
|
-
return v1_envelope(hash) if hash.key?('x-amz-key')
|
|
134
|
-
return v2_envelope(hash) if hash.key?('x-amz-key-v2')
|
|
135
|
-
if hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
|
|
136
|
-
msg = "unsupported envelope encryption version #{$1}"
|
|
137
|
-
raise Errors::DecryptionError, msg
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def v1_envelope(envelope)
|
|
142
|
-
envelope
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def v2_envelope(envelope)
|
|
146
|
-
unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
|
|
147
|
-
alg = envelope['x-amz-cek-alg'].inspect
|
|
148
|
-
msg = "unsupported content encrypting key (cek) format: #{alg}"
|
|
149
|
-
raise Errors::DecryptionError, msg
|
|
150
|
-
end
|
|
151
|
-
unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
|
|
152
|
-
alg = envelope['x-amz-wrap-alg'].inspect
|
|
153
|
-
msg = "unsupported key wrapping algorithm: #{alg}"
|
|
154
|
-
raise Errors::DecryptionError, msg
|
|
155
|
-
end
|
|
156
|
-
unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
|
|
157
|
-
msg = "incomplete v2 encryption envelope:\n"
|
|
158
|
-
msg += " missing: #{missing_keys.join(',')}\n"
|
|
159
|
-
raise Errors::DecryptionError, msg
|
|
160
|
-
end
|
|
161
|
-
envelope
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# This method fetches the tag from the end of the object by
|
|
165
|
-
# making a GET Object w/range request. This auth tag is used
|
|
166
|
-
# to initialize the cipher, and the decrypter truncates the
|
|
167
|
-
# auth tag from the body when writing the final bytes.
|
|
168
|
-
def authenticated_decrypter(context, cipher, envelope)
|
|
169
|
-
http_resp = context.http_response
|
|
170
|
-
content_length = http_resp.headers['content-length'].to_i
|
|
171
|
-
auth_tag_length = auth_tag_length(envelope)
|
|
172
|
-
|
|
173
|
-
auth_tag = context.client.get_object(
|
|
174
|
-
bucket: context.params[:bucket],
|
|
175
|
-
key: context.params[:key],
|
|
176
|
-
version_id: context.params[:version_id],
|
|
177
|
-
range: "bytes=-#{auth_tag_length}"
|
|
178
|
-
).body.read
|
|
179
|
-
|
|
180
|
-
cipher.auth_tag = auth_tag
|
|
181
|
-
cipher.auth_data = ''
|
|
182
|
-
|
|
183
|
-
# The encrypted object contains both the cipher text
|
|
184
|
-
# plus a trailing auth tag.
|
|
185
|
-
IOAuthDecrypter.new(
|
|
186
|
-
io: http_resp.body,
|
|
187
|
-
encrypted_content_length: content_length - auth_tag_length,
|
|
188
|
-
cipher: cipher)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def body_contains_auth_tag?(envelope)
|
|
192
|
-
AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Determine the auth tag length from the algorithm
|
|
196
|
-
# Validate it against the value provided in the x-amz-tag-len
|
|
197
|
-
# Return the tag length in bytes
|
|
198
|
-
def auth_tag_length(envelope)
|
|
199
|
-
tag_length =
|
|
200
|
-
case envelope['x-amz-cek-alg']
|
|
201
|
-
when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
|
|
202
|
-
else
|
|
203
|
-
raise ArgumentError, 'Unsupported cek-alg: ' \
|
|
204
|
-
"#{envelope['x-amz-cek-alg']}"
|
|
205
|
-
end
|
|
206
|
-
if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
|
|
207
|
-
raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
|
|
208
|
-
end
|
|
209
|
-
tag_length
|
|
210
|
-
end
|
|
211
|
-
|
|
212
57
|
def apply_cse_user_agent(context)
|
|
213
58
|
if context.config.user_agent_suffix.nil?
|
|
214
59
|
context.config.user_agent_suffix = EC_USER_AGENT
|
|
@@ -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
|