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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +2 -0
- data/duo-auth.gemspec +24 -0
- data/lib/duo/auth.rb +98 -0
- data/lib/duo/auth/version.rb +5 -0
- data/spec/auth_spec.rb +67 -0
- data/spec/spec_helper.rb +14 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+

|
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
|
data/Rakefile
ADDED
data/duo-auth.gemspec
ADDED
@@ -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
|
data/lib/duo/auth.rb
ADDED
@@ -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
|
data/spec/auth_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|