api-auth 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92c46c40ca59ec931c02893f62155ea3efdfba48
4
- data.tar.gz: ac4c5514140b61f5df1b81408898413aef553cf7
3
+ metadata.gz: 9ee2a233ac1efcbf1af8b4028fde060ab2de42d5
4
+ data.tar.gz: 32dc7e2f7fb53759ff4d043e03f53914e4944345
5
5
  SHA512:
6
- metadata.gz: 38370d16d830ae325f862f972b20d267da8135750e923520df5902662cae46b4244a79d1520ec15b9ca4722f76018f88bfbb874bc4b64fd44f70b36f8402d018
7
- data.tar.gz: c3a16015aa11cd3da2b296d5729b3c7f0fccdea1e5a8005a98a4b7465e95944a951afdb9a474f182e1ff3df996d5d36e369f37f2cc60ed9e9eaf6c9e82c04985
6
+ metadata.gz: 5264828dfb7ec5743f4f74bb2b13ac6e8e002f1addfb448bb2e6ded240fe7ef13e0d1d2bc4646c3f568a33d25a32ff32ea4b070ce59c30d6431a8a451350cb2c
7
+ data.tar.gz: ffed44efe3245f7c74e9a6b76057ec1de088c16805d7f96fd10061d9babb940680e3b4927bc9aa480cd9aa8d03955c60c8fce1c6f596528f2bc4aaa9d2289c13
@@ -1,3 +1,9 @@
1
+ # 2.1.0 (2016-12-22)
2
+ - Fixed a NoMethodError that might occur when using the NetHttp Driver (#130 grahamkenville)
3
+ - More securely compare signatures in a way that prevents timing attacks (#56 leishman, #133 will0)
4
+ - Remove support for MD2 and MD4 hashing algorithms since they are insecure (#134 will0)
5
+ - Disallow requests that are too far in the future to limit the time available for a brute force signature guess (#119 fwininger)
6
+
1
7
  # 2.0.1 (2016-07-25)
2
8
  - Support of `api_auth_options` in ActiveResource integration (#102 fwininger)
3
9
  - Replace use of `#blank?` with `#nil?` to not depend on ActiveSupport (#114 packrat386)
data/README.md CHANGED
@@ -72,6 +72,7 @@ Here is the current list of supported request objects:
72
72
  * Curb (Curl::Easy)
73
73
  * RestClient
74
74
  * Faraday
75
+ * Httpi
75
76
 
76
77
  ### HTTP Client Objects
77
78
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.1
1
+ 2.1.0
@@ -20,7 +20,8 @@ Gem::Specification.new do |s|
20
20
  s.add_development_dependency 'rest-client', '~> 1.6.0'
21
21
  s.add_development_dependency 'curb', '~> 0.8.1'
22
22
  s.add_development_dependency 'httpi'
23
- s.add_development_dependency 'faraday'
23
+ faraday_version = RUBY_VERSION == '1.8.7' ? '< 0.10' : '>= 0.10'
24
+ s.add_development_dependency 'faraday', faraday_version
24
25
  s.add_development_dependency 'multipart-post', '~> 2.0'
25
26
 
26
27
  s.files = `git ls-files`.split("\n")
@@ -41,7 +41,7 @@ module ApiAuth
41
41
  false
42
42
  elsif !signatures_match?(headers, secret_key, options)
43
43
  false
44
- elsif request_too_old?(headers)
44
+ elsif !request_within_time_window?(headers)
45
45
  false
46
46
  else
47
47
  true
@@ -69,14 +69,15 @@ module ApiAuth
69
69
 
70
70
  private
71
71
 
72
- AUTH_HEADER_PATTERN = /APIAuth(?:-HMAC-(MD[245]|SHA(?:1|224|256|384|512)?))? ([^:]+):(.+)$/
72
+ AUTH_HEADER_PATTERN = /APIAuth(?:-HMAC-(MD5|SHA(?:1|224|256|384|512)?))? ([^:]+):(.+)$/
73
73
 
74
- def request_too_old?(headers)
74
+ def request_within_time_window?(headers)
75
75
  # 900 seconds is 15 minutes
76
76
 
77
- Time.httpdate(headers.timestamp).utc < (Time.now.utc - 900)
77
+ Time.httpdate(headers.timestamp).utc > (Time.now.utc - 900) &&
78
+ Time.httpdate(headers.timestamp).utc < (Time.now.utc + 900)
78
79
  rescue ArgumentError
79
- true
80
+ false
80
81
  end
81
82
 
82
83
  def signatures_match?(headers, secret_key, options)
@@ -91,7 +92,16 @@ module ApiAuth
91
92
  header_sig = match_data[3]
92
93
  calculated_sig = hmac_signature(headers, secret_key, options)
93
94
 
94
- header_sig == calculated_sig
95
+ secure_equals?(header_sig, calculated_sig, secret_key)
96
+ end
97
+
98
+ def secure_equals?(m1, m2, key)
99
+ sha1_hmac(key, m1) == sha1_hmac(key, m2)
100
+ end
101
+
102
+ def sha1_hmac(key, message)
103
+ digest = OpenSSL::Digest.new('sha1')
104
+ OpenSSL::HMAC.digest(digest, key, message)
95
105
  end
96
106
 
97
107
  def hmac_signature(headers, secret_key, options)
@@ -1,3 +1,4 @@
1
+ require 'time'
1
2
  module ApiAuth
2
3
  module RequestDrivers # :nodoc:
3
4
  class NetHttpRequest # :nodoc:
@@ -75,37 +75,44 @@ describe 'ApiAuth' do
75
75
 
76
76
  describe '.authentic?' do
77
77
  let(:request) do
78
- new_request = Net::HTTP::Put.new('/resource.xml?foo=bar&bar=foo',
79
- 'content-type' => 'text/plain',
80
- 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
81
- 'date' => Time.now.utc.httpdate)
78
+ Net::HTTP::Put.new('/resource.xml?foo=bar&bar=foo',
79
+ 'content-type' => 'text/plain',
80
+ 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
81
+ 'date' => Time.now.utc.httpdate)
82
+ end
82
83
 
83
- signature = hmac('123', new_request)
84
- new_request['Authorization'] = "APIAuth 1044:#{signature}"
85
- new_request
84
+ let(:signed_request) do
85
+ signature = hmac('123', request)
86
+ request['Authorization'] = "APIAuth 1044:#{signature}"
87
+ request
86
88
  end
87
89
 
88
90
  it 'validates that the signature in the request header matches the way we sign it' do
89
- expect(ApiAuth.authentic?(request, '123')).to eq true
91
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
90
92
  end
91
93
 
92
94
  it 'fails to validate a non matching signature' do
93
- expect(ApiAuth.authentic?(request, '456')).to eq false
95
+ expect(ApiAuth.authentic?(signed_request, '456')).to eq false
94
96
  end
95
97
 
96
98
  it 'fails to validate non matching md5' do
97
99
  request['content-md5'] = '12345'
98
- expect(ApiAuth.authentic?(request, '123')).to eq false
100
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq false
99
101
  end
100
102
 
101
103
  it 'fails to validate expired requests' do
102
104
  request['date'] = 16.minutes.ago.utc.httpdate
103
- expect(ApiAuth.authentic?(request, '123')).to eq false
105
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq false
106
+ end
107
+
108
+ it 'fails to validate far future requests' do
109
+ request['date'] = 16.minutes.from_now.utc.httpdate
110
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq false
104
111
  end
105
112
 
106
113
  it 'fails to validate if the date is invalid' do
107
114
  request['date'] = '٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠'
108
- expect(ApiAuth.authentic?(request, '123')).to eq false
115
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq false
109
116
  end
110
117
 
111
118
  it 'fails to validate if the request method differs' do
@@ -73,7 +73,7 @@ describe 'Rails integration' do
73
73
  expect(response.code).to eq('200')
74
74
  end
75
75
 
76
- it 'should forbid a request with properly signed headers but timestamp > 15 minutes' do
76
+ it 'should forbid a request with properly signed headers but timestamp > 15 minutes ago' do
77
77
  request = if ActionController::TestRequest.respond_to?(:create)
78
78
  ActionController::TestRequest.create
79
79
  else
@@ -85,6 +85,18 @@ describe 'Rails integration' do
85
85
  expect(response.code).to eq('401')
86
86
  end
87
87
 
88
+ it 'should forbid a request with properly signed headers but timestamp > 15 minutes in the future' do
89
+ request = if ActionController::TestRequest.respond_to?(:create)
90
+ ActionController::TestRequest.create
91
+ else
92
+ ActionController::TestRequest.new
93
+ end
94
+ request.env['DATE'] = 'Mon, 23 Jan 2100 03:29:56 GMT'
95
+ ApiAuth.sign!(request, '1044', API_KEY_STORE['1044'])
96
+ response = generated_response(request, :index)
97
+ expect(response.code).to eq('401')
98
+ end
99
+
88
100
  it "should insert a DATE header in the request when one hasn't been specified" do
89
101
  request = if ActionController::TestRequest.respond_to?(:create)
90
102
  ActionController::TestRequest.create
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauricio Gomes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-26 00:00:00.000000000 Z
11
+ date: 2016-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -168,14 +168,14 @@ dependencies:
168
168
  requirements:
169
169
  - - ">="
170
170
  - !ruby/object:Gem::Version
171
- version: '0'
171
+ version: '0.10'
172
172
  type: :development
173
173
  prerelease: false
174
174
  version_requirements: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - ">="
177
177
  - !ruby/object:Gem::Version
178
- version: '0'
178
+ version: '0.10'
179
179
  - !ruby/object:Gem::Dependency
180
180
  name: multipart-post
181
181
  requirement: !ruby/object:Gem::Requirement
@@ -265,7 +265,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
265
  version: '0'
266
266
  requirements: []
267
267
  rubyforge_project:
268
- rubygems_version: 2.5.1
268
+ rubygems_version: 2.5.2
269
269
  signing_key:
270
270
  specification_version: 4
271
271
  summary: Simple HMAC authentication for your APIs