authmac 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 79cdcf6928f30abe694de994eba88c6f2ec89e28
4
+ data.tar.gz: 62c7ba521390202bb2d61b28d183be513060a1b4
5
+ SHA512:
6
+ metadata.gz: a7faef3549f1033ed067aa165c1fbe6c9c2e6572d0d4564f5fbd83ccadaed50bc9c2f0922ff55a23d546ada894ef0fb025cba7e0e9536b8adca8a3d68427e712
7
+ data.tar.gz: 486d280fccd12f4127caef2fe1c65982cd7b9d721ef350aa2224ae82e6526ca951ed51caa4655350ce14e4a8eee88144f46c243d04a3482da31f320028ebda56
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in authmac.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Marten Veldthuis
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Authmac
2
+
3
+ [![Build Status](https://travis-ci.org/roqua/authmac.png)](https://travis-ci.org/roqua/authmac)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/roqua/authmac)
5
+
6
+ TODO: Write a gem description
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'authmac'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install authmac
21
+
22
+ ## Usage
23
+
24
+ TODO: Write usage instructions here
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
data/authmac.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'authmac/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "authmac"
8
+ gem.version = Authmac::VERSION
9
+ gem.authors = ["Marten Veldthuis"]
10
+ gem.email = ["marten@veldthuis.com"]
11
+ gem.description = 'Single Sign-On implementation based on HMAC.'
12
+ gem.summary = 'Single Sign-On implementation based on HMAC.'
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "rake", "~> 10.0.3"
21
+ gem.add_development_dependency "rspec", "~> 2.11"
22
+ end
data/example/app.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'sinatra'
2
+ require 'cgi'
3
+ require 'authmac'
4
+
5
+ set :app_file, __FILE__
6
+
7
+ get '/' do
8
+ erb :form
9
+ end
10
+
11
+ 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
+ @link = @params_with_hmac.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
17
+
18
+ erb :sign
19
+ end
20
+
21
+ get '/auth' do
22
+ hmac_checker = Authmac::HmacChecker.new("very_secret", '|', 'sha256')
23
+ timestamp_checker = Authmac::TimestampChecker.new(30, 10)
24
+ authenticator = Authmac::Authenticator.new(hmac_checker, timestamp_checker)
25
+ @validation = authenticator.validate(params)
26
+
27
+ if @validation.success?
28
+ erb :auth_success
29
+ elsif @validation.hmac_failure?
30
+ erb :auth_hmac_failure
31
+ elsif @validation.timestamp_failure?
32
+ erb :auth_timestamp_failure
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ <!DOCTYPE html>
2
+ <title>HMAC Generator</title>
3
+
4
+ <h1>HMAC failure</h1>
5
+ <p>The HMAC you passed did not seem to be correct. For your reference, we use the secret <pre>very_secret</pre> and used the message <pre><%= message %></pre>.</p>
@@ -0,0 +1,5 @@
1
+ <!DOCTYPE html>
2
+ <title>Success!</title>
3
+
4
+ <h1>Success!</h1>
5
+ <p>Everything seems to be in order.</p>
@@ -0,0 +1,5 @@
1
+ <!DOCTYPE html>
2
+ <title>HMAC Generator</title>
3
+
4
+ <h1>Timestamp expired</h1>
5
+ <p>In this demo application, we've set the timestamp limits to expire after 30 seconds, and not to allow timestamps that are more than 10 seconds in the future.</p>
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <title>HMAC Generator</title>
3
+ <style>body { font-size: 1.2em; }</style>
4
+ <form method="post" action="/sign">
5
+ Professional ID: <input type="text" name="userid" /> <br/>
6
+ Patient ID: <input type="text" name="clientid" /> <br/>
7
+
8
+ <input type="submit" value="Generate URL" />
9
+ </form>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <title>HMAC Generator</title>
3
+ <h1>Okay, here's what I've got</h1>
4
+
5
+ <p>These are the values we used to generate the hash:
6
+ <code><%= @params.inspect %></code></p>
7
+
8
+ <p>Then we add the timestamp parameter:
9
+ <code><%= @params_with_timestamp.inspect %></code></p>
10
+
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>
13
+
14
+ <p>And use this secret for the HMAC generation:
15
+ <code>very_secret</code></p>
16
+
17
+ <p>Then we add the HMAC:
18
+ <code><%= @params_with_hmac.inspect %></code></p>
19
+
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>
21
+
22
+ <a href="/auth?<%= @link %>">Link</a>
@@ -0,0 +1,34 @@
1
+ require 'openssl'
2
+
3
+ module Authmac
4
+ class HmacChecker
5
+ def initialize(secret, parameter_separator = '|', digest_function = "sha1")
6
+ @secret = secret
7
+ @digest = digest_function
8
+ @separator = parameter_separator
9
+ end
10
+
11
+ def validate(hash, given_hmac)
12
+ sign(hash) == given_hmac
13
+ end
14
+
15
+ def sign(hash)
16
+ OpenSSL::HMAC.hexdigest(digester, @secret, message_string(hash))
17
+ end
18
+
19
+ private
20
+
21
+ def digester
22
+ OpenSSL::Digest::Digest.new(@digest)
23
+ end
24
+
25
+ def message_string(hash)
26
+ hash_values_sorted_by_key(hash).join(@separator)
27
+ end
28
+
29
+ def hash_values_sorted_by_key(hash)
30
+ hash.sort_by {|key, value| key }.map(&:last)
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,22 @@
1
+ module Authmac
2
+ class TimestampChecker
3
+ def initialize(max_behind, max_ahead)
4
+ @max_behind = max_behind
5
+ @max_ahead = max_ahead
6
+ end
7
+
8
+ def validate(timestamp)
9
+ not_too_old(timestamp) and not_too_new(timestamp)
10
+ end
11
+
12
+ private
13
+
14
+ def not_too_old(timestamp)
15
+ (Time.now - @max_behind) <= Time.at(timestamp)
16
+ end
17
+
18
+ def not_too_new(timestamp)
19
+ Time.at(timestamp) <= (Time.now + @max_ahead)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Authmac
2
+ VERSION = "1.0.0"
3
+ end
data/lib/authmac.rb ADDED
@@ -0,0 +1,61 @@
1
+ require "authmac/version"
2
+ require 'authmac/hmac_checker'
3
+ require 'authmac/timestamp_checker'
4
+
5
+ module Authmac
6
+ class HmacError < StandardError; end
7
+ class TimestampError < StandardError; end
8
+
9
+ class ValidationResult
10
+ def initialize(options = {})
11
+ @hmac = options.fetch(:hmac)
12
+ @timestamp = options.fetch(:timestamp)
13
+ end
14
+
15
+ def success?
16
+ @hmac and @timestamp
17
+ end
18
+
19
+ def failure?
20
+ !success?
21
+ end
22
+
23
+ def hmac_failure?
24
+ !@hmac
25
+ end
26
+
27
+ def timestamp_failure?
28
+ !@timestamp
29
+ end
30
+ end
31
+
32
+ class Authenticator
33
+ def initialize(hmac_checker, timestamp_checker)
34
+ @hmac_checker = hmac_checker
35
+ @timestamp_checker = timestamp_checker
36
+ end
37
+
38
+ def validate(params)
39
+ ValidationResult.new(hmac: validate_hmac(params),
40
+ timestamp: validate_timestamp(params))
41
+ end
42
+
43
+ private
44
+
45
+ def validate_hmac(params)
46
+ hash, hmac = split_params(params)
47
+ @hmac_checker.validate(hash, hmac)
48
+ end
49
+
50
+ def validate_timestamp(params)
51
+ timestamp = params[:timestamp].to_i
52
+ @timestamp_checker.validate(timestamp)
53
+ end
54
+
55
+ def split_params(params)
56
+ hash = params.reduce({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
57
+ hmac = hash.delete(:hmac)
58
+ return [hash, hmac]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ require 'authmac/hmac_checker'
2
+
3
+ module Authmac
4
+ describe HmacChecker do
5
+ let(:checker) { HmacChecker.new("very secret key", "|", "sha1") }
6
+
7
+ describe '#validate' do
8
+ context 'for an empty hash' do
9
+ let(:hash) { Hash.new }
10
+
11
+ it 'succeeds with the correct hmac' do
12
+ checker.validate(hash, hmacify('')).should be_true
13
+ end
14
+
15
+ it 'fails with an incorrect hmac' do
16
+ checker.validate(hash, "wrong").should be_false
17
+ end
18
+ end
19
+
20
+ context 'for a hash with a single parameter' do
21
+ it 'succeeds with the correct hmac' do
22
+ checker.validate({single: 'parameter'}, hmacify("parameter")).should be_true
23
+ end
24
+
25
+ it 'fails with incorrect hmac' do
26
+ checker.validate({single: 'parameter'}, 'wrong').should be_false
27
+ end
28
+ end
29
+
30
+ context 'for a hash with multiple parameters' do
31
+ it 'succeeds with correct hmac' do
32
+ checker.validate({first: 'parameter', second: 'another'},
33
+ hmacify('parameter|another')).should be_true
34
+ end
35
+
36
+ it 'sorts hash values based on their keys' do
37
+ checker.validate({second: 'another', first: 'parameter'},
38
+ hmacify('parameter|another')).should be_true
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#calculate_hmac' do
45
+ it 'generates hmac' do
46
+ checker.sign(second: 'another', first: 'parameter').should == hmacify('parameter|another')
47
+ end
48
+ end
49
+
50
+ def hmacify(string, method='sha1')
51
+ digester = OpenSSL::Digest::Digest.new(method)
52
+ OpenSSL::HMAC.hexdigest(digester, "very secret key", string)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ require 'authmac/timestamp_checker'
2
+
3
+ module Authmac
4
+ describe TimestampChecker do
5
+ let(:checker) { TimestampChecker.new(15*60, 5*60) }
6
+
7
+ it 'returns true if timestamp is recent' do
8
+ checker.validate(Time.now.to_i).should be_true
9
+ end
10
+
11
+ it 'returns false if timestamp is too old' do
12
+ checker.validate(Time.now.to_i - (15*60 + 1)).should be_false
13
+ end
14
+
15
+ it 'returns false if timestamp is too far in the future' do
16
+ checker.validate(Time.now.to_i + (5*60 + 1)).should be_false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ require 'authmac'
2
+
3
+ module Authmac
4
+ describe Authenticator do
5
+ let(:hmac_checker) { stub("HmacChecker", validate: true) }
6
+ let(:timestamp_checker) { stub("TimestampChecker", validate: true) }
7
+ let(:auth) { Authenticator.new(hmac_checker, timestamp_checker) }
8
+
9
+ describe '#validate' do
10
+ it 'checks hmac' do
11
+ hash = {userid: 'someone', clientid: 'something'}
12
+ hmac = "a-calculated-hmac"
13
+ hmac_checker.should_receive(:validate).with(hash, hmac)
14
+ auth.validate(hash.merge(hmac: hmac))
15
+ end
16
+
17
+ it 'raises HmacError if hmac is incorrect' do
18
+ hmac_checker.stub(validate: false)
19
+ auth.validate({}).hmac_failure?.should be_true
20
+ end
21
+
22
+ it 'checks timestamp' do
23
+ timestamp = Time.now.to_i
24
+ timestamp_checker.should_receive(:validate).with(timestamp)
25
+ auth.validate({timestamp: timestamp.to_s})
26
+ end
27
+
28
+ it 'raises TimestampError if timestamp is out of bounds' do
29
+ timestamp_checker.stub(validate: false)
30
+ auth.validate({}).timestamp_failure?.should be_true
31
+ end
32
+ end
33
+
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authmac
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Marten Veldthuis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 10.0.3
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 10.0.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.11'
41
+ description: Single Sign-On implementation based on HMAC.
42
+ email:
43
+ - marten@veldthuis.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - authmac.gemspec
55
+ - example/app.rb
56
+ - example/views/auth_hmac_failure.erb
57
+ - example/views/auth_success.erb
58
+ - example/views/auth_timestamp_failure.erb
59
+ - example/views/form.erb
60
+ - example/views/sign.erb
61
+ - lib/authmac.rb
62
+ - lib/authmac/hmac_checker.rb
63
+ - lib/authmac/timestamp_checker.rb
64
+ - lib/authmac/version.rb
65
+ - spec/authmac/hmac_checker_spec.rb
66
+ - spec/authmac/timestamp_checker_spec.rb
67
+ - spec/authmac_spec.rb
68
+ homepage: ''
69
+ licenses: []
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.2.0
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Single Sign-On implementation based on HMAC.
91
+ test_files:
92
+ - spec/authmac/hmac_checker_spec.rb
93
+ - spec/authmac/timestamp_checker_spec.rb
94
+ - spec/authmac_spec.rb