duo-auth 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 197075825fc8cad240329986e5dfc0fc1b99eec2
4
+ data.tar.gz: 9971f8e9c251e8b6071daf03b9e0a131a57a68fa
5
+ SHA512:
6
+ metadata.gz: b86471b1b9abe9e9aa1e427ce24adf009b7e80367ec3660ac0e4512e27539a48acc7ded786cf2b7af7d899b9a00bb142b2bbb714abfb267645a33407958dcd2f
7
+ data.tar.gz: ef721a90532f9d740fd185dbee9e83f8fae7b1cea115ccbaf6898b28145683a82675c46f28799e392831530e859b4327174603a81aa68d5619aa702efa98097d
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in duo-auth.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Christopher Ostrowski
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.
@@ -0,0 +1,65 @@
1
+ ![](http://techjeeper.com/wp-content/uploads/2013/08/Wordmark-Duo.png)
2
+
3
+ # DUO Auth
4
+
5
+ This gem provides the ability to create HMAC login verification requests with the DUO 2-factor server.
6
+ The code is taken from https://github.com/duosecurity/duo_ruby with some minor refactorings and gemification.
7
+
8
+ ## Compatibilty
9
+ This gem is up-to-date with `bb77567c838d77bce4f501b475d28dade300edcd` from https://github.com/duosecurity/duo_ruby.
10
+ This covers `DUO-PSA-2015-001`.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'duo-auth'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install duo-auth
27
+
28
+ ## Usage
29
+
30
+ ### Create a Signed Request
31
+
32
+ To create a signed request to be passed to the DUO servers use the follow singleton method:
33
+ ```ruby
34
+ Duo::Auth.sign_request(ENV[:duo_ikey], ENV[:duo_skey], Rails::Application.config.secret_token, @user.id)
35
+ ```
36
+
37
+ The sign request method takes 4 arguments:
38
+
39
+ 1. Your DUO Integration Key.
40
+ 2. Your DUO Secret Key.
41
+ 3. An application secret token. Any secret token with a minimum of 40 characters will do, but if you're using rails, the default secret token will work great.
42
+ 4. Your user identifier. This is whatever you are using on DUO to track your users. This is also returned from the verify request step.
43
+
44
+ ### Verify a DUO Response
45
+
46
+ To verify the returned DUO response after 2-factor has been verified use the following singleton:
47
+ ```ruby
48
+ Duo::Auth.verify_response(ENV[:duo_ikey], ENV[:duo_skey], Rails::Application.config.secret_token, params[:sig_response])
49
+ ```
50
+ The verify response method takes 4 arguments:
51
+
52
+ 1. Your DUO Integration Key.
53
+ 2. Your DUO Secret Key.
54
+ 3. An application secret token..
55
+ 4. The response from DUO. This is typically posted back to your application after authentication is complete.
56
+
57
+ The `verify_response` method will return the user identifier that was passed to the `sign_request` method if the verification was successful.
58
+
59
+ ## Contributing
60
+
61
+ 1. Fork it ( https://github.com/TheKidCoder/duo-auth/fork )
62
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
63
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
64
+ 4. Push to the branch (`git push origin my-new-feature`)
65
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'duo/auth/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "duo-auth"
8
+ spec.version = Duo::Auth::VERSION
9
+ spec.authors = ["Christopher Ostrowski"]
10
+ spec.email = ["chris@madebyfunction.com"]
11
+ spec.summary = %q{Sign & Verify 2-factor auth requests using DUO Security.}
12
+ spec.description = %q{This is a minimal gem that will provide the ability to sign & verify a duo request. Doesn't include any view layer.}
13
+ spec.homepage = "http://github.com/thekidcoder/duo-auth"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,98 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require "duo/auth/version"
4
+
5
+ module Duo
6
+ DUO_PREFIX = 'TX'
7
+ APP_PREFIX = 'APP'
8
+ AUTH_PREFIX = 'AUTH'
9
+
10
+ DUO_EXPIRE = 300
11
+ APP_EXPIRE = 3600
12
+
13
+ IKEY_LEN = 20
14
+ SKEY_LEN = 40
15
+ AKEY_LEN = 40
16
+
17
+ ERR_USER = 'ERR|The username passed to sign_request() is invalid.'
18
+ ERR_IKEY = 'ERR|The Duo integration key passed to sign_request() is invalid.'
19
+ ERR_SKEY = 'ERR|The Duo secret key passed to sign_request() is invalid.'
20
+ ERR_AKEY = "ERR|The application secret key passed to sign_request() must be at least #{Duo::AKEY_LEN} characters."
21
+
22
+ module Auth
23
+ class << self
24
+ # Sign a Duo request with the ikey, skey, akey, and username
25
+ def sign_request(ikey, skey, akey, username)
26
+ return Duo::ERR_USER if not username or username.to_s.length == 0
27
+ return Duo::ERR_USER if username.include? '|'
28
+ return Duo::ERR_IKEY if not ikey or ikey.to_s.length != Duo::IKEY_LEN
29
+ return Duo::ERR_SKEY if not skey or skey.to_s.length != Duo::SKEY_LEN
30
+ return Duo::ERR_AKEY if not akey or akey.to_s.length < Duo::AKEY_LEN
31
+
32
+ vals = [username, ikey]
33
+
34
+ duo_sig = sign_vals(skey, vals, Duo::DUO_PREFIX, Duo::DUO_EXPIRE)
35
+ app_sig = sign_vals(akey, vals, Duo::APP_PREFIX, Duo::APP_EXPIRE)
36
+
37
+ return [duo_sig, app_sig].join(':')
38
+ end
39
+
40
+ # Verify a response from Duo with the skey and akey.
41
+ def verify_response(ikey, skey, akey, sig_response)
42
+ begin
43
+ auth_sig, app_sig = sig_response.to_s.split(':')
44
+ auth_user = parse_vals(skey, auth_sig, Duo::AUTH_PREFIX, ikey)
45
+ app_user = parse_vals(akey, app_sig, Duo::APP_PREFIX, ikey)
46
+ rescue
47
+ return nil
48
+ end
49
+
50
+ return nil if auth_user != app_user
51
+
52
+ return auth_user
53
+ end
54
+
55
+ private
56
+ def hmac_sha1(key, data)
57
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), key, data.to_s)
58
+ end
59
+
60
+ def sign_vals(key, vals, prefix, expire)
61
+ exp = Time.now.to_i + expire
62
+
63
+ val_list = vals + [exp]
64
+ val = val_list.join('|')
65
+
66
+ b64 = Base64.encode64(val).gsub(/\n/,'')
67
+ cookie = prefix + '|' + b64
68
+
69
+ sig = hmac_sha1(key, cookie)
70
+ return [cookie, sig].join('|')
71
+ end
72
+
73
+ def parse_vals(key, val, prefix, ikey)
74
+ ts = Time.now.to_i
75
+
76
+ parts = val.to_s.split('|')
77
+ return nil if parts.length !=3
78
+ u_prefix, u_b64, u_sig = parts
79
+
80
+ sig = hmac_sha1(key, [u_prefix, u_b64].join('|'))
81
+
82
+ return nil if hmac_sha1(key, sig) != hmac_sha1(key, u_sig)
83
+
84
+ return nil if u_prefix != prefix
85
+
86
+ cookie_parts = Base64.decode64(u_b64).to_s.split('|')
87
+ return nil if cookie_parts.length != 3
88
+ user, u_ikey, exp = cookie_parts
89
+
90
+ return nil if u_ikey != ikey
91
+
92
+ return nil if ts >= exp.to_i
93
+
94
+ return user
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ module Duo
2
+ module Auth
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duo::Auth do
4
+ context 'signing request' do
5
+ subject {Duo::Auth}
6
+ it 'creates a proper response with valid arguments' do
7
+ expect(subject.sign_request(IKEY, SKEY, AKEY, USER)).not_to be_nil
8
+ end
9
+
10
+ it 'expects a user ID' do
11
+ expect(subject.sign_request(IKEY, SKEY, AKEY, '')).to be(Duo::ERR_USER)
12
+ end
13
+
14
+ it 'cannot have a pipe character in the user key' do
15
+ expect(subject.sign_request(IKEY, SKEY, AKEY, 'in|valid')).to be(Duo::ERR_USER)
16
+ end
17
+
18
+ it 'expects a valid integration key' do
19
+ expect(subject.sign_request('invalid', SKEY, AKEY, USER)).to be(Duo::ERR_IKEY)
20
+ end
21
+
22
+ it 'expects a valid secret key' do
23
+ expect(subject.sign_request(IKEY, 'invalid', AKEY, USER)).to be(Duo::ERR_SKEY)
24
+ end
25
+
26
+ it 'expects a valid application key' do
27
+ expect(subject.sign_request(IKEY, SKEY, 'invalid', USER)).to be(Duo::ERR_AKEY)
28
+ end
29
+ end
30
+
31
+ context 'verifying a response' do
32
+ let(:request_sig) {Duo::Auth.sign_request(IKEY, SKEY, AKEY, USER)}
33
+ let(:valid_app_sig) {request_sig.to_s.split(':').last}
34
+
35
+ let(:invalid_request_sig) {Duo::Auth.sign_request(IKEY, SKEY, 'invalid' * 6, USER)}
36
+ let(:invalid_app_sig) {invalid_request_sig.to_s.split(':').last}
37
+
38
+ it 'rejects an invalid user' do
39
+ expect(Duo::Auth.verify_response(IKEY, SKEY, AKEY, INVALID_RESPONSE + ':' + valid_app_sig)).to be_nil
40
+ end
41
+
42
+ it 'rejects expired responses' do
43
+ expect(Duo::Auth.verify_response(IKEY, SKEY, AKEY, EXPIRED_RESPONSE + ':' + valid_app_sig)).to be_nil
44
+ end
45
+
46
+ it 'rejects responses with invalid app sig' do
47
+ expect(Duo::Auth.verify_response(IKEY, SKEY, AKEY, FUTURE_RESPONSE + ':' + invalid_app_sig)).to be_nil
48
+ end
49
+
50
+ it 'allows responses with valid app sig' do
51
+ expect(Duo::Auth.verify_response(IKEY, SKEY, AKEY, FUTURE_RESPONSE + ':' + valid_app_sig)).to eq(USER)
52
+ end
53
+
54
+ it 'rejects wrong params' do
55
+ expect(Duo::Auth.verify_response(IKEY, SKEY, AKEY, FUTURE_RESPONSE + ':' + WRONG_PARAMS_APP)).to be_nil
56
+ end
57
+
58
+ it 'rejects wrong params from response' do
59
+ expect(Duo::Auth.verify_response(IKEY, SKEY, AKEY, WRONG_PARAMS_RESPONSE + ':' + valid_app_sig)).to be_nil
60
+ end
61
+
62
+ it 'will not verify with wrong integration key' do
63
+ expect(Duo::Auth.verify_response(WRONG_IKEY, SKEY, AKEY, FUTURE_RESPONSE + ':' + valid_app_sig)).to be_nil
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ require_relative '../lib/duo/auth'
2
+
3
+ IKEY = "DIXXXXXXXXXXXXXXXXXX"
4
+ WRONG_IKEY = "DIXXXXXXXXXXXXXXXXXY"
5
+ SKEY = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
6
+ AKEY = "useacustomerprovidedapplicationsecretkey"
7
+
8
+ USER = "testuser"
9
+
10
+ INVALID_RESPONSE = "AUTH|INVALID|SIG"
11
+ EXPIRED_RESPONSE = "AUTH|dGVzdHVzZXJ8RElYWFhYWFhYWFhYWFhYWFhYWFh8MTMwMDE1Nzg3NA==|cb8f4d60ec7c261394cd5ee5a17e46ca7440d702"
12
+ FUTURE_RESPONSE = "AUTH|dGVzdHVzZXJ8RElYWFhYWFhYWFhYWFhYWFhYWFh8MTYxNTcyNzI0Mw==|d20ad0d1e62d84b00a3e74ec201a5917e77b6aef"
13
+ WRONG_PARAMS_RESPONSE = "AUTH|dGVzdHVzZXJ8RElYWFhYWFhYWFhYWFhYWFhYWFh8MTYxNTcyNzI0M3xpbnZhbGlkZXh0cmFkYXRh|6cdbec0fbfa0d3f335c76b0786a4a18eac6cdca7"
14
+ WRONG_PARAMS_APP = "APP|dGVzdHVzZXJ8RElYWFhYWFhYWFhYWFhYWFhYWFh8MTYxNTcyNzI0M3xpbnZhbGlkZXh0cmFkYXRh|7c2065ea122d028b03ef0295a4b4c5521823b9b5"
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: duo-auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Ostrowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: This is a minimal gem that will provide the ability to sign & verify
56
+ a duo request. Doesn't include any view layer.
57
+ email:
58
+ - chris@madebyfunction.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - duo-auth.gemspec
69
+ - lib/duo/auth.rb
70
+ - lib/duo/auth/version.rb
71
+ - spec/auth_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: http://github.com/thekidcoder/duo-auth
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.4.5
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Sign & Verify 2-factor auth requests using DUO Security.
97
+ test_files:
98
+ - spec/auth_spec.rb
99
+ - spec/spec_helper.rb