hmac-uri 0.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.
- data/CHANGELOG +3 -0
- data/README.md +35 -0
- data/lib/hmac-uri.rb +1 -0
- data/lib/hmac/uri.rb +67 -0
- data/test/helper.rb +2 -0
- data/test/test_hmac_uri.rb +43 -0
- metadata +84 -0
data/CHANGELOG
ADDED
data/README.md
ADDED
@@ -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
|
data/lib/hmac-uri.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'hmac/uri'
|
data/lib/hmac/uri.rb
ADDED
@@ -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
|
data/test/helper.rb
ADDED
@@ -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=.+×tamp=\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:
|