authmac 1.0.4 → 2.0.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +51 -1
- data/example/app.rb +16 -6
- data/example/public/app.css +13 -0
- data/example/views/form.erb +2 -1
- data/example/views/sign.erb +18 -11
- data/lib/authmac/hmac_checker.rb +32 -3
- data/lib/authmac/version.rb +1 -1
- data/spec/authmac/hmac_checker_spec.rb +38 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 829ec585cf6a375f48ac8173e6d0ac78b74cbc80
|
4
|
+
data.tar.gz: 1dac8a149c91af398587addbf5b1438d48ca13db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfd1e0b844b368b2c943861f6c1e7c89c11a96149083477ee48dd735cd54a4da6406d414b2dcb801da6519e75d7edb3df567dfca183af97ddaaf5c1ddebc2f11
|
7
|
+
data.tar.gz: 6e2d287aa49a7aad085e83fa85b0d206f877568148c0216f0b66b1b0b6fc6a7875471e249106eac47aee74aee3248a90c18c7da7b173de7ec4b4b40f850d2ead
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## Version 2.0.0 / 2016-11-08
|
2
|
+
|
3
|
+
* BREAKING: keyword arguments for HmacChecker.new
|
4
|
+
* Allow nested hashes/arrays to be signed.
|
5
|
+
* Add :json message_format to sign a sorted json string, instead of just values.
|
6
|
+
|
1
7
|
## Version 1.0.4 / 2014-06-20
|
2
8
|
|
3
9
|
* Added HmacChecker#with_signature which returns the given hash with the HMAC merged in.
|
data/README.md
CHANGED
@@ -21,7 +21,57 @@ Or install it yourself as:
|
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
|
-
|
24
|
+
```
|
25
|
+
checker = Authmac::HmacChecker.new(secret, parameter_separator: '|', digest_function: 'sha1', message_format: :json)
|
26
|
+
checker.with_signature(params_to_send)
|
27
|
+
```
|
28
|
+
|
29
|
+
* message_format: :values or :json. Format of message-string to sign. default :values.
|
30
|
+
* digest_function: string to give `OpenSSL::Digest.new(digest_function)`
|
31
|
+
* parameter_separator: Used for :values format to separate values.
|
32
|
+
|
33
|
+
## Examples
|
34
|
+
|
35
|
+
|
36
|
+
```
|
37
|
+
params_to_sign = {
|
38
|
+
'some_param' => 'foo',
|
39
|
+
'other_param' => 'bar',
|
40
|
+
'timestamp' => timestamp.to_s, # 12345678
|
41
|
+
'nonce' => SecureRandom.urlsafe_base64(4), # ILFtHg
|
42
|
+
'consumer_key' => 'my_key'
|
43
|
+
}
|
44
|
+
```
|
45
|
+
|
46
|
+
Classic syntax: signs values, sorted by key, joined by '|'
|
47
|
+
|
48
|
+
```
|
49
|
+
checker = Authmac::HmacChecker.new(ENV['consumer_secret'],
|
50
|
+
parameter_separator: '|',
|
51
|
+
digest_function: 'sha256')
|
52
|
+
params_to_send = checker.with_signature(params_to_sign)
|
53
|
+
|
54
|
+
checker.send(:message_string, params_to_sign) # "my_key|ILFtHg|bar|foo|123456789"
|
55
|
+
```
|
56
|
+
|
57
|
+
Syntax for more complex parameters, signs json, sorted by key (subhashes as well).
|
58
|
+
|
59
|
+
```
|
60
|
+
params_to_sign['some_param'] = {foo: [1, {bar: 2}]}
|
61
|
+
```
|
62
|
+
|
63
|
+
```
|
64
|
+
checker = Authmac::HmacChecker.new(ENV['consumer_secret'],
|
65
|
+
digest_function: 'sha256',
|
66
|
+
message_format: :json)
|
67
|
+
params_to_send = checker.with_signature(params_to_sign)
|
68
|
+
|
69
|
+
checker.send(:message_string, params_to_sign)
|
70
|
+
# '{"consumer_key":"my_key","nonce":"ILFtHg","other_params":"bar",' \
|
71
|
+
# '"some_param":{"foo":["1",{"bar":"2"}]},"timestamp":"12345678"}'
|
72
|
+
|
73
|
+
```
|
74
|
+
|
25
75
|
|
26
76
|
## Contributing
|
27
77
|
|
data/example/app.rb
CHANGED
@@ -1,25 +1,35 @@
|
|
1
1
|
require 'sinatra'
|
2
2
|
require 'cgi'
|
3
3
|
require 'authmac'
|
4
|
+
require 'json'
|
4
5
|
|
5
6
|
set :app_file, __FILE__
|
7
|
+
def hmac_secret
|
8
|
+
"very_secret_string_of_at_least_the_length_of_the_hash_so_64_for_sha256"
|
9
|
+
end
|
6
10
|
|
7
11
|
get '/' do
|
8
12
|
erb :form
|
9
13
|
end
|
10
14
|
|
11
15
|
post '/sign' do
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
+
@params = params.select { |_k, v| v != '' }
|
17
|
+
@secret = hmac_secret
|
18
|
+
@checker = Authmac::HmacChecker.new(hmac_secret, '|', 'sha256')
|
19
|
+
@params_to_sign = @params.merge \
|
20
|
+
'timestamp' => Time.now.to_i.to_s,
|
21
|
+
'version' => '3',
|
22
|
+
'nonce' => 'implementing_apps_should_store_this_to_prevent_replays',
|
23
|
+
'consumer_key' => 'key_to_find_secret'
|
24
|
+
@hmac = @checker.sign(@params_to_sign)
|
25
|
+
@params_with_hmac = @params_to_sign.merge('hmac' => @hmac)
|
16
26
|
@link = @params_with_hmac.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
|
17
27
|
|
18
28
|
erb :sign
|
19
29
|
end
|
20
30
|
|
21
31
|
get '/auth' do
|
22
|
-
hmac_checker = Authmac::HmacChecker.new(
|
32
|
+
hmac_checker = Authmac::HmacChecker.new(hmac_secret, '|', 'sha256')
|
23
33
|
timestamp_checker = Authmac::TimestampChecker.new(30, 10)
|
24
34
|
authenticator = Authmac::Authenticator.new(hmac_checker, timestamp_checker)
|
25
35
|
@validation = authenticator.validate(params)
|
@@ -31,4 +41,4 @@ get '/auth' do
|
|
31
41
|
elsif @validation.timestamp_failure?
|
32
42
|
erb :auth_timestamp_failure
|
33
43
|
end
|
34
|
-
end
|
44
|
+
end
|
data/example/views/form.erb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
<form method="post" action="/sign">
|
5
5
|
Professional ID: <input type="text" name="userid" /> <br/>
|
6
6
|
Patient ID: <input type="text" name="clientid" /> <br/>
|
7
|
-
|
7
|
+
Professional First Name: <input type="text" name="user_firstname" /> <br />
|
8
|
+
Professional Last Name: <input type="text" name="user_lastname" /> <br />
|
8
9
|
<input type="submit" value="Generate URL" />
|
9
10
|
</form>
|
data/example/views/sign.erb
CHANGED
@@ -1,22 +1,29 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
|
+
<link rel="stylesheet" type="text/css" href="/app.css">
|
3
|
+
|
2
4
|
<title>HMAC Generator</title>
|
3
5
|
<h1>Okay, here's what I've got</h1>
|
4
6
|
|
5
|
-
<p>These are the values we used to generate the hash
|
6
|
-
<code><%= @params
|
7
|
+
<p>These are the values we used to generate the hash:</p>
|
8
|
+
<code><%= JSON.pretty_generate @params %></code>
|
9
|
+
<p class="info">Removed blank params here, we won't send them to the provider, if we had, they should have been in the string_to_sign e.g. "1|2||4".</p>
|
7
10
|
|
8
|
-
<p>Then we add the timestamp parameter
|
9
|
-
<code
|
11
|
+
<p>Then we add the timestamp and other parameter we want:</p>
|
12
|
+
<code>params = <%= JSON.pretty_generate @params_to_sign %></code>
|
13
|
+
<p class="info">timestamp is the only key that is required. <br />
|
14
|
+
version, nonce and consumer_key are app-specific and can be anything you want, just make sure you calculate the hmac over all the params you send.
|
15
|
+
</p>
|
10
16
|
|
11
|
-
<p>To calculate the HMAC, we
|
12
|
-
<code
|
17
|
+
<p>To calculate the HMAC, we sort the params by key alfabetically and join the values with a '|' in between:</p>
|
18
|
+
<code>string_to_sign = <%= @checker.send(:message_string, @params_to_sign) %></code>
|
13
19
|
|
14
|
-
<p>And use
|
15
|
-
<code>
|
20
|
+
<p>And use the configured secret for the HMAC generation:</p>
|
21
|
+
<code>hmac_secret = '<%= @secret %>'</code>
|
16
22
|
|
17
|
-
<p>Then we add the HMAC
|
18
|
-
<code
|
23
|
+
<p>Then we add the HMAC to the params:</p>
|
24
|
+
<code># Authmac::HmacChecker.new(hmac_secret, '|', 'sha256').sign(params)
|
25
|
+
<%= JSON.pretty_generate @params_with_hmac %></code>
|
19
26
|
|
20
|
-
<p>Then, this is a nice URL to use. We URL-escape each value, and keep in mind that the order of HTTP-
|
27
|
+
<p>Then, this is a nice URL to use. We URL-escape each value, and keep in mind that the order of HTTP-methods does not matter:</p>
|
21
28
|
|
22
29
|
<a href="/auth?<%= @link %>">Link</a>
|
data/lib/authmac/hmac_checker.rb
CHANGED
@@ -2,10 +2,16 @@ require 'openssl'
|
|
2
2
|
|
3
3
|
module Authmac
|
4
4
|
class HmacChecker
|
5
|
-
|
5
|
+
# @param message_format [symbol] `:values` or `:json`.
|
6
|
+
# `:json` will use a sorted json string to sign.
|
7
|
+
# `:values` will use the sorted values separated by `parameter_separator` to sign.
|
8
|
+
def initialize(secret, message_format: :values,
|
9
|
+
parameter_separator: '|',
|
10
|
+
digest_function: 'sha1')
|
6
11
|
@secret = secret
|
7
12
|
@digest = digest_function
|
8
13
|
@separator = parameter_separator
|
14
|
+
@message_format = message_format
|
9
15
|
fail Authmac::SecretError, 'secret too short, see rfc2104' unless @secret.bytes.size >= digester.digest_length * 2
|
10
16
|
end
|
11
17
|
|
@@ -28,12 +34,35 @@ module Authmac
|
|
28
34
|
end
|
29
35
|
|
30
36
|
def message_string(hash)
|
31
|
-
|
37
|
+
fail ArgumentError, 'hash arg not a hash' unless hash.is_a? Hash
|
38
|
+
|
39
|
+
case @message_format
|
40
|
+
when :values
|
41
|
+
hash_values_sorted_by_key(hash).flatten.join(@separator)
|
42
|
+
when :json
|
43
|
+
require 'json'
|
44
|
+
JSON.generate(params_sorted_by_key(hash))
|
45
|
+
else
|
46
|
+
fail ArgumentError, 'unknown message_format'
|
47
|
+
end
|
32
48
|
end
|
33
49
|
|
34
50
|
def hash_values_sorted_by_key(hash)
|
35
51
|
hash.sort_by {|key, value| key }.map(&:last)
|
36
52
|
end
|
53
|
+
|
54
|
+
# stringifies and sorts hashes by key at all levels.
|
55
|
+
def params_sorted_by_key(params)
|
56
|
+
case params
|
57
|
+
when Hash
|
58
|
+
params.map { |k, v| [k.to_s, params_sorted_by_key(v)] }
|
59
|
+
.sort_by { |k, v| k }
|
60
|
+
.to_h
|
61
|
+
when Array
|
62
|
+
params.map { |val| params_sorted_by_key(val) }
|
63
|
+
else
|
64
|
+
params
|
65
|
+
end
|
66
|
+
end
|
37
67
|
end
|
38
68
|
end
|
39
|
-
|
data/lib/authmac/version.rb
CHANGED
@@ -2,11 +2,11 @@ require 'authmac/hmac_checker'
|
|
2
2
|
|
3
3
|
module Authmac
|
4
4
|
describe HmacChecker do
|
5
|
-
let(:checker) { HmacChecker.new('very secret random key of sufficient size'
|
5
|
+
let(:checker) { HmacChecker.new('very secret random key of sufficient size') }
|
6
6
|
|
7
7
|
it 'raises an error for a secret shorter than the hmac output' do
|
8
8
|
expect {
|
9
|
-
HmacChecker.new('way too short key'
|
9
|
+
HmacChecker.new('way too short key')
|
10
10
|
}.to raise_error SecretError, 'secret too short, see rfc2104'
|
11
11
|
end
|
12
12
|
|
@@ -45,6 +45,42 @@ module Authmac
|
|
45
45
|
hmacify('parameter|another'))).to be_truthy
|
46
46
|
end
|
47
47
|
end
|
48
|
+
|
49
|
+
context 'with json message_string' do
|
50
|
+
let(:checker) { HmacChecker.new('very secret random key of sufficient size', message_format: :json) }
|
51
|
+
|
52
|
+
it 'creates a sorted json string' do
|
53
|
+
hash = {first: '1', third: '6', second: {b: ['3', {c: '4', d: '5'}], a: '2'}}
|
54
|
+
expect(checker.send :message_string, hash)
|
55
|
+
.to eq '{"first":"1","second":{"a":"2","b":["3",{"c":"4","d":"5"}]},"third":"6"}'
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'for a hash with nested hashes' do
|
59
|
+
it 'succeeds with correct hmac' do
|
60
|
+
expect(checker.validate({first: '1', second: {a: '2', b: '3'}, third: '4'},
|
61
|
+
hmacify('{"first":"1","second":{"a":"2","b":"3"},"third":"4"}'))).to be_truthy
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'sorts hash values based on their keys' do
|
65
|
+
expect(checker.validate({first: '1', third: '4', second: {b: '3', a: '2'}},
|
66
|
+
hmacify('{"first":"1","second":{"a":"2","b":"3"},"third":"4"}'))).to be_truthy
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'for a hash with nested hashes and arrays' do
|
71
|
+
it 'succeeds with correct hmac' do
|
72
|
+
expect(checker.validate({first: '1', second: {a: '2', b: ['3', '4']}, third: '5'},
|
73
|
+
hmacify('{"first":"1","second":{"a":"2","b":["3","4"]},"third":"5"}')))
|
74
|
+
.to be_truthy
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'sorts hash values based on their keys' do
|
78
|
+
expect(checker.validate({first: '1', third: '6', second: {b: ['3', {c: '4', d: '5'}], a: '2'}},
|
79
|
+
hmacify( '{"first":"1","second":{"a":"2","b":["3",{"c":"4","d":"5"}]},"third":"6"}')))
|
80
|
+
.to be_truthy
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
48
84
|
end
|
49
85
|
|
50
86
|
describe '#with_signature' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: authmac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marten Veldthuis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- authmac.gemspec
|
57
57
|
- circle.yml
|
58
58
|
- example/app.rb
|
59
|
+
- example/public/app.css
|
59
60
|
- example/views/auth_hmac_failure.erb
|
60
61
|
- example/views/auth_success.erb
|
61
62
|
- example/views/auth_timestamp_failure.erb
|
@@ -87,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
88
|
version: '0'
|
88
89
|
requirements: []
|
89
90
|
rubyforge_project:
|
90
|
-
rubygems_version: 2.
|
91
|
+
rubygems_version: 2.4.8
|
91
92
|
signing_key:
|
92
93
|
specification_version: 4
|
93
94
|
summary: Single Sign-On implementation based on HMAC.
|
@@ -95,4 +96,3 @@ test_files:
|
|
95
96
|
- spec/authmac/hmac_checker_spec.rb
|
96
97
|
- spec/authmac/timestamp_checker_spec.rb
|
97
98
|
- spec/authmac_spec.rb
|
98
|
-
has_rdoc:
|