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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +5 -0
- data/authmac.gemspec +22 -0
- data/example/app.rb +34 -0
- data/example/views/auth_hmac_failure.erb +5 -0
- data/example/views/auth_success.erb +5 -0
- data/example/views/auth_timestamp_failure.erb +5 -0
- data/example/views/form.erb +9 -0
- data/example/views/sign.erb +22 -0
- data/lib/authmac/hmac_checker.rb +34 -0
- data/lib/authmac/timestamp_checker.rb +22 -0
- data/lib/authmac/version.rb +3 -0
- data/lib/authmac.rb +61 -0
- data/spec/authmac/hmac_checker_spec.rb +55 -0
- data/spec/authmac/timestamp_checker_spec.rb +19 -0
- data/spec/authmac_spec.rb +35 -0
- metadata +94 -0
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
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
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
|
+
[](https://travis-ci.org/roqua/authmac)
|
4
|
+
[](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
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,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
|
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
|