hmac-uri 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == 0.1.0 (2012-09-06)
2
+
3
+ * Initial version.
@@ -0,0 +1,35 @@
1
+ # HMAC URI
2
+
3
+ HMAC based request signing of URI.
4
+
5
+
6
+ ## Example
7
+
8
+ ```ruby
9
+ require 'hmac/uri'
10
+
11
+ mac = HMAC::URI.new(secret: 'some long shared secret')
12
+ uri = mac.sign "http://example.org/resource?id=1"
13
+
14
+ mac.signed?(uri) #=> true
15
+ mac.signed?(uri, delta: 0) #=> false
16
+ ```
17
+
18
+ ## Nonce
19
+
20
+ `HMAC::URI` generates nonces which can be used to prevent replay attacks.
21
+
22
+ ```ruby
23
+ require 'hmac/uri'
24
+
25
+ seen = {}
26
+ check = proc {|nonce, ts, delta| (Time.now.to_i - ts) < delta && !seen.include?(nonce) && seen << nonce}
27
+ mac = HMAC::URI.new(secret: 'some long shared secret', validator: check)
28
+ uri = mac.sign "http://example.org/resource?id=1"
29
+
30
+ mac.signed?(uri) #=> true
31
+ mac.signed?(uri, delta: 0) #=> false
32
+
33
+ ## License
34
+
35
+ BSD
@@ -0,0 +1 @@
1
+ require 'hmac/uri'
@@ -0,0 +1,67 @@
1
+ require 'openssl'
2
+ require 'addressable/uri'
3
+
4
+ # HMAC based request signing with uri
5
+
6
+ module HMAC
7
+ class URI
8
+ def initialize options = {}
9
+ @secret = options.fetch(:secret)
10
+ @validator = options.fetch(:validator, method(:default_validator))
11
+ @digest = OpenSSL::Digest::Digest.new('sha1')
12
+ end
13
+
14
+ def sign uri
15
+ uri = merge_query(Addressable::URI.parse(timestamp(uri)), nonce: nonce)
16
+ merge_query(uri, signature: signature(uri))
17
+ end
18
+
19
+ def signed? uri, options = {}
20
+ delta = options.fetch(:delta, 300).to_i
21
+ uri = Addressable::URI.parse(uri)
22
+ query = uri.query_values || {}
23
+ ts = query['timestamp'].to_i
24
+ nonce = query['nonce']
25
+ hmac = query.delete('signature')
26
+ uri = uri.tap {|u| u.query_values = query}
27
+
28
+ validate(nonce, ts, delta) && hmac == signature(uri.to_s)
29
+ end
30
+
31
+ private
32
+
33
+ def default_validator nonce, ts, delta
34
+ nonce.to_i > 0 && valid_timestamp?(ts, delta)
35
+ end
36
+
37
+ def validate nonce, ts, delta
38
+ @validator.call(nonce, ts, delta)
39
+ end
40
+
41
+ def stringy_hash hash
42
+ hash.inject({}) {|a, (k, v)| a.tap {a[k.to_s] = Hash === v ? stringy_hash(v) : v}}
43
+ end
44
+
45
+ def nonce
46
+ (Time.now.to_f.round(6) * 1_000_000).to_i
47
+ end
48
+
49
+ def merge_query uri, hash
50
+ uri.tap do
51
+ uri.query_values = (uri.query_values || {}).merge(stringy_hash(hash))
52
+ end
53
+ end
54
+
55
+ def valid_timestamp? ts, delta
56
+ (Time.now.utc.to_f - ts.to_f).abs < delta
57
+ end
58
+
59
+ def timestamp uri
60
+ merge_query(Addressable::URI.parse(uri), timestamp: Time.now.utc.to_i)
61
+ end
62
+
63
+ def signature message
64
+ OpenSSL::HMAC.hexdigest(@digest, @secret, message.to_s)
65
+ end
66
+ end # URI
67
+ end # HMAC
@@ -0,0 +1,2 @@
1
+ require 'minitest/autorun'
2
+ require 'hmac/uri'
@@ -0,0 +1,43 @@
1
+ require 'helper'
2
+
3
+ describe 'HMAC::URI' do
4
+ OPTIONS = {secret: 'foobar'}
5
+ EXAMPLE_URL = 'http://example.com'
6
+ SIGNED_URI_RE = %r{http://example.com\?nonce=\d+&signature=.+&timestamp=\d+}
7
+
8
+ def signed_url
9
+ HMAC::URI.new(OPTIONS).sign(EXAMPLE_URL)
10
+ end
11
+
12
+ it 'should sign uri' do
13
+ assert_match SIGNED_URI_RE, signed_url
14
+ end
15
+
16
+ it 'should validate signed uri' do
17
+ assert HMAC::URI.new(OPTIONS).signed? signed_url
18
+ end
19
+
20
+ it 'should fail on invalid nonce' do
21
+ url = signed_url.to_s.sub %r{nonce=\d+}, 'nonce=123'
22
+ assert !HMAC::URI.new(OPTIONS).signed?(url), 'invalid nonce should fail check'
23
+ end
24
+
25
+ it 'should fail on invalid timestamp' do
26
+ url = signed_url.to_s.sub %r{timestamp=\d+}, 'timestamp=123'
27
+ assert !HMAC::URI.new(OPTIONS).signed?(url), 'invalid timestamp should fail check'
28
+ end
29
+
30
+ it 'should fail on stale timestamp' do
31
+ assert HMAC::URI.new(OPTIONS).signed?(signed_url, delta: 1), 'valid timestamp should pass check'
32
+ assert !HMAC::URI.new(OPTIONS).signed?(signed_url, delta: 0), 'stale timestamp should fail check'
33
+ end
34
+
35
+ it 'should fail on repeated nonces' do
36
+ seen = []
37
+ url = signed_url
38
+ validator = proc {|n, ts, delta| ((Time.now.to_i - ts) < delta) && !seen.include?(n) && seen << n}
39
+
40
+ assert HMAC::URI.new(OPTIONS.merge(validator: validator)).signed?(url), 'nonce passes'
41
+ assert !HMAC::URI.new(OPTIONS.merge(validator: validator)).signed?(url), 'dupe nonce fails'
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hmac-uri
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bharanee Rathna
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: addressable
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: OpenSSL based HMAC signing for request urls with shared secret
47
+ email:
48
+ - deepfryed@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - test/helper.rb
54
+ - test/test_hmac_uri.rb
55
+ - lib/hmac-uri.rb
56
+ - lib/hmac/uri.rb
57
+ - README.md
58
+ - CHANGELOG
59
+ homepage: http://github.com/deepfryed/hmac-uri
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.24
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: HMAC signing for urls
83
+ test_files: []
84
+ has_rdoc: