class HTTPX::Plugins::AWSSigV4::Signer

  1. lib/httpx/plugins/aws_sigv4.rb
Superclass: Object

Signs requests using the AWS sigv4 signing.

Methods

Public Class

  1. new

Public Instance

  1. sign!

Public Class methods

new( service:, region:, credentials: nil, username: nil, password: nil, security_token: nil, provider_prefix: "aws", header_provider_field: "amz", unsigned_headers: [], apply_checksum_header: true, algorithm: "SHA256" )
[show source]
   # File lib/httpx/plugins/aws_sigv4.rb
17 def initialize(
18   service:,
19   region:,
20   credentials: nil,
21   username: nil,
22   password: nil,
23   security_token: nil,
24   provider_prefix: "aws",
25   header_provider_field: "amz",
26   unsigned_headers: [],
27   apply_checksum_header: true,
28   algorithm: "SHA256"
29 )
30   @credentials = credentials || Credentials.new(username, password, security_token)
31   @service = service
32   @region = region
33 
34   @unsigned_headers = Set.new(unsigned_headers.map(&:downcase))
35   @unsigned_headers << "authorization"
36   @unsigned_headers << "x-amzn-trace-id"
37   @unsigned_headers << "expect"
38 
39   @apply_checksum_header = apply_checksum_header
40   @provider_prefix = provider_prefix
41   @header_provider_field = header_provider_field
42 
43   @algorithm = algorithm
44 end

Public Instance methods

sign!(request)
[show source]
    # File lib/httpx/plugins/aws_sigv4.rb
 46 def sign!(request)
 47   lower_provider_prefix = "#{@provider_prefix}4"
 48   upper_provider_prefix = lower_provider_prefix.upcase
 49 
 50   downcased_algorithm = @algorithm.downcase
 51 
 52   datetime = (request.headers["x-#{@header_provider_field}-date"] ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ"))
 53   date = datetime[0, 8]
 54 
 55   content_hashed = request.headers["x-#{@header_provider_field}-content-#{downcased_algorithm}"] || hexdigest(request.body)
 56 
 57   request.headers["x-#{@header_provider_field}-content-#{downcased_algorithm}"] ||= content_hashed if @apply_checksum_header
 58   request.headers["x-#{@header_provider_field}-security-token"] ||= @credentials.security_token if @credentials.security_token
 59 
 60   signature_headers = request.headers.each.reject do |k, _|
 61     @unsigned_headers.include?(k)
 62   end
 63   # aws sigv4 needs to declare the host, regardless of protocol version
 64   signature_headers << ["host", request.authority] unless request.headers.key?("host")
 65   signature_headers.sort_by!(&:first)
 66 
 67   signed_headers = signature_headers.map(&:first).join(";")
 68 
 69   canonical_headers = signature_headers.map do |k, v|
 70     # eliminate whitespace between value fields, unless it's a quoted value
 71     "#{k}:#{v.start_with?("\"") && v.end_with?("\"") ? v : v.gsub(/\s+/, " ").strip}\n"
 72   end.join
 73 
 74   # canonical request
 75   creq = "#{request.verb}" \
 76          "\n#{request.canonical_path}" \
 77          "\n#{request.canonical_query}" \
 78          "\n#{canonical_headers}" \
 79          "\n#{signed_headers}" \
 80          "\n#{content_hashed}"
 81 
 82   credential_scope = "#{date}" \
 83                      "/#{@region}" \
 84                      "/#{@service}" \
 85                      "/#{lower_provider_prefix}_request"
 86 
 87   algo_line = "#{upper_provider_prefix}-HMAC-#{@algorithm}"
 88   # string to sign
 89   sts = "#{algo_line}" \
 90         "\n#{datetime}" \
 91         "\n#{credential_scope}" \
 92         "\n#{OpenSSL::Digest.new(@algorithm).hexdigest(creq)}"
 93 
 94   # signature
 95   k_date = hmac("#{upper_provider_prefix}#{@credentials.password}", date)
 96   k_region = hmac(k_date, @region)
 97   k_service = hmac(k_region, @service)
 98   k_credentials = hmac(k_service, "#{lower_provider_prefix}_request")
 99   sig = hexhmac(k_credentials, sts)
100 
101   credential = "#{@credentials.username}/#{credential_scope}"
102   # apply signature
103   request.headers["authorization"] =
104     "#{algo_line} " \
105     "Credential=#{credential}, " \
106     "SignedHeaders=#{signed_headers}, " \
107     "Signature=#{sig}"
108 end