class Miasma::Contrib::AwsApiCore::SignatureV4
AWS signature version 4
Attributes
@return [String] access key
@return [Hmac]
@return [String] region
@return [String] service
Public Class Methods
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
@return [String] signature algorithm
# File lib/miasma/contrib/aws.rb, line 263 def algorithm "AWS4-HMAC-SHA256" end
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
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
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
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
@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 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 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 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
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 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
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