authmac 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: