Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build_tools/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ class ServiceEnumerator
MANIFEST_PATH = File.expand_path('../../services.json', __FILE__)

# Minimum `aws-sdk-core` version for new gem builds
MINIMUM_CORE_VERSION = "3.241.0"
MINIMUM_CORE_VERSION = "3.241.3"

# Minimum `aws-sdk-core` version for new S3 gem builds
MINIMUM_CORE_VERSION_S3 = "3.241.0"
MINIMUM_CORE_VERSION_S3 = "3.241.3"

EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration"

Expand Down
2 changes: 2 additions & 0 deletions gems/aws-sdk-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Issue - Disable request trailer checksums when using non-HTTPs endpoints.

3.241.2 (2026-01-07)
------------------

Expand Down
39 changes: 23 additions & 16 deletions gems/aws-sdk-core/lib/aws-sdk-core/plugins/checksum_algorithm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ def checksum_provided_as_header?(headers)
# 1. **No existing checksum in header**: Skips if checksum header already present
# 2. **Operation support**: Considers model, client configuration and user input.
def should_calculate_request_checksum?(context)
!checksum_provided_as_header?(context.http_request.headers) &&
checksum_applicable?(context)
!checksum_provided_as_header?(context.http_request.headers) && checksum_applicable?(context)
end

# Checks if checksum calculation should proceed based on operation requirements and client settings.
Expand Down Expand Up @@ -317,12 +316,13 @@ def choose_request_algorithm!(context)
end

def checksum_request_in(context)
if context.operation['unsignedPayload'] ||
context.operation['authtype'] == 'v4-unsigned-body'
'trailer'
else
'header'
end
return 'header' unless supports_trailer_checksums?(context.operation)

should_fallback_to_header?(context) ? 'header' : 'trailer'
end

def supports_trailer_checksums?(operation)
operation['unsignedPayload'] || operation['authtype'] == 'v4-unsigned-body'
end

def calculate_request_checksum(context, checksum_properties)
Expand All @@ -331,12 +331,6 @@ def calculate_request_checksum(context, checksum_properties)
headers[algorithm_header] = checksum_properties[:algorithm]
end

# Trailer implementation within Mac/JRUBY environment is facing some
# network issues that will need further investigation:
# * https://github.com/jruby/jruby-openssl/issues/271
# * https://github.com/jruby/jruby-openssl/issues/317
return apply_request_checksum(context, headers, checksum_properties) if defined?(JRUBY_VERSION)

case checksum_properties[:in]
when 'header'
apply_request_checksum(context, headers, checksum_properties)
Expand All @@ -347,6 +341,20 @@ def calculate_request_checksum(context, checksum_properties)
end
end

def should_fallback_to_header?(context)
# Trailer implementation within Mac/JRUBY environment is facing some
# network issues that will need further investigation:
# * https://github.com/jruby/jruby-openssl/issues/271
# * https://github.com/jruby/jruby-openssl/issues/317
return true if defined?(JRUBY_VERSION)

# Chunked signing is currently not supported
# Https is required for unsigned payload for security
return true if context.http_request.endpoint.scheme == 'http'

context[:skip_trailer_checksums]
end

def apply_request_checksum(context, headers, checksum_properties)
header_name = checksum_properties[:name]
headers[header_name] = calculate_checksum(
Expand Down Expand Up @@ -422,8 +430,7 @@ def add_verify_response_checksum_handlers(context)
end

def add_verify_response_headers_handler(context, checksum_context)
validation_list = CHECKSUM_ALGORITHM_PRIORITIES &
operation_response_algorithms(context)
validation_list = CHECKSUM_ALGORITHM_PRIORITIES & operation_response_algorithms(context)
context[:http_checksum][:validation_list] = validation_list

context.http_response.on_headers do |_status, headers|
Expand Down
2 changes: 2 additions & 0 deletions gems/aws-sdk-s3/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Issue - Falls back to header request checksums when using custom endpoints or endpoint providers for PutObject and UploadPart operations.

1.210.1 (2026-01-06)
------------------

Expand Down
23 changes: 18 additions & 5 deletions gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/checksum_algorithm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,25 @@ def call(context)
end
end

# Handler to disable trailer checksums for S3-compatible services
# that don't support STREAMING-UNSIGNED-PAYLOAD-TRAILER
# See: https://github.com/aws/aws-sdk-ruby/issues/3338
class SkipTrailerChecksumsHandler < Seahorse::Client::Handler
def call(context)
context[:skip_trailer_checksums] = true if custom_endpoint?(context.config)
@handler.call(context)
end

private

def custom_endpoint?(config)
!config.regional_endpoint || !config.endpoint_provider.instance_of?(Aws::S3::EndpointProvider)
end
end

def add_handlers(handlers, _config)
handlers.add(
SkipWholeMultipartGetChecksumsHandler,
step: :initialize,
operations: [:get_object]
)
handlers.add(SkipWholeMultipartGetChecksumsHandler, step: :initialize, operations: [:get_object])
handlers.add(SkipTrailerChecksumsHandler, step: :build, priority: 16, operations: %i[put_object upload_part])
end
end
end
Expand Down
27 changes: 26 additions & 1 deletion gems/aws-sdk-s3/spec/plugins/checksum_algorithm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Plugins
let(:body) { 'hello world' }
let(:digest) { 'DUoRhQ==' }

let(:body_part_1) { 'hello '}
let(:body_part_1) { 'hello ' }
let(:digest_part_1) { '7YH59g==' }

it 'validates the checksum on an Object GET' do
Expand Down Expand Up @@ -112,6 +112,31 @@ module Plugins
end

context 'request trailer checksum', skip: defined?(JRUBY_VERSION) do
it 'falls back to header checksums when endpoint scheme is http' do
client = Aws::S3::Client.new(stub_responses: true, endpoint: 'http://example.com')

resp = client.put_object(bucket: bucket, key: key, body: body, content_encoding: 'gzip')
expect(resp.context.http_request.headers['X-Amz-Content-Sha256']).not_to eq('STREAMING-UNSIGNED-PAYLOAD-TRAILER')
expect(resp.context.http_request.headers['Content-Encoding']).not_to include('aws-chunked')
end

it 'falls back to header checksums for custom endpoint URLs' do
client = Aws::S3::Client.new(stub_responses: true, endpoint: 'https://example.com')

resp = client.put_object(bucket: bucket, key: key, body: body, content_encoding: 'gzip')
expect(resp.context.http_request.headers['X-Amz-Content-Sha256']).not_to eq('STREAMING-UNSIGNED-PAYLOAD-TRAILER')
expect(resp.context.http_request.headers['Content-Encoding']).not_to include('aws-chunked')
end

it 'falls back to header checksums for custom endpoint providers' do
custom_endpoint_provider = Class.new(Aws::S3::EndpointProvider).new
client = Aws::S3::Client.new(stub_responses: true, endpoint_provider: custom_endpoint_provider)

resp = client.put_object(bucket: bucket, key: key, body: body, content_encoding: 'gzip')
expect(resp.context.http_request.headers['X-Amz-Content-Sha256']).not_to eq('STREAMING-UNSIGNED-PAYLOAD-TRAILER')
expect(resp.context.http_request.headers['Content-Encoding']).not_to include('aws-chunked')
end

it 'sets aws-chunked when no existing Content-Encoding header' do
resp = client.put_object(bucket: bucket, key: key, body: body)
expect(resp.context.http_request.headers['Content-Encoding']).to eq('aws-chunked')
Expand Down