class Miasma::Contrib::AwsApiCore::SignatureV4

AWS signature version 4

Attributes

access_key[R]

@return [String] access key

hmac[R]

@return [Hmac]

region[R]

@return [String] region

service[R]

@return [String] service

Public Class Methods

new(access_key, secret_key, region, service) click to toggle source

Create new signature generator

@param access_key [String] @param secret_key [String] @param region [String] @param service [String] @return [self]

# File lib/miasma/contrib/aws.rb, line 178
def initialize(access_key, secret_key, region, service)
  @hmac = Hmac.new("sha256", secret_key)
  @access_key = access_key
  @region = region
  @service = service
end

Public Instance Methods

algorithm() click to toggle source

@return [String] signature algorithm

# File lib/miasma/contrib/aws.rb, line 263
def algorithm
  "AWS4-HMAC-SHA256"
end
build_canonical_request(http_method, path, opts) click to toggle source

Build the canonical request string used for signing

@param http_method [Symbol] HTTP request method @param path [String] request path @param opts [Hash] request options @return [String] canonical request string

# File lib/miasma/contrib/aws.rb, line 291
def build_canonical_request(http_method, path, opts)
  unless path.start_with?("/")
    path = "/#{path}"
  end
  [
    http_method.to_s.upcase,
    path,
    canonical_query(opts[:params]),
    canonical_headers(opts[:headers]),
    signed_headers(opts[:headers]),
    canonical_payload(opts),
  ].join("\n")
end
canonical_headers(headers) click to toggle source

Build the canonical header string used for signing

@param headers [Hash] request headers @return [String] canonical headers string

# File lib/miasma/contrib/aws.rb, line 321
def canonical_headers(headers)
  headers ||= {}
  headers = Hash[headers.sort_by(&:first)]
  headers.map do |key, value|
    [key.downcase, value.chomp].join(":")
  end.join("\n") << "\n"
end
canonical_payload(options) click to toggle source

Build the canonical payload string used for signing

@param options [Hash] request options @return [String] body checksum

# File lib/miasma/contrib/aws.rb, line 343
def canonical_payload(options)
  body = options.fetch(:body, "")
  if options[:json]
    body = MultiJson.dump(options[:json])
  elsif options[:form]
    body = URI.encode_www_form(options[:form])
  end
  if body == "UNSIGNED-PAYLOAD"
    body
  else
    hmac.hexdigest_of(body)
  end
end
canonical_query(params) click to toggle source

Build the canonical query string used for signing

@param params [Hash] query params @return [String] canonical query string

# File lib/miasma/contrib/aws.rb, line 309
def canonical_query(params)
  params ||= {}
  params = Hash[params.sort_by(&:first)]
  query = params.map do |key, value|
    "#{safe_escape(key)}=#{safe_escape(value)}"
  end.join("&")
end
credential_scope() click to toggle source

@return [String] credential scope for request

# File lib/miasma/contrib/aws.rb, line 268
def credential_scope
  [
    Time.now.utc.strftime("%Y%m%d"),
    region,
    service,
    "aws4_request",
  ].join("/")
end
generate(http_method, path, opts) click to toggle source

Generate the signature string for AUTH

@param http_method [Symbol] HTTP request method @param path [String] request path @param opts [Hash] request options @return [String] signature

# File lib/miasma/contrib/aws.rb, line 191
def generate(http_method, path, opts)
  signature = generate_signature(http_method, path, opts)
  "#{algorithm} Credential=#{access_key}/#{credential_scope}, " \
  "SignedHeaders=#{signed_headers(opts[:headers])}, Signature=#{signature}"
end
generate_signature(http_method, path, opts) click to toggle source

Generate the signature

@param http_method [Symbol] HTTP request method @param path [String] request path @param opts [Hash] request options @return [String] signature

# File lib/miasma/contrib/aws.rb, line 226
def generate_signature(http_method, path, opts)
  to_sign = [
    algorithm,
    opts.to_smash.fetch(:headers, "X-Amz-Date", AwsApiCore.time_iso8601),
    credential_scope,
    hashed_canonical_request(
      can_req = build_canonical_request(http_method, path, opts)
    ),
  ].join("\n")
  logger.debug("generating signature for `#{to_sign.inspect}`")
  signature = sign_request(to_sign)
end
generate_url(http_method, path, opts) click to toggle source

Generate URL with signed params

@param http_method [Symbol] HTTP request method @param path [String] request path @param opts [Hash] request options @return [String] signature

# File lib/miasma/contrib/aws.rb, line 203
def generate_url(http_method, path, opts)
  opts[:params].merge!(
    Smash.new(
      "X-Amz-SignedHeaders" => signed_headers(opts[:headers]),
      "X-Amz-Algorithm" => algorithm,
      "X-Amz-Credential" => "#{access_key}/#{credential_scope}",
    )
  )
  signature = generate_signature(
    http_method, path,
    opts.merge(:body => "UNSIGNED-PAYLOAD")
  )
  params = opts[:params].merge("X-Amz-Signature" => signature)
  logger.debug("url generation parameters `#{params.inspect}`")
  "https://#{opts[:headers]["Host"]}/#{path}?#{canonical_query(params)}"
end
hashed_canonical_request(request) click to toggle source

Generate the hash of the canonical request

@param request [String] canonical request string @return [String] hashed canonical request

# File lib/miasma/contrib/aws.rb, line 281
def hashed_canonical_request(request)
  hmac.hexdigest_of(request)
end
sign_request(request) click to toggle source

Sign the request

@param request [String] request to sign @return [String] signature

# File lib/miasma/contrib/aws.rb, line 243
def sign_request(request)
  key = hmac.sign(
    "aws4_request",
    hmac.sign(
      service,
      hmac.sign(
        region,
        hmac.sign(
          Time.now.utc.strftime("%Y%m%d"),
          "AWS4#{hmac.key}"
        )
      )
    )
  )
  signature = hmac.hex_sign(request, key)
  logger.debug("generated signature `#{signature.inspect}`")
  signature
end
signed_headers(headers) click to toggle source

List of headers included in signature

@param headers [Hash] request headers @return [String] header list

# File lib/miasma/contrib/aws.rb, line 333
def signed_headers(headers)
  headers ||= {}
  headers.sort_by(&:first).map(&:first).
    map(&:downcase).join(";")
end