Skip to content

Commit

Permalink
Forward the Hmac digest method in the Header HMAC_METHOD
Browse files Browse the repository at this point in the history
  • Loading branch information
fwininger committed Feb 25, 2016
1 parent 188e6d0 commit e2abb84
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 8 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ you can pass the http method as an option into the sign! method like so:
@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key, :digest => 'sha256')
```

With the `digest` option, the `Authorization` header will be change from:

Authorization = APIAuth 'client access id':'signature'

to:

Authorization = APIAuth-HMAC-DIGEST_NAME 'client access id':'signature'

### ActiveResource Clients

ApiAuth can transparently protect your ActiveResource communications with a
Expand Down Expand Up @@ -161,7 +169,14 @@ To validate whether or not a request is authentic:
ApiAuth.authentic?(signed_request, secret_key)
```

If you want to use another digest method, you should pass it as an option parameter:
The `authentic?` method uses the digest specified in the `Authorization` header.
For exemple SHA256 for:

Authorization = APIAuth-HMAC-SHA256 'client access id':'signature'

And by default SHA1 if the HMAC-DIGEST is not specified.

If you want to force the usage of another digest method, you should pass it as an option parameter:

``` ruby
ApiAuth.authentic?(signed_request, secret_key, :digest => 'sha256')
Expand Down
18 changes: 13 additions & 5 deletions lib/api_auth/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ def sign!(request, access_id, secret_key, options = {})
# secret key. Returns true if the request is authentic and false otherwise.
def authentic?(request, secret_key, options = {})
return false if secret_key.nil?
options = { :override_http_method => nil, :digest => 'sha1' }.merge(options)

options = { :override_http_method => nil }.merge(options)

headers = Headers.new(request)

if headers.md5_mismatch?
false
elsif !signatures_match?(headers, secret_key, options)
Expand All @@ -50,7 +52,7 @@ def authentic?(request, secret_key, options = {})
def access_id(request)
headers = Headers.new(request)
if match_data = parse_auth_header(headers.authorization_header)
return match_data[1]
return match_data[2]
end

nil
Expand All @@ -67,7 +69,7 @@ def generate_secret_key

private

AUTH_HEADER_PATTERN = /APIAuth ([^:]+):(.+)$/
AUTH_HEADER_PATTERN = /APIAuth(?:-HMAC-(MD[245]|SHA(?:1|224|256|384|512)*))? ([^:]+):(.+)$/

def request_too_old?(headers)
# 900 seconds is 15 minutes
Expand All @@ -81,7 +83,12 @@ def signatures_match?(headers, secret_key, options)
match_data = parse_auth_header(headers.authorization_header)
return false unless match_data

header_sig = match_data[2]
digest = match_data[1].blank? ? 'SHA1' : match_data[1].upcase
raise InvalidRequestCipher if !options[:digest].nil? && !options[:digest].casecmp(digest).zero?

options = { :digest => digest }.merge(options)

header_sig = match_data[3]
calculated_sig_no_http = hmac_signature(headers, secret_key, options.merge(:with_http_method => false))
calculated_sig_with_http = hmac_signature(headers, secret_key, options.merge(:with_http_method => true))

Expand All @@ -99,7 +106,8 @@ def hmac_signature(headers, secret_key, options)
end

def auth_header(headers, access_id, secret_key, options)
"APIAuth #{access_id}:#{hmac_signature(headers, secret_key, options)}"
hmac_string = "-HMAC-#{options[:digest].upcase}" unless options[:digest] == 'sha1'
"APIAuth#{hmac_string} #{access_id}:#{hmac_signature(headers, secret_key, options)}"
end

def parse_auth_header(auth_header)
Expand Down
3 changes: 3 additions & 0 deletions lib/api_auth/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ class ApiAuthError < StandardError; end

# Raised when the HTTP request object passed is not supported
class UnknownHTTPRequest < ApiAuthError; end

# Raised when the client request digest is not the same as the server
class InvalidRequestDigest < ApiAuthError; end
end
8 changes: 6 additions & 2 deletions spec/api_auth_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def hmac(secret_key, request, canonical_string = nil, digest = 'sha1')
it 'calculates the hmac_signature with http method' do
ApiAuth.sign!(request, '1044', '123', :with_http_method => true, :digest => 'sha256')
signature = hmac('123', request, canonical_string, 'sha256')
expect(request['Authorization']).to eq("APIAuth 1044:#{signature}")
expect(request['Authorization']).to eq("APIAuth-HMAC-SHA256 1044:#{signature}")
end
end
end
Expand Down Expand Up @@ -162,13 +162,17 @@ def hmac(secret_key, request, canonical_string = nil, digest = 'sha1')
)
canonical_string = ApiAuth::Headers.new(new_request).canonical_string_with_http_method
signature = hmac('123', new_request, canonical_string, 'sha256')
new_request['Authorization'] = "APIAuth 1044:#{signature}"
new_request['Authorization'] = "APIAuth-HMAC-SHA256 1044:#{signature}"
new_request
end

it 'validates for sha256 digest' do
expect(ApiAuth.authentic?(request, '123', :digest => 'sha256')).to eq true
end

it 'validates exception with wrong client digest' do
expect { ApiAuth.authentic?(request, '123', :digest => 'sha512') }.to raise_error(ApiAuth::InvalidRequestDigest)
end
end
end

Expand Down

0 comments on commit e2abb84

Please sign in to comment.