Class: AuthHMAC

Inherits:
Object
  • Object
show all
Includes:
Headers
Defined in:
lib/auth-hmac.rb,
lib/auth-hmac/version.rb,
lib/auth-hmac/middleware.rb

Overview

This module provides a HMAC Authentication method for HTTP requests. It should work with net/http request classes and CGIRequest classes and hence Rails.

It is loosely based on the Amazon Web Services Authentication mechanism but generalized to be useful to any application that requires HMAC based authentication. As a result of the generalization, it won’t work with AWS because it doesn’t support the Amazon extension headers.

References

Cryptographic Hash functions

en.wikipedia.org/wiki/Cryptographic_hash_function

SHA-1 Hash function

en.wikipedia.org/wiki/SHA-1

HMAC algorithm

en.wikipedia.org/wiki/HMAC

RFC 2104

tools.ietf.org/html/rfc2104

Defined Under Namespace

Modules: Headers, VERSION Classes: CanonicalString, Middleware

Constant Summary collapse

@@default_signature_class =
CanonicalString

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Headers

#find_header, #headers

Constructor Details

#initialize(credential_store, options = nil) ⇒ AuthHMAC

Create an AuthHMAC instance using the given credential store

Credential Store:

  • Credential store must respond to the [] method and return a secret for access key id

Options: Override default options

  • :service_id - Service ID used in the AUTHORIZATION header string. Default is AuthHMAC.

  • :signature_method - Proc object that takes request and produces the signature string

    used for authentication. Default is CanonicalString.
    

Examples:

my_hmac = AuthHMAC.new('access_id1' => 'secret1', 'access_id2' => 'secret2')

cred_store = { 'access_id1' => 'secret1', 'access_id2' => 'secret2' }
options = { :service_id => 'MyApp', :signature_method => lambda { |r| MyRequestString.new(r) } }
my_hmac = AuthHMAC.new(cred_store, options)


179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/auth-hmac.rb', line 179

def initialize(credential_store, options = nil)
  @credential_store = credential_store

  # Defaults
  @service_id = self.class.name
  @signature_class = @@default_signature_class
  @authenticate_referrer = false

  unless options.nil?
    @service_id = options[:service_id] if options.key?(:service_id)
    @signature_class = options[:signature] if options.key?(:signature) && options[:signature].is_a?(Class)
    @authenticate_referrer = options[:authenticate_referrer] || options[:authenticate_referer]
  end
  
  @signature_method = lambda { |r,ar| @signature_class.send(:new, r, ar) }
end

Instance Attribute Details

#service_idObject (readonly)

Returns the value of attribute service_id.



196
197
198
# File 'lib/auth-hmac.rb', line 196

def service_id
  @service_id
end

Class Method Details

.authenticated?(request, access_key_id, secret, options) ⇒ Boolean

Authenticates a request using HMAC

Supports same options as AuthHMAC.initialize for overriding service_id and signature method.

Returns:

  • (Boolean)


231
232
233
234
# File 'lib/auth-hmac.rb', line 231

def AuthHMAC.authenticated?(request, access_key_id, secret, options)
  credentials = { access_key_id => secret }
  self.new(credentials, options).authenticated?(request)
end

.canonical_string(request, options = nil) ⇒ Object

Generates canonical signing string for given request

Supports same options as AuthHMAC.initialize for overriding service_id and signature method.



203
204
205
# File 'lib/auth-hmac.rb', line 203

def AuthHMAC.canonical_string(request, options = nil)
  self.new(nil, options).canonical_string(request)
end

.sign!(request, access_key_id, secret, options = nil) ⇒ Object

Signs a request using a given access key id and secret.

Supports same options as AuthHMAC.initialize for overriding service_id and signature method.



221
222
223
224
# File 'lib/auth-hmac.rb', line 221

def AuthHMAC.sign!(request, access_key_id, secret, options = nil)
  credentials = { access_key_id => secret }
  self.new(credentials, options).sign!(request, access_key_id)
end

.signature(request, secret, options = nil) ⇒ Object

Generates signature string for a given secret

Supports same options as AuthHMAC.initialize for overriding service_id and signature method.



212
213
214
# File 'lib/auth-hmac.rb', line 212

def AuthHMAC.signature(request, secret, options = nil)
  self.new(nil, options).signature(request, secret)
end

Instance Method Details

#authenticated?(request) ⇒ Boolean

Authenticates a request using HMAC

Returns true if the request has an AuthHMAC Authorization header and the access id and HMAC match an id and HMAC produced for the secret in the credential store. Otherwise returns false.

Returns:

  • (Boolean)


261
262
263
264
265
266
267
268
269
270
271
# File 'lib/auth-hmac.rb', line 261

def authenticated?(request)
  rx = Regexp.new("#{@service_id} ([^:]+):(.+)$")
  if md = rx.match(authorization_header(request))
    access_key_id = md[1]
    hmac = md[2]
    secret = @credential_store[access_key_id]
    !secret.nil? && hmac == signature(request, secret)
  else
    false
  end
end

#authorization(request, access_key_id, secret) ⇒ Object



286
287
288
# File 'lib/auth-hmac.rb', line 286

def authorization(request, access_key_id, secret)
  "#{@service_id} #{access_key_id}:#{signature(request, secret)}"      
end

#authorization_header(request) ⇒ Object



282
283
284
# File 'lib/auth-hmac.rb', line 282

def authorization_header(request)
  find_header(%w(Authorization HTTP_AUTHORIZATION), headers(request))
end

#canonical_string(request, authenticate_referrer = false) ⇒ Object



278
279
280
# File 'lib/auth-hmac.rb', line 278

def canonical_string(request, authenticate_referrer=false)
  @signature_method.call(request, authenticate_referrer)
end

#sign!(request, access_key_id) ⇒ Object

Signs a request using the access_key_id and the secret associated with that id in the credential store.

Signing a requests adds an Authorization header to the request in the format:

<service_id> <access_key_id>:<signature>

where <signature> is the Base64 encoded HMAC-SHA1 of the CanonicalString and the secret.

Raises:

  • (ArgumentError)


245
246
247
248
249
250
251
252
253
# File 'lib/auth-hmac.rb', line 245

def sign!(request, access_key_id)
  secret = @credential_store[access_key_id]
  raise ArgumentError, "No secret found for key id '#{access_key_id}'" if secret.nil?
  if request.respond_to?(:headers)
    request.headers['Authorization'] = authorization(request, access_key_id, secret)
  else
    request['Authorization'] = authorization(request, access_key_id, secret)
  end
end

#signature(request, secret) ⇒ Object



273
274
275
276
# File 'lib/auth-hmac.rb', line 273

def signature(request, secret)
  digest = OpenSSL::Digest::Digest.new('sha1')
  [OpenSSL::HMAC.digest(digest, secret, canonical_string(request, @authenticate_referrer))].pack('m').strip
end