aws-sdk-s3 1.207.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 +5 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/client.rb +1 -1
- 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
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8d399e87c3b6a02f60b4fc2efbb7e656af4f8ef66302eec0454129bded2fdb0
|
|
4
|
+
data.tar.gz: 3d348a0ce7abfbc7bfd9210227df82f33d4576f321f9da5374b29f6834e62a38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 525a8c73faf271316eb39fad4880032687e38fc54f452b733c7f161af4e367bf142c73b7218185f5d335219940bce706a5f9884d0716a388a8289a1ea44150f4
|
|
7
|
+
data.tar.gz: ff3d94155efdef438f2db5f4e2a6126590924fb5b712e74981dd174f2e05a7bebcfb1c23e69f00d1aa84b0a1f20415670a630a93ef142472159ee6d8de20df3b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
Unreleased Changes
|
|
2
2
|
------------------
|
|
3
3
|
|
|
4
|
+
1.208.0 (2025-12-16)
|
|
5
|
+
------------------
|
|
6
|
+
|
|
7
|
+
* Feature - Updates to the S3 Encryption Client. The V3 S3 Encryption Client now requires key committing algorithm suites by default.
|
|
8
|
+
|
|
4
9
|
1.207.0 (2025-12-15)
|
|
5
10
|
------------------
|
|
6
11
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.208.0
|
data/lib/aws-sdk-s3/client.rb
CHANGED
|
@@ -22283,7 +22283,7 @@ module Aws::S3
|
|
|
22283
22283
|
tracer: tracer
|
|
22284
22284
|
)
|
|
22285
22285
|
context[:gem_name] = 'aws-sdk-s3'
|
|
22286
|
-
context[:gem_version] = '1.
|
|
22286
|
+
context[:gem_version] = '1.208.0'
|
|
22287
22287
|
Seahorse::Client::Request.new(handlers, context)
|
|
22288
22288
|
end
|
|
22289
22289
|
|
|
@@ -6,6 +6,7 @@ module Aws
|
|
|
6
6
|
autoload :BucketRegionCache, 'aws-sdk-s3/bucket_region_cache'
|
|
7
7
|
autoload :Encryption, 'aws-sdk-s3/encryption'
|
|
8
8
|
autoload :EncryptionV2, 'aws-sdk-s3/encryption_v2'
|
|
9
|
+
autoload :EncryptionV3, 'aws-sdk-s3/encryption_v3'
|
|
9
10
|
autoload :FilePart, 'aws-sdk-s3/file_part'
|
|
10
11
|
autoload :DefaultExecutor, 'aws-sdk-s3/default_executor'
|
|
11
12
|
autoload :FileUploader, 'aws-sdk-s3/file_uploader'
|
|
@@ -6,9 +6,9 @@ module Aws
|
|
|
6
6
|
module S3
|
|
7
7
|
|
|
8
8
|
# [MAINTENANCE MODE] There is a new version of the Encryption Client.
|
|
9
|
-
# AWS strongly recommends upgrading to the {Aws::S3::
|
|
9
|
+
# AWS strongly recommends upgrading to the {Aws::S3::EncryptionV3::Client},
|
|
10
10
|
# which provides updated data security best practices.
|
|
11
|
-
# See documentation for {Aws::S3::
|
|
11
|
+
# See documentation for {Aws::S3::EncryptionV3::Client}.
|
|
12
12
|
# Provides an encryption client that encrypts and decrypts data client-side,
|
|
13
13
|
# storing the encrypted data in Amazon S3.
|
|
14
14
|
#
|
|
@@ -16,6 +16,8 @@ module Aws
|
|
|
16
16
|
# envelope and encryption cipher.
|
|
17
17
|
def encryption_cipher
|
|
18
18
|
cipher = Utils.aes_encryption_cipher(:CBC)
|
|
19
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
|
|
20
|
+
##% Objects encrypted with ALG_AES_256_CBC_IV16_NO_KDF MAY use either the V1 or V2 message format version.
|
|
19
21
|
envelope = {
|
|
20
22
|
'x-amz-key' => encode64(encrypt(envelope_key(cipher))),
|
|
21
23
|
'x-amz-iv' => encode64(envelope_iv(cipher)),
|
|
@@ -38,6 +38,8 @@ module Aws
|
|
|
38
38
|
io = StringIO.new(io) if String === io
|
|
39
39
|
context.params[:body] = IOEncrypter.new(cipher, io)
|
|
40
40
|
context.params[:metadata] ||= {}
|
|
41
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#content-metadata-mapkeys
|
|
42
|
+
##% - The mapkey "x-amz-unencrypted-content-length" SHOULD be present for V1 format objects.
|
|
41
43
|
context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
|
|
42
44
|
if context.params.delete(:content_md5)
|
|
43
45
|
warn('Setting content_md5 on client side encrypted objects is deprecated')
|
|
@@ -26,6 +26,8 @@ module Aws
|
|
|
26
26
|
end
|
|
27
27
|
cipher = Utils.aes_encryption_cipher(:CBC)
|
|
28
28
|
cipher.key = key_data.plaintext
|
|
29
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
|
|
30
|
+
##% Objects encrypted with ALG_AES_256_CBC_IV16_NO_KDF MAY use either the V1 or V2 message format version.
|
|
29
31
|
envelope = {
|
|
30
32
|
'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
|
|
31
33
|
'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
|
|
@@ -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
|