Class: AuthHMAC

Inherits:
Object
  • Object
show all
Includes:
Headers
Defined in:
lib/auth-hmac.rb,
lib/auth-hmac/version.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

Constant Summary collapse

@@default_signature_class =
CanonicalString

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)


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/auth-hmac.rb', line 172

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

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)


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

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.



194
195
196
# File 'lib/auth-hmac.rb', line 194

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.



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

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.



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

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)


252
253
254
255
256
257
258
259
260
261
262
# File 'lib/auth-hmac.rb', line 252

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



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

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

#authorization_header(request) ⇒ Object



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

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

#canonical_string(request, authenticate_referrer = false) ⇒ Object



269
270
271
# File 'lib/auth-hmac.rb', line 269

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)


236
237
238
239
240
241
242
243
244
# File 'lib/auth-hmac.rb', line 236

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



264
265
266
267
# File 'lib/auth-hmac.rb', line 264

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