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, Rails

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)


139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/auth-hmac.rb', line 139

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

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

  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)
  end
  
  @signature_method = lambda { |r| @signature_class.send(:new, r) }
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)


187
188
189
190
# File 'lib/auth-hmac.rb', line 187

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.



159
160
161
# File 'lib/auth-hmac.rb', line 159

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.



177
178
179
180
# File 'lib/auth-hmac.rb', line 177

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.



168
169
170
# File 'lib/auth-hmac.rb', line 168

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)


213
214
215
216
217
218
219
220
221
222
223
# File 'lib/auth-hmac.rb', line 213

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



238
239
240
# File 'lib/auth-hmac.rb', line 238

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

#authorization_header(request) ⇒ Object



234
235
236
# File 'lib/auth-hmac.rb', line 234

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

#canonical_string(request) ⇒ Object



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

def canonical_string(request)
  @signature_method.call(request)
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)


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

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?
  request['Authorization'] = authorization(request, access_key_id, secret)
end

#signature(request, secret) ⇒ Object



225
226
227
228
# File 'lib/auth-hmac.rb', line 225

def signature(request, secret)
  digest = OpenSSL::Digest::Digest.new('sha1')
  Base64.encode64(OpenSSL::HMAC.digest(digest, secret, canonical_string(request))).strip
end