Class: HMAC::Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/hmac/signer.rb

Overview

Helper class that provides signing capabilites for the hmac strategies.

Author:

Constant Summary collapse

DEFAULT_OPTS =
{
  :auth_scheme => "HMAC",
  :auth_param => "auth",
  :auth_header => "Authorization",
  :auth_header_format => "%{auth_scheme} %{signature}",
  :query_based => false,
  :use_alternate_date_header => false,
  :extra_auth_params => {},
  :ignore_params => []
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(algorithm = "sha1", default_opts = {}) ⇒ Signer

create a new HMAC instance

Parameters:

  • algorithm (String) (defaults to: "sha1")

    The hashing-algorithm to use. See the openssl documentation for valid values.

  • default_opts (Hash) (defaults to: {})

    The default options for all calls that take opts

Options Hash (default_opts):

  • :auth_scheme (String) — default: 'HMAC'

    The name of the authorization scheme used in the Authorization header and to construct various header-names

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

  • :auth_header (String) — default: 'Authorization'

    The name of the authorization header to use

  • :auth_header_format (String) — default: '%{auth_scheme} %{signature}'

    The format of the authorization header. Will be interpolated with the given options and the signature.

  • :nonce_header (String) — default: 'X-#{auth_scheme}-Nonce'

    The header name for the request nonce

  • :alternate_date_header (String) — default: 'X-#{auth_scheme}-Date'

    The header name for the alternate date header

  • :query_based (Bool) — default: false

    Whether to use query based authentication

  • :use_alternate_date_header (Bool) — default: false

    Use the alternate date header instead of ‘Date`

  • :extra_auth_params (Hash) — default: {}

    Additional parameters to inject in the auth parameter

  • :ignore_params (Array<Symbol>) — default: []

    Params to ignore for signing



41
42
43
44
45
46
# File 'lib/hmac/signer.rb', line 41

def initialize(algorithm = "sha1", default_opts = {})
  self.algorithm = algorithm
  default_opts[:nonce_header] ||="X-%{scheme}-Nonce" % {:scheme => (default_opts[:auth_scheme] || "HMAC")}
  default_opts[:alternate_date_header] ||= "X-%{scheme}-Date" % {:scheme => (default_opts[:auth_scheme] || "HMAC")}
  self.default_opts = DEFAULT_OPTS.merge(default_opts)
end

Instance Attribute Details

#algorithmObject

Returns the value of attribute algorithm.



12
13
14
# File 'lib/hmac/signer.rb', line 12

def algorithm
  @algorithm
end

#default_optsObject

Returns the value of attribute default_opts.



12
13
14
# File 'lib/hmac/signer.rb', line 12

def default_opts
  @default_opts
end

#secretObject

Returns the value of attribute secret.



12
13
14
# File 'lib/hmac/signer.rb', line 12

def secret
  @secret
end

Instance Method Details

#canonical_representation(params) ⇒ String

generates the canonical representation for a given request

Parameters:

  • params (Hash)

    the parameters to create the representation with

Options Hash (params):

  • :method (String)

    The HTTP Verb of the request

  • :date (String)

    The date of the request as it was formatted in the request

  • :nonce (String) — default: ''

    The nonce given in the request

  • :path (String)

    The path portion of the request

  • :query (Hash) — default: {}

    The query parameters given in the request. Must not contain the auth param.

  • :headers (Hash) — default: {}

    All headers given in the request (optional and required)

  • :auth_scheme (String) — default: 'HMAC'

    The name of the authorization scheme used in the Authorization header and to construct various header-names

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

  • :extra_auth_params (Hash) — default: {}

    Additional parameters to inject in the auth parameter

  • :ignore_params (Array<Symbol>) — default: []

    Params to ignore for signing

  • :auth_header (String) — default: 'Authorization'

    The name of the authorization header to use

  • :auth_header_format (String) — default: '%{auth_scheme} %{signature}'

    The format of the authorization header. Will be interpolated with the given options and the signature.

  • :nonce_header (String) — default: 'X-#{auth_scheme}-Nonce'

    The header name for the request nonce

  • :alternate_date_header (String) — default: 'X-#{auth_scheme}-Date'

    The header name for the alternate date header

  • :query_based (Bool) — default: false

    Whether to use query based authentication

  • :use_alternate_date_header (Bool) — default: false

    Use the alternate date header instead of ‘Date`

Returns:

  • (String)

    the canonical representation



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/hmac/signer.rb', line 156

def canonical_representation(params)
  rep = ""

  rep << "#{params[:method].upcase}\n"
  rep << "date:#{params[:date]}\n"
  rep << "nonce:#{params[:nonce]}\n"

  (params[:headers] || {}).sort.each do |pair|
    name,value = *pair
    rep << "#{name.downcase}:#{value}\n"
  end

  rep << params[:path]

  p = (params[:query] || {}).dup

  if !p.empty?
    query = p.sort.map do |key, value|
      "%{key}=%{value}" % {
        :key => Rack::Utils.unescape(key.to_s),
        :value => Rack::Utils.unescape(value.to_s)
      }
    end.join("&")
    rep << "?#{query}"
  end

  rep
end

#generate_signature(params) ⇒ String

Generate the signature from a hash representation

returns nil if no secret or an empty secret was given

Parameters:

  • params (Hash)

    the parameters to create the representation with

Options Hash (params):

  • :secret (String)

    The secret to generate the signature with

  • :method (String)

    The HTTP Verb of the request

  • :date (String)

    The date of the request as it was formatted in the request

  • :nonce (String) — default: ''

    The nonce given in the request

  • :path (String)

    The path portion of the request

  • :query (Hash) — default: {}

    The query parameters given in the request. Must not contain the auth param.

  • :headers (Hash) — default: {}

    All headers given in the request (optional and required)

  • :auth_scheme (String) — default: 'HMAC'

    The name of the authorization scheme used in the Authorization header and to construct various header-names

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

  • :extra_auth_params (Hash) — default: {}

    Additional parameters to inject in the auth parameter

  • :ignore_params (Array<Symbol>) — default: []

    Params to ignore for signing

  • :auth_header (String) — default: 'Authorization'

    The name of the authorization header to use

  • :auth_header_format (String) — default: '%{auth_scheme} %{signature}'

    The format of the authorization header. Will be interpolated with the given options and the signature.

  • :nonce_header (String) — default: 'X-#{auth_scheme}-Nonce'

    The header name for the request nonce

  • :alternate_date_header (String) — default: 'X-#{auth_scheme}-Date'

    The header name for the alternate date header

  • :query_based (Bool) — default: false

    Whether to use query based authentication

  • :use_alternate_date_header (Bool) — default: false

    Use the alternate date header instead of ‘Date`

Returns:



72
73
74
75
76
77
78
79
80
81
# File 'lib/hmac/signer.rb', line 72

def generate_signature(params)
  secret = params.delete(:secret)

  # jruby stumbles over empty secrets, we regard them as invalid anyways, so we return an empty digest if no scret was given
  if '' == secret.to_s
    nil
  else
    OpenSSL::HMAC.hexdigest(algorithm, secret, canonical_representation(params))
  end
end

#sign_request(url, secret, opts = {}) ⇒ Object

sign the given request

Parameters:

  • url (String)

    The url of the request

  • secret (String)

    The shared secret for the signature

  • opts (Hash) (defaults to: {})

    Options for the signature generation

Options Hash (opts):

  • :nonce (String) — default: ''

    The nonce to use in the signature

  • :date (String, #strftime) — default: Time.now

    The date to use in the signature

  • :headers (Hash) — default: {}

    A list of optional headers to include in the signature

  • :method (String, Symbol) — default: 'GET'

    The HTTP method to use in the signature

  • :auth_scheme (String) — default: 'HMAC'

    The name of the authorization scheme used in the Authorization header and to construct various header-names

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

  • :extra_auth_params (Hash) — default: {}

    Additional parameters to inject in the auth parameter

  • :ignore_params (Array<Symbol>) — default: []

    Params to ignore for signing

  • :auth_header (String) — default: 'Authorization'

    The name of the authorization header to use

  • :auth_header_format (String) — default: '%{auth_scheme} %{signature}'

    The format of the authorization header. Will be interpolated with the given options and the signature.

  • :nonce_header (String) — default: 'X-#{auth_scheme}-Nonce'

    The header name for the request nonce

  • :alternate_date_header (String) — default: 'X-#{auth_scheme}-Date'

    The header name for the alternate date header

  • :query_based (Bool) — default: false

    Whether to use query based authentication

  • :use_alternate_date_header (Bool) — default: false

    Use the alternate date header instead of ‘Date`



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/hmac/signer.rb', line 207

def sign_request(url, secret, opts = {})
  opts = default_opts.merge(opts)

  uri = parse_url(url)
  headers = opts[:headers] || {}

  date = opts[:date] || Time.now.gmtime
  date = date.gmtime.strftime('%a, %e %b %Y %T GMT') if date.respond_to? :strftime

  method = opts[:method] ? opts[:method].to_s.upcase : "GET"

  query_values = Rack::Utils.parse_nested_query(uri.query)

  if query_values
    query_values.delete_if do |k,v|
      opts[:ignore_params].one? { |param| (k == param) || (k == param.to_s) }
    end
  end

  signature = generate_signature(:secret => secret, :method => method, :path => uri.path, :date => date, :nonce => opts[:nonce], :query => query_values, :headers => opts[:headers], :ignore_params => opts[:ignore_params])

  if opts[:query_based]
    auth_params = opts[:extra_auth_params].merge({
      "date" => date,
      "signature" => signature
    })
    auth_params[:nonce] = opts[:nonce] unless opts[:nonce].nil?

    query_values ||= {}
    query_values[opts[:auth_param]] = auth_params
    uri.query = Rack::Utils.build_nested_query(query_values)
  else
    headers[opts[:auth_header]]   = opts[:auth_header_format] % opts.merge({:signature => signature})
    headers[opts[:nonce_header]]  = opts[:nonce] unless opts[:nonce].nil?

    if opts[:use_alternate_date_header]
      headers[opts[:alternate_date_header]] = date
    else
      headers["Date"] = date
    end
  end

  [headers, uri.to_s]
end

#sign_url(url, secret, opts = {}) ⇒ String

convienience method to sign a url for use with query-based authentication

Parameters:

  • url (String)

    the url to sign

  • secret (String)

    the secret used to sign the url

  • opts (Hash) (defaults to: {})

    Options controlling the singature generation

Options Hash (opts):

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

  • :extra_auth_params (Hash) — default: {}

    Additional parameters to inject in the auth parameter

Returns:



262
263
264
265
266
267
268
# File 'lib/hmac/signer.rb', line 262

def sign_url(url, secret, opts = {})
  opts = default_opts.merge(opts)
  opts[:query_based] = true

  headers, url = *sign_request(url, secret, opts)
  url
end

#validate_signature(signature, params) ⇒ Bool

compares the given signature with the signature created from a hash representation

Parameters:

  • signature (String)

    the signature to compare with

  • params (Hash)

    the parameters to create the representation with

Options Hash (params):

  • :secret (String)

    The secret to generate the signature with

  • :method (String)

    The HTTP Verb of the request

  • :date (String)

    The date of the request as it was formatted in the request

  • :nonce (String) — default: ''

    The nonce given in the request

  • :path (String)

    The path portion of the request

  • :query (Hash) — default: {}

    The query parameters given in the request. Must not contain the auth param.

  • :headers (Hash) — default: {}

    All headers given in the request (optional and required)

  • :auth_scheme (String) — default: 'HMAC'

    The name of the authorization scheme used in the Authorization header and to construct various header-names

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

  • :extra_auth_params (Hash) — default: {}

    Additional parameters to inject in the auth parameter

  • :ignore_params (Array<Symbol>) — default: []

    Params to ignore for signing

  • :auth_header (String) — default: 'Authorization'

    The name of the authorization header to use

  • :auth_header_format (String) — default: '%{auth_scheme} %{signature}'

    The format of the authorization header. Will be interpolated with the given options and the signature.

  • :nonce_header (String) — default: 'X-#{auth_scheme}-Nonce'

    The header name for the request nonce

  • :alternate_date_header (String) — default: 'X-#{auth_scheme}-Date'

    The header name for the alternate date header

  • :query_based (Bool) — default: false

    Whether to use query based authentication

  • :use_alternate_date_header (Bool) — default: false

    Use the alternate date header instead of ‘Date`

Returns:

  • (Bool)

    true if the signature matches



106
107
108
# File 'lib/hmac/signer.rb', line 106

def validate_signature(signature, params)
  compare_hashes(signature, generate_signature(params))
end

#validate_url_signature(url, secret, opts = {}) ⇒ Bool

convienience method to check the signature of a url with query-based authentication

Parameters:

  • url (String)

    the url to test

  • secret (String)

    the secret used to sign the url

  • opts (Hash) (defaults to: {})

    Options controlling the singature generation

Options Hash (opts):

  • :auth_param (String) — default: 'auth'

    The name of the authentication param to use for query based authentication

Returns:

  • (Bool)

    true if the signature is valid



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/hmac/signer.rb', line 119

def validate_url_signature(url, secret, opts = {})
  opts = default_opts.merge(opts)
  opts[:query_based] = true

  uri = parse_url(url)
  query_values = Rack::Utils.parse_nested_query(uri.query)
  return false unless query_values

  auth_params = query_values.delete(opts[:auth_param])
  return false unless auth_params

  date = auth_params["date"]
  nonce = auth_params["nonce"]
  validate_signature(auth_params["signature"], :secret => secret, :method => "GET", :path => uri.path, :date => date, :nonce => nonce, :query => query_values, :headers => {})
end