class Aws::S3::Encryption::DecryptHandler

@api private

Constants

AUTH_REQUIRED_CEK_ALGS
POSSIBLE_ENCRYPTION_FORMATS
POSSIBLE_ENVELOPE_KEYS
POSSIBLE_WRAPPING_FORMATS
V1_ENVELOPE_KEYS
V2_ENVELOPE_KEYS
V2_OPTIONAL_KEYS

Public Instance Methods

call(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 46
def call(context)
  attach_http_event_listeners(context)
  apply_cse_user_agent(context)

  if context[:response_target].is_a?(Proc) && !@@warned_response_target_proc
    @@warned_response_target_proc = true
    warn(':response_target is a Proc, or a block was provided. ' \
      'Read the entire object to the ' \
      'end before you start using the decrypted data. This is to ' \
      'verify that the object has not been modified since it ' \
      'was encrypted.')
  end

  @handler.call(context)
end

Private Instance Methods

apply_cse_user_agent(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 210
def apply_cse_user_agent(context)
  if context.config.user_agent_suffix.nil?
    context.config.user_agent_suffix = EC_USER_AGENT
  elsif !context.config.user_agent_suffix.include? EC_USER_AGENT
    context.config.user_agent_suffix += " #{EC_USER_AGENT}"
  end
end
attach_http_event_listeners(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 64
def attach_http_event_listeners(context)

  context.http_response.on_headers(200) do
    cipher, envelope = decryption_cipher(context)
    decrypter = body_contains_auth_tag?(envelope) ?
      authenticated_decrypter(context, cipher, envelope) :
      IODecrypter.new(cipher, context.http_response.body)
    context.http_response.body = decrypter
  end

  context.http_response.on_success(200) do
    decrypter = context.http_response.body
    decrypter.finalize
    decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
    context.http_response.body = decrypter.io
  end

  context.http_response.on_error do
    if context.http_response.body.respond_to?(:io)
      context.http_response.body = context.http_response.body.io
    end
  end
end
auth_tag_length(envelope) click to toggle source

Determine the auth tag length from the algorithm Validate it against the value provided in the x-amz-tag-len Return the tag length in bytes

# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 196
def auth_tag_length(envelope)
  tag_length =
    case envelope['x-amz-cek-alg']
    when 'AES/GCM/NoPadding' then AES_GCM_TAG_LEN_BYTES
    else
      raise ArgumentError, 'Unsupported cek-alg: ' \
        "#{envelope['x-amz-cek-alg']}"
    end
  if (tag_length * 8) != envelope['x-amz-tag-len'].to_i
    raise Errors::DecryptionError, 'x-amz-tag-len does not match expected'
  end
  tag_length
end
authenticated_decrypter(context, cipher, envelope) click to toggle source

This method fetches the tag from the end of the object by making a GET Object w/range request. This auth tag is used to initialize the cipher, and the decrypter truncates the auth tag from the body when writing the final bytes.

# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 167
def authenticated_decrypter(context, cipher, envelope)
  http_resp = context.http_response
  content_length = http_resp.headers['content-length'].to_i
  auth_tag_length = auth_tag_length(envelope)

  auth_tag = context.client.get_object(
    bucket: context.params[:bucket],
    key: context.params[:key],
    range: "bytes=-#{auth_tag_length}"
  ).body.read

  cipher.auth_tag = auth_tag
  cipher.auth_data = ''

  # The encrypted object contains both the cipher text
  # plus a trailing auth tag.
  IOAuthDecrypter.new(
    io: http_resp.body,
    encrypted_content_length: content_length - auth_tag_length,
    cipher: cipher)
end
body_contains_auth_tag?(envelope) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 189
def body_contains_auth_tag?(envelope)
  AUTH_REQUIRED_CEK_ALGS.include?(envelope['x-amz-cek-alg'])
end
decryption_cipher(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 88
def decryption_cipher(context)
  if (envelope = get_encryption_envelope(context))
    cipher = context[:encryption][:cipher_provider]
             .decryption_cipher(
               envelope,
               context[:encryption]
             )
    [cipher, envelope]
  else
    raise Errors::DecryptionError, "unable to locate encryption envelope"
  end
end
envelope_from_instr_file(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 119
def envelope_from_instr_file(context)
  suffix = context[:encryption][:instruction_file_suffix]
  possible_envelope = Json.load(context.client.get_object(
    bucket: context.params[:bucket],
    key: context.params[:key] + suffix
  ).body.read)
  extract_envelope(possible_envelope)
rescue S3::Errors::ServiceError, Json::ParseError
  nil
end
envelope_from_metadata(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 109
def envelope_from_metadata(context)
  possible_envelope = {}
  POSSIBLE_ENVELOPE_KEYS.each do |suffix|
    if value = context.http_response.headers["x-amz-meta-#{suffix}"]
      possible_envelope[suffix] = value
    end
  end
  extract_envelope(possible_envelope)
end
extract_envelope(hash) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 130
def extract_envelope(hash)
  return nil unless hash
  return v1_envelope(hash) if hash.key?('x-amz-key')
  return v2_envelope(hash) if hash.key?('x-amz-key-v2')
  if hash.keys.any? { |key| key.match(/^x-amz-key-(.+)$/) }
    msg = "unsupported envelope encryption version #{$1}"
    raise Errors::DecryptionError, msg
  end
end
get_encryption_envelope(context) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 101
def get_encryption_envelope(context)
  if context[:encryption][:envelope_location] == :metadata
    envelope_from_metadata(context) || envelope_from_instr_file(context)
  else
    envelope_from_instr_file(context) || envelope_from_metadata(context)
  end
end
v1_envelope(envelope) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 140
def v1_envelope(envelope)
  envelope
end
v2_envelope(envelope) click to toggle source
# File lib/aws-sdk-s3/encryption/decrypt_handler.rb, line 144
def v2_envelope(envelope)
  unless POSSIBLE_ENCRYPTION_FORMATS.include? envelope['x-amz-cek-alg']
    alg = envelope['x-amz-cek-alg'].inspect
    msg = "unsupported content encrypting key (cek) format: #{alg}"
    raise Errors::DecryptionError, msg
  end
  unless POSSIBLE_WRAPPING_FORMATS.include? envelope['x-amz-wrap-alg']
    alg = envelope['x-amz-wrap-alg'].inspect
    msg = "unsupported key wrapping algorithm: #{alg}"
    raise Errors::DecryptionError, msg
  end
  unless (missing_keys = V2_ENVELOPE_KEYS - envelope.keys).empty?
    msg = "incomplete v2 encryption envelope:\n"
    msg += "  missing: #{missing_keys.join(',')}\n"
    raise Errors::DecryptionError, msg
  end
  envelope
end