Class: Cyclid::HMAC::Signer

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

Overview

Helper class that provides signing capabilites for the hmac strategies.

Author:

Constant Summary collapse

DEFAULT_OPTS =

HMAC defaults

{
  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: []
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(algorithm = 'sha256', default_opts = {}) ⇒ Signer

create a new HMAC instance

Parameters:

  • algorithm (String) (defaults to: 'sha256')

    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



64
65
66
67
68
69
# File 'lib/cyclid/hmac.rb', line 64

def initialize(algorithm = 'sha256', 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.



34
35
36
# File 'lib/cyclid/hmac.rb', line 34

def algorithm
  @algorithm
end

#default_optsObject

Returns the value of attribute default_opts.



34
35
36
# File 'lib/cyclid/hmac.rb', line 34

def default_opts
  @default_opts
end

#secretObject

Returns the value of attribute secret.



34
35
36
# File 'lib/cyclid/hmac.rb', line 34

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



154
155
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
# File 'lib/cyclid/hmac.rb', line 154

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

  unless 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:

  • (String)

    the signature



95
96
97
98
99
100
101
102
103
104
# File 'lib/cyclid/hmac.rb', line 95

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`



205
206
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
# File 'lib/cyclid/hmac.rb', line 205

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, %d %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

#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



129
130
131
# File 'lib/cyclid/hmac.rb', line 129

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