aws-sdk-s3 1.10.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 +5 -5
- data/CHANGELOG.md +1517 -0
- data/LICENSE.txt +202 -0
- data/VERSION +1 -0
- data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
- data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
- data/lib/aws-sdk-s3/bucket.rb +1062 -99
- data/lib/aws-sdk-s3/bucket_acl.rb +67 -17
- data/lib/aws-sdk-s3/bucket_cors.rb +80 -17
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +71 -19
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +126 -20
- data/lib/aws-sdk-s3/bucket_logging.rb +68 -18
- data/lib/aws-sdk-s3/bucket_notification.rb +56 -20
- data/lib/aws-sdk-s3/bucket_policy.rb +108 -17
- data/lib/aws-sdk-s3/bucket_region_cache.rb +11 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +60 -15
- data/lib/aws-sdk-s3/bucket_tagging.rb +71 -17
- data/lib/aws-sdk-s3/bucket_versioning.rb +166 -17
- data/lib/aws-sdk-s3/bucket_website.rb +78 -17
- data/lib/aws-sdk-s3/client.rb +20068 -3879
- data/lib/aws-sdk-s3/client_api.rb +1957 -209
- data/lib/aws-sdk-s3/customizations/bucket.rb +57 -38
- data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
- data/lib/aws-sdk-s3/customizations/object.rb +338 -68
- data/lib/aws-sdk-s3/customizations/object_summary.rb +17 -0
- data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
- data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
- data/lib/aws-sdk-s3/customizations.rb +30 -27
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +29 -8
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +45 -5
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +15 -2
- data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +11 -3
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +48 -11
- data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
- data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
- data/lib/aws-sdk-s3/encryption.rb +4 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +645 -0
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +68 -0
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +187 -0
- data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +67 -0
- data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
- data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +75 -0
- data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +181 -0
- data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +108 -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 +24 -0
- data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
- data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
- data/lib/aws-sdk-s3/endpoint_provider.rb +886 -0
- data/lib/aws-sdk-s3/endpoints.rb +1544 -0
- data/lib/aws-sdk-s3/errors.rb +181 -1
- data/lib/aws-sdk-s3/event_streams.rb +69 -0
- data/lib/aws-sdk-s3/express_credentials.rb +55 -0
- data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
- data/lib/aws-sdk-s3/file_downloader.rb +261 -82
- data/lib/aws-sdk-s3/file_part.rb +16 -13
- data/lib/aws-sdk-s3/file_uploader.rb +37 -22
- data/lib/aws-sdk-s3/legacy_signer.rb +19 -26
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +142 -80
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +191 -0
- data/lib/aws-sdk-s3/multipart_upload.rb +342 -31
- data/lib/aws-sdk-s3/multipart_upload_error.rb +5 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +387 -47
- data/lib/aws-sdk-s3/object.rb +2733 -204
- data/lib/aws-sdk-s3/object_acl.rb +112 -25
- data/lib/aws-sdk-s3/object_copier.rb +9 -5
- data/lib/aws-sdk-s3/object_multipart_copier.rb +50 -23
- data/lib/aws-sdk-s3/object_summary.rb +2265 -181
- data/lib/aws-sdk-s3/object_version.rb +542 -74
- data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +7 -43
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +20 -3
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
- data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +62 -17
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +14 -67
- data/lib/aws-sdk-s3/plugins/redirects.rb +5 -1
- data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +67 -93
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +137 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +4 -1
- data/lib/aws-sdk-s3/presigned_post.rb +160 -99
- data/lib/aws-sdk-s3/presigner.rb +178 -81
- data/lib/aws-sdk-s3/resource.rb +164 -15
- data/lib/aws-sdk-s3/transfer_manager.rb +303 -0
- data/lib/aws-sdk-s3/types.rb +15981 -4168
- data/lib/aws-sdk-s3/waiters.rb +67 -1
- data/lib/aws-sdk-s3.rb +46 -31
- data/sig/bucket.rbs +231 -0
- data/sig/bucket_acl.rbs +78 -0
- data/sig/bucket_cors.rbs +69 -0
- data/sig/bucket_lifecycle.rbs +88 -0
- data/sig/bucket_lifecycle_configuration.rbs +115 -0
- data/sig/bucket_logging.rbs +76 -0
- data/sig/bucket_notification.rbs +114 -0
- data/sig/bucket_policy.rbs +59 -0
- data/sig/bucket_request_payment.rbs +54 -0
- data/sig/bucket_tagging.rbs +65 -0
- data/sig/bucket_versioning.rbs +77 -0
- data/sig/bucket_website.rbs +93 -0
- data/sig/client.rbs +2612 -0
- data/sig/customizations/bucket.rbs +19 -0
- data/sig/customizations/object.rbs +38 -0
- data/sig/customizations/object_summary.rbs +35 -0
- data/sig/errors.rbs +44 -0
- data/sig/multipart_upload.rbs +120 -0
- data/sig/multipart_upload_part.rbs +109 -0
- data/sig/object.rbs +464 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +347 -0
- data/sig/object_version.rbs +143 -0
- data/sig/resource.rbs +141 -0
- data/sig/types.rbs +2899 -0
- data/sig/waiters.rbs +95 -0
- metadata +97 -14
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
module EncryptionV2
|
|
8
|
+
# @api private
|
|
9
|
+
class DecryptHandler < Seahorse::Client::Handler
|
|
10
|
+
@@warned_response_target_proc = false
|
|
11
|
+
|
|
12
|
+
def call(context)
|
|
13
|
+
attach_http_event_listeners(context)
|
|
14
|
+
apply_cse_user_agent(context)
|
|
15
|
+
|
|
16
|
+
if context[:response_target].is_a?(Proc) && !@@warned_response_target_proc
|
|
17
|
+
@@warned_response_target_proc = true
|
|
18
|
+
warn(':response_target is a Proc, or a block was provided. ' \
|
|
19
|
+
'Read the entire object to the ' \
|
|
20
|
+
'end before you start using the decrypted data. This is to ' \
|
|
21
|
+
'verify that the object has not been modified since it ' \
|
|
22
|
+
'was encrypted.')
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@handler.call(context)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def attach_http_event_listeners(context)
|
|
32
|
+
context.http_response.on_headers(200) do
|
|
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
|
|
40
|
+
context.http_response.body = decrypter
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context.http_response.on_success(200) do
|
|
44
|
+
decrypter = context.http_response.body
|
|
45
|
+
decrypter.finalize
|
|
46
|
+
decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
|
|
47
|
+
context.http_response.body = decrypter.io
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context.http_response.on_error do
|
|
51
|
+
if context.http_response.body.respond_to?(:io)
|
|
52
|
+
context.http_response.body = context.http_response.body.io
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def apply_cse_user_agent(context)
|
|
58
|
+
if context.config.user_agent_suffix.nil?
|
|
59
|
+
context.config.user_agent_suffix = EC_USER_AGENT
|
|
60
|
+
elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
|
|
61
|
+
context.config.user_agent_suffix += " #{EC_USER_AGENT}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
module EncryptionV2
|
|
8
|
+
# @api private
|
|
9
|
+
class DefaultCipherProvider
|
|
10
|
+
|
|
11
|
+
def initialize(options = {})
|
|
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
|
+
##= ../specification/s3-encryption/encryption.md#content-encryption
|
|
18
|
+
##% The S3EC MUST use the encryption algorithm configured during [client](./client.md) initialization.
|
|
19
|
+
@content_encryption_schema = validate_cek(
|
|
20
|
+
options[:content_encryption_schema]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :key_provider
|
|
25
|
+
|
|
26
|
+
# @return [Array<Hash,Cipher>] Creates an returns a new encryption
|
|
27
|
+
# envelope and encryption cipher.
|
|
28
|
+
def encryption_cipher(options = {})
|
|
29
|
+
validate_options(options)
|
|
30
|
+
cipher = Utils.aes_encryption_cipher(:GCM)
|
|
31
|
+
if @key_provider.encryption_materials.key.is_a? OpenSSL::PKey::RSA
|
|
32
|
+
enc_key = encode64(
|
|
33
|
+
encrypt_rsa(envelope_key(cipher), @content_encryption_schema)
|
|
34
|
+
)
|
|
35
|
+
else
|
|
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.
|
|
39
|
+
encrypt_aes_gcm(envelope_key(cipher), @content_encryption_schema)
|
|
40
|
+
)
|
|
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.
|
|
45
|
+
envelope = {
|
|
46
|
+
'x-amz-key-v2' => enc_key,
|
|
47
|
+
'x-amz-cek-alg' => @content_encryption_schema,
|
|
48
|
+
'x-amz-tag-len' => (AES_GCM_TAG_LEN_BYTES * 8).to_s,
|
|
49
|
+
'x-amz-wrap-alg' => @key_wrap_schema,
|
|
50
|
+
'x-amz-iv' => encode64(envelope_iv(cipher)),
|
|
51
|
+
'x-amz-matdesc' => materials_description
|
|
52
|
+
}
|
|
53
|
+
cipher.auth_data = '' # auth_data must be set after key and iv
|
|
54
|
+
[envelope, cipher]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Cipher] Given an encryption envelope, returns a
|
|
58
|
+
# decryption cipher.
|
|
59
|
+
def decryption_cipher(envelope, options = {})
|
|
60
|
+
validate_options(options)
|
|
61
|
+
master_key = @key_provider.key_for(envelope['x-amz-matdesc'])
|
|
62
|
+
if envelope.key? 'x-amz-key'
|
|
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.
|
|
68
|
+
raise Errors::LegacyDecryptionError
|
|
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).
|
|
74
|
+
# Support for decryption of legacy objects
|
|
75
|
+
key = Utils.decrypt(master_key, decode64(envelope['x-amz-key']))
|
|
76
|
+
iv = decode64(envelope['x-amz-iv'])
|
|
77
|
+
Utils.aes_decryption_cipher(:CBC, key, iv)
|
|
78
|
+
else
|
|
79
|
+
if envelope['x-amz-cek-alg'] != 'AES/GCM/NoPadding'
|
|
80
|
+
raise ArgumentError, 'Unsupported cek-alg: ' \
|
|
81
|
+
"#{envelope['x-amz-cek-alg']}"
|
|
82
|
+
end
|
|
83
|
+
key =
|
|
84
|
+
case envelope['x-amz-wrap-alg']
|
|
85
|
+
when 'AES/GCM'
|
|
86
|
+
if master_key.is_a? OpenSSL::PKey::RSA
|
|
87
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
|
88
|
+
' with an RSA key and the x-amz-wrap-alg is AES/GCM.'
|
|
89
|
+
end
|
|
90
|
+
Utils.decrypt_aes_gcm(master_key,
|
|
91
|
+
decode64(envelope['x-amz-key-v2']),
|
|
92
|
+
envelope['x-amz-cek-alg'])
|
|
93
|
+
when 'RSA-OAEP-SHA1'
|
|
94
|
+
unless master_key.is_a? OpenSSL::PKey::RSA
|
|
95
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
|
96
|
+
' with an AES key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
|
|
97
|
+
end
|
|
98
|
+
key, cek_alg = Utils.decrypt_rsa(master_key, decode64(envelope['x-amz-key-v2']))
|
|
99
|
+
raise Errors::CEKAlgMismatchError unless cek_alg == envelope['x-amz-cek-alg']
|
|
100
|
+
key
|
|
101
|
+
when 'kms+context'
|
|
102
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
|
103
|
+
' with a user provided key and the x-amz-wrap-alg is' \
|
|
104
|
+
' kms+context. Please configure the client with the' \
|
|
105
|
+
' required kms_key_id'
|
|
106
|
+
else
|
|
107
|
+
raise ArgumentError, 'Unsupported wrap-alg: ' \
|
|
108
|
+
"#{envelope['x-amz-wrap-alg']}"
|
|
109
|
+
end
|
|
110
|
+
iv = decode64(envelope['x-amz-iv'])
|
|
111
|
+
Utils.aes_decryption_cipher(:GCM, key, iv)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# Validate that the key_wrap_schema
|
|
118
|
+
# is valid, supported and matches the provided key.
|
|
119
|
+
# Returns the string version for the x-amz-key-wrap-alg
|
|
120
|
+
def validate_key_wrap(key_wrap_schema, key)
|
|
121
|
+
if key.is_a? OpenSSL::PKey::RSA
|
|
122
|
+
unless key_wrap_schema == :rsa_oaep_sha1
|
|
123
|
+
raise ArgumentError, ':key_wrap_schema must be set to :rsa_oaep_sha1 for RSA keys.'
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
unless key_wrap_schema == :aes_gcm
|
|
127
|
+
raise ArgumentError, ':key_wrap_schema must be set to :aes_gcm for AES keys.'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
case key_wrap_schema
|
|
132
|
+
when :rsa_oaep_sha1 then 'RSA-OAEP-SHA1'
|
|
133
|
+
when :aes_gcm then 'AES/GCM'
|
|
134
|
+
when :kms_context
|
|
135
|
+
raise ArgumentError, 'A kms_key_id is required when using :kms_context.'
|
|
136
|
+
else
|
|
137
|
+
raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def validate_cek(content_encryption_schema)
|
|
142
|
+
case content_encryption_schema
|
|
143
|
+
when :aes_gcm_no_padding
|
|
144
|
+
"AES/GCM/NoPadding"
|
|
145
|
+
else
|
|
146
|
+
raise ArgumentError, "Unsupported content_encryption_schema: #{content_encryption_schema}"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def envelope_key(cipher)
|
|
151
|
+
cipher.key = cipher.random_key
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def envelope_iv(cipher)
|
|
155
|
+
cipher.iv = cipher.random_iv
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def encrypt_aes_gcm(data, auth_data)
|
|
159
|
+
Utils.encrypt_aes_gcm(@key_provider.encryption_materials.key, data, auth_data)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def encrypt_rsa(data, auth_data)
|
|
163
|
+
Utils.encrypt_rsa(@key_provider.encryption_materials.key, data, auth_data)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def materials_description
|
|
167
|
+
@key_provider.encryption_materials.description
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def encode64(str)
|
|
171
|
+
Base64.encode64(str).split("\n") * ''
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def decode64(str)
|
|
175
|
+
Base64.decode64(str)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def validate_options(options)
|
|
179
|
+
if !options[:kms_encryption_context].nil?
|
|
180
|
+
raise ArgumentError, 'Cannot provide :kms_encryption_context ' \
|
|
181
|
+
'with non KMS client.'
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV2
|
|
6
|
+
|
|
7
|
+
# The default key provider is constructed with a single key
|
|
8
|
+
# that is used for both encryption and decryption, ignoring
|
|
9
|
+
# the possible per-object envelope encryption materials description.
|
|
10
|
+
# @api private
|
|
11
|
+
class DefaultKeyProvider
|
|
12
|
+
|
|
13
|
+
include KeyProvider
|
|
14
|
+
|
|
15
|
+
# @option options [required, OpenSSL::PKey::RSA, String] :encryption_key
|
|
16
|
+
# The master key to use for encrypting objects.
|
|
17
|
+
# @option options [String<JSON>] :materials_description ('{}')
|
|
18
|
+
# A description of the encryption key.
|
|
19
|
+
def initialize(options = {})
|
|
20
|
+
@encryption_materials = Materials.new(
|
|
21
|
+
key: options[:encryption_key],
|
|
22
|
+
description: options[:materials_description] || '{}'
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Materials]
|
|
27
|
+
def encryption_materials
|
|
28
|
+
@encryption_materials
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param [String<JSON>] materials_description
|
|
32
|
+
# @return Returns the key given in the constructor.
|
|
33
|
+
def key_for(materials_description)
|
|
34
|
+
@encryption_materials.key
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
module EncryptionV2
|
|
8
|
+
# @api private
|
|
9
|
+
class EncryptHandler < Seahorse::Client::Handler
|
|
10
|
+
|
|
11
|
+
def call(context)
|
|
12
|
+
envelope, cipher = context[:encryption][:cipher_provider]
|
|
13
|
+
.encryption_cipher(
|
|
14
|
+
kms_encryption_context: context[:encryption][:kms_encryption_context]
|
|
15
|
+
)
|
|
16
|
+
context[:encryption][:cipher] = cipher
|
|
17
|
+
apply_encryption_envelope(context, envelope)
|
|
18
|
+
apply_encryption_cipher(context, cipher)
|
|
19
|
+
apply_cse_user_agent(context)
|
|
20
|
+
@handler.call(context)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def apply_encryption_envelope(context, envelope)
|
|
26
|
+
if context[:encryption][:envelope_location] == :instruction_file
|
|
27
|
+
suffix = context[:encryption][:instruction_file_suffix]
|
|
28
|
+
context.client.put_object(
|
|
29
|
+
bucket: context.params[:bucket],
|
|
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.
|
|
33
|
+
body: Json.dump(envelope)
|
|
34
|
+
)
|
|
35
|
+
else # :metadata
|
|
36
|
+
context.params[:metadata] ||= {}
|
|
37
|
+
context.params[:metadata].update(envelope)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def apply_encryption_cipher(context, cipher)
|
|
42
|
+
io = context.params[:body] || ''
|
|
43
|
+
io = StringIO.new(io) if io.is_a? String
|
|
44
|
+
context.params[:body] = IOEncrypter.new(cipher, io)
|
|
45
|
+
context.params[:metadata] ||= {}
|
|
46
|
+
context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
|
|
47
|
+
if context.params.delete(:content_md5)
|
|
48
|
+
raise ArgumentError, 'Setting content_md5 on client side '\
|
|
49
|
+
'encrypted objects is deprecated.'
|
|
50
|
+
end
|
|
51
|
+
context.http_response.on_headers do
|
|
52
|
+
context.params[:body].close
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def apply_cse_user_agent(context)
|
|
57
|
+
if context.config.user_agent_suffix.nil?
|
|
58
|
+
context.config.user_agent_suffix = EC_USER_AGENT
|
|
59
|
+
elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
|
|
60
|
+
context.config.user_agent_suffix += " #{EC_USER_AGENT}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV2
|
|
6
|
+
module Errors
|
|
7
|
+
|
|
8
|
+
# Generic DecryptionError
|
|
9
|
+
class DecryptionError < RuntimeError; end
|
|
10
|
+
|
|
11
|
+
class EncryptionError < RuntimeError; end
|
|
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
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV2
|
|
6
|
+
# @api private
|
|
7
|
+
class IOAuthDecrypter
|
|
8
|
+
|
|
9
|
+
# @option options [required, IO#write] :io
|
|
10
|
+
# An IO-like object that responds to {#write}.
|
|
11
|
+
# @option options [required, Integer] :encrypted_content_length
|
|
12
|
+
# The number of bytes to decrypt from the `:io` object.
|
|
13
|
+
# This should be the total size of `:io` minus the length of
|
|
14
|
+
# the cipher auth tag.
|
|
15
|
+
# @option options [required, OpenSSL::Cipher] :cipher An initialized
|
|
16
|
+
# cipher that can be used to decrypt the bytes as they are
|
|
17
|
+
# written to the `:io` object. The cipher should already have
|
|
18
|
+
# its `#auth_tag` set.
|
|
19
|
+
def initialize(options = {})
|
|
20
|
+
@decrypter = IODecrypter.new(options[:cipher], options[:io])
|
|
21
|
+
@max_bytes = options[:encrypted_content_length]
|
|
22
|
+
@bytes_written = 0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write(chunk)
|
|
26
|
+
chunk = truncate_chunk(chunk)
|
|
27
|
+
if chunk.bytesize > 0
|
|
28
|
+
@bytes_written += chunk.bytesize
|
|
29
|
+
@decrypter.write(chunk)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def finalize
|
|
34
|
+
@decrypter.finalize
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def io
|
|
38
|
+
@decrypter.io
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def truncate_chunk(chunk)
|
|
44
|
+
if chunk.bytesize + @bytes_written <= @max_bytes
|
|
45
|
+
chunk
|
|
46
|
+
elsif @bytes_written < @max_bytes
|
|
47
|
+
chunk[0..(@max_bytes - @bytes_written - 1)]
|
|
48
|
+
else
|
|
49
|
+
# If the tag was sent over after the full body has been read,
|
|
50
|
+
# we don't want to accidentally append it.
|
|
51
|
+
""
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|