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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90105d4d9efdd25c6c15260a90dce6a807b82413
4
- data.tar.gz: 713c8d6560a0f879af99e5ed4d3ba1152fe2dcba
3
+ metadata.gz: 829ec585cf6a375f48ac8173e6d0ac78b74cbc80
4
+ data.tar.gz: 1dac8a149c91af398587addbf5b1438d48ca13db
5
5
  SHA512:
6
- metadata.gz: bac599e7b2238e0b346b8bb3b5dbc4bf2e7243c464a0b4fab90325264f10a4edf4e49ff39162d1c5b28bebafd9b475293936d79cebe5cbda02d22a58dfa2ac12
7
- data.tar.gz: 86f1fa8713e62e8d5ac15cb395bde67d750daddbd339c1b544e5527ce259799ba0d2a53e1b36f618bfbdb4a60d56891113fab3201f34a4334e44395453f856ff
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
- TODO: Write usage instructions here
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
- @checker = Authmac::HmacChecker.new("very_secret", '|', 'sha256')
13
- @params_with_timestamp = params.merge('timestamp' => Time.now.to_i)
14
- @hmac = @checker.sign(@params_with_timestamp)
15
- @params_with_hmac = @params_with_timestamp.merge('hmac' => @hmac)
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("very_secret", '|', 'sha256')
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
@@ -0,0 +1,13 @@
1
+ code {
2
+ white-space: pre;
3
+ display: block;
4
+ background: #eee;
5
+ margin-left: 2em;
6
+ padding: 1em;
7
+ overflow: auto;
8
+ }
9
+
10
+ .info {
11
+ margin-left: 2em;
12
+ font-size: .9em;
13
+ }
@@ -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>
@@ -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.inspect %></code></p>
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><%= @params_with_timestamp.inspect %></code></p>
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 generate the following string based on this:
12
- <code><%= @checker.send(:message_string, @params_with_timestamp) %></code></p>
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 this secret for the HMAC generation:
15
- <code>very_secret</code></p>
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><%= @params_with_hmac.inspect %></code></p>
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-parameters does not matter:</p>
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>
@@ -2,10 +2,16 @@ require 'openssl'
2
2
 
3
3
  module Authmac
4
4
  class HmacChecker
5
- def initialize(secret, parameter_separator = '|', digest_function = 'sha1')
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
- hash_values_sorted_by_key(hash).join(@separator)
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
-
@@ -1,3 +1,3 @@
1
1
  module Authmac
2
- VERSION = '1.0.4'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -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', '|', 'sha1') }
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', '|', 'sha1')
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: 1.0.4
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: 2014-06-20 00:00:00.000000000 Z
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.2.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: