class Aws::Plugins::ChecksumAlgorithm::ChecksumHandler
def add_request_checksum_metrics(algorithm, metrics)
def add_request_checksum_metrics(algorithm, metrics) case algorithm when 'CRC32' metrics << 'FLEXIBLE_CHECKSUMS_REQ_CRC32' when 'CRC32C' metrics << 'FLEXIBLE_CHECKSUMS_REQ_CRC32C' when 'CRC64NVME' metrics << 'FLEXIBLE_CHECKSUMS_REQ_CRC64' when 'SHA1' metrics << 'FLEXIBLE_CHECKSUMS_REQ_SHA1' when 'SHA256' metrics << 'FLEXIBLE_CHECKSUMS_REQ_SHA256' end end
def add_request_config_metric(config, metrics)
def add_request_config_metric(config, metrics) case config.request_checksum_calculation when 'when_supported' metrics << 'FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED' when 'when_required' metrics << 'FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED' end end
def add_response_config_metric(config, metrics)
def add_response_config_metric(config, metrics) case config.response_checksum_validation when 'when_supported' metrics << 'FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED' when 'when_required' metrics << 'FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED' end end
def add_verify_response_checksum_handlers(context)
This prevents the body from being read multiple times
Add events to the http_response to verify the checksum as its read
def add_verify_response_checksum_handlers(context) checksum_context = {} add_verify_response_headers_handler(context, checksum_context) add_verify_response_data_handler(context, checksum_context) add_verify_response_success_handler(context, checksum_context) end
def add_verify_response_data_handler(context, checksum_context)
def add_verify_response_data_handler(context, checksum_context) context.http_response.on_data do |chunk| checksum_context[:digest]&.update(chunk) end end
def add_verify_response_headers_handler(context, checksum_context)
def add_verify_response_headers_handler(context, checksum_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| header_name, algorithm = response_header_to_verify( headers, validation_list ) next unless header_name expected = headers[header_name] next if context[:http_checksum][:skip_on_suffix] && /-\d+$/.match(expected) checksum_context[:algorithm] = algorithm checksum_context[:header_name] = header_name checksum_context[:digest] = ChecksumAlgorithm.digest_for_algorithm(algorithm) checksum_context[:expected] = expected end end
def add_verify_response_success_handler(context, checksum_context)
def add_verify_response_success_handler(context, checksum_context) context.http_response.on_success do next unless checksum_context[:digest] computed = checksum_context[:digest].base64digest if computed == checksum_context[:expected] context[:http_checksum][:validated] = checksum_context[:algorithm] else raise Aws::Errors::ChecksumError, "Checksum validation failed on #{checksum_context[:header_name]} "\ "computed: #{computed}, expected: #{checksum_context[:expected]}" end end end
def apply_request_checksum(context, headers, checksum_properties)
def apply_request_checksum(context, headers, checksum_properties) header_name = checksum_properties[:name] body = context.http_request.body_contents headers[header_name] = calculate_checksum( checksum_properties[:algorithm], body ) end
def apply_request_trailer_checksum(context, headers, checksum_properties)
def apply_request_trailer_checksum(context, headers, checksum_properties) location_name = checksum_properties[:name] # set required headers headers['Content-Encoding'] = 'aws-chunked' headers['X-Amz-Content-Sha256'] = 'STREAMING-UNSIGNED-PAYLOAD-TRAILER' headers['X-Amz-Trailer'] = location_name # We currently always compute the size in the modified body wrapper - allowing us # to set the Content-Length header (set by content_length plugin). # This means we cannot use Transfer-Encoding=chunked unless context.http_request.body.respond_to?(:size) raise Aws::Errors::ChecksumError, 'Could not determine length of the body' end headers['X-Amz-Decoded-Content-Length'] = context.http_request.body.size context.http_request.body = AwsChunkedTrailerDigestIO.new( context.http_request.body, checksum_properties[:algorithm], location_name ) end
def calculate_checksum(algorithm, body)
def calculate_checksum(algorithm, body) digest = ChecksumAlgorithm.digest_for_algorithm(algorithm) if body.respond_to?(:read) update_in_chunks(digest, body) else digest.update(body) end digest.base64digest end
def calculate_request_checksum(context, checksum_properties)
def calculate_request_checksum(context, checksum_properties) headers = context.http_request.headers if (algorithm_header = checksum_properties[:request_algorithm_header]) headers[algorithm_header] = checksum_properties[:algorithm] end case checksum_properties[:in] when 'header' apply_request_checksum(context, headers, checksum_properties) when 'trailer' apply_request_trailer_checksum(context, headers, checksum_properties) else # nothing end end
def call(context)
def call(context) algorithm = nil if should_calculate_request_checksum?(context) algorithm = choose_request_algorithm!(context) request_algorithm = { algorithm: algorithm, in: checksum_request_in(context), name: "x-amz-checksum-#{algorithm.downcase}", request_algorithm_header: request_algorithm_header(context) } context[:http_checksum][:request_algorithm] = request_algorithm calculate_request_checksum(context, request_algorithm) end if should_verify_response_checksum?(context) add_verify_response_checksum_handlers(context) end with_metrics(context.config, algorithm) { @handler.call(context) } end
def checksum_optional?(context)
def checksum_optional?(context) context.operation.http_checksum && context.config.request_checksum_calculation != 'when_required' end
def checksum_provided_as_header?(headers)
def checksum_provided_as_header?(headers) headers.any? { |k, _| k.start_with?('x-amz-checksum-') } end
def checksum_request_in(context)
def checksum_request_in(context) if context.operation['unsignedPayload'] || context.operation['authtype'] == 'v4-unsigned-body' 'trailer' else 'header' end end
def checksum_required?(context)
def checksum_required?(context) (http_checksum = context.operation.http_checksum) && (checksum_required = http_checksum['requestChecksumRequired']) && (checksum_required && context.config.request_checksum_calculation == 'when_required') end
def choose_request_algorithm!(context)
def choose_request_algorithm!(context) algorithm = request_algorithm_selection(context).upcase return algorithm if CLIENT_ALGORITHMS.include?(algorithm) if CRT_ALGORITHMS.include?(algorithm) raise ArgumentError, 'CRC32C and CRC64NVME requires CRT support ' \ '- install the aws-crt gem' end raise ArgumentError, "#{algorithm} is not a supported checksum algorithm." end
def operation_response_algorithms(context)
def operation_response_algorithms(context) return unless context.operation.http_checksum context.operation.http_checksum['responseAlgorithms'] end
def request_algorithm_header(context)
def request_algorithm_header(context) input_member = context.operation.http_checksum['requestAlgorithmMember'] shape = context.operation.input.shape.member(input_member) shape.location_name if shape && shape.location == 'header' end
def request_algorithm_selection(context)
def request_algorithm_selection(context) return unless context.operation.http_checksum input_member = context.operation.http_checksum['requestAlgorithmMember'] context.params[input_member.to_sym] ||= DEFAULT_CHECKSUM if input_member end
def request_validation_mode(context)
def request_validation_mode(context) return unless context.operation.http_checksum input_member = context.operation.http_checksum['requestValidationModeMember'] context.params[input_member.to_sym] if input_member end
def response_header_to_verify(headers, validation_list)
def response_header_to_verify(headers, validation_list) validation_list.each do |algorithm| header_name = "x-amz-checksum-#{algorithm.downcase}" return [header_name, algorithm] if headers[header_name] end nil end
def should_calculate_request_checksum?(context)
def should_calculate_request_checksum?(context) !checksum_provided_as_header?(context.http_request.headers) && request_algorithm_selection(context) && (checksum_required?(context) || checksum_optional?(context)) end
def should_verify_response_checksum?(context)
def should_verify_response_checksum?(context) request_validation_mode(context) == 'ENABLED' end
def update_in_chunks(digest, io)
def update_in_chunks(digest, io) loop do chunk = io.read(CHUNK_SIZE) break unless chunk digest.update(chunk) end io.rewind end
def with_metrics(config, algorithm, &block)
def with_metrics(config, algorithm, &block) metrics = [] add_request_config_metric(config, metrics) add_response_config_metric(config, metrics) add_request_checksum_metrics(algorithm, metrics) Aws::Plugins::UserAgent.metric(*metrics, &block) end