yubioath 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +15 -1
- data/.rubocop_todo.yml +16 -0
- data/Gemfile +9 -0
- data/LICENSE.markdown +21 -0
- data/README.md +49 -14
- data/lib/yubioath.rb +20 -8
- data/lib/yubioath/list.rb +3 -1
- data/lib/yubioath/put.rb +2 -5
- data/lib/yubioath/response.rb +2 -2
- data/yubioath.gemspec +2 -9
- metadata +7 -64
- data/exe/yubioath +0 -4
- data/lib/card.rb +0 -36
- data/lib/cli.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f3d7712ca20e86423b1dd55b26c6e8390bd9c08
|
4
|
+
data.tar.gz: e81afd263ba2466f972568e4fe199bd9b9237b5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 903e5115d614d3cc777f6a2ea46a66561bdb2c7be8bebccc653a695c36449b970ff00dd56eed04bbdc879d18b34fb39ab0ffe36a18ed03798c5db3b45bca37b7
|
7
|
+
data.tar.gz: 34215cf74eb5316b6728136df0106a0c74ec5610fc0e7c2bb5b643d7a3b1c66b45217f749c631f8303378c1ec7b3d36b85d894f94b31793381a606f10bf186ff
|
data/.rspec
ADDED
data/.rubocop.yml
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
1
3
|
AllCops:
|
2
4
|
Exclude:
|
3
5
|
- 'yubioath.gemspec'
|
@@ -8,5 +10,17 @@ Metrics/LineLength:
|
|
8
10
|
Style/Documentation:
|
9
11
|
Enabled: false
|
10
12
|
|
11
|
-
|
13
|
+
Style/SpaceAroundOperators:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Style/SpaceInsideHashLiteralBraces:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/TrailingComma:
|
20
|
+
EnforcedStyleForMultiline: comma
|
21
|
+
|
22
|
+
Style/BracesAroundHashParameters:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Style/IndentHash:
|
12
26
|
Enabled: false
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2015-07-04 16:09:47 +1000 using RuboCop version 0.29.1.
|
3
|
+
# The point is for the user to remove these configuration records
|
4
|
+
# one by one as the offenses are removed from the code base.
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
7
|
+
|
8
|
+
# Offense count: 2
|
9
|
+
Metrics/AbcSize:
|
10
|
+
Max: 19
|
11
|
+
|
12
|
+
# Offense count: 6
|
13
|
+
# Cop supports --auto-correct.
|
14
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
15
|
+
Style/SignalException:
|
16
|
+
Enabled: false
|
data/Gemfile
CHANGED
@@ -4,5 +4,14 @@ source 'https://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :development do
|
7
|
+
gem 'bundler'
|
8
|
+
gem 'rake'
|
7
9
|
gem 'rubocop'
|
8
10
|
end
|
11
|
+
|
12
|
+
group :test do
|
13
|
+
gem 'rspec', '~> 3.3'
|
14
|
+
gem 'rspec-its', '~> 1.2'
|
15
|
+
gem 'rspec-the', '~> 1.0'
|
16
|
+
gem 'smartcard', '~> 0.5.5'
|
17
|
+
end
|
data/LICENSE.markdown
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015, James Ottaway
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,26 +1,61 @@
|
|
1
1
|
# YubiOATH
|
2
2
|
|
3
|
-
|
3
|
+
A mostly-complete Ruby implementation of the [YubiOATH applet protocol](https://developers.yubico.com/ykneo-oath/Protocol.html).
|
4
4
|
|
5
|
-
##
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
The `YubiOATH` class accepts a `card`, which must respond to `#transmit(apdu)`.
|
6
8
|
|
7
|
-
|
9
|
+
You probably want to use [costan/smartcard](https://github.com/costan/smartcard).
|
8
10
|
|
11
|
+
```ruby
|
12
|
+
yubioath = YubiOATH.new(card)
|
9
13
|
```
|
10
|
-
|
14
|
+
|
15
|
+
### Calculate
|
16
|
+
|
17
|
+
Do calculate for one named code.
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
yubioath.calculate(name: 'foo', timestamp: Time.now) # => '237893'
|
11
21
|
```
|
12
22
|
|
13
|
-
|
23
|
+
### Calculate All
|
14
24
|
|
15
|
-
|
25
|
+
Do calculation for all available codes.
|
16
26
|
|
27
|
+
``` ruby
|
28
|
+
yubioath.calculate_all(timestamp: Time.now) # => { 'foo' => '576238', 'bar' => '123895' }
|
17
29
|
```
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
30
|
+
|
31
|
+
### Delete
|
32
|
+
|
33
|
+
Deletes an existing code.
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
yubioath.delete(name: 'foo') # => true
|
37
|
+
```
|
38
|
+
|
39
|
+
### List
|
40
|
+
|
41
|
+
List configured codes.
|
42
|
+
|
43
|
+
``` ruby
|
44
|
+
yubioath.list # => { 'foo' => { type: :totp, algorithm: :sha256 } }
|
45
|
+
```
|
46
|
+
|
47
|
+
### Put
|
48
|
+
|
49
|
+
Adds a new (or overwrites) OATH code.
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
yubioath.put(name: 'foo', secret: 'bar', …) # => true
|
53
|
+
```
|
54
|
+
|
55
|
+
### Reset
|
56
|
+
|
57
|
+
Reset the applet to just-installed state.
|
58
|
+
|
59
|
+
``` ruby
|
60
|
+
yubioath.reset # => true
|
26
61
|
```
|
data/lib/yubioath.rb
CHANGED
@@ -4,6 +4,10 @@ require 'yubioath/response'
|
|
4
4
|
|
5
5
|
class YubiOATH
|
6
6
|
AID = [0xA0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
|
7
|
+
ALGORITHMS = { sha1: 0x1, sha256: 0x2 }
|
8
|
+
TYPES = { hotp: 0x1, totp: 0x2 }
|
9
|
+
|
10
|
+
RequestFailed = Class.new(StandardError)
|
7
11
|
|
8
12
|
def initialize(card)
|
9
13
|
@card = card
|
@@ -14,16 +18,18 @@ class YubiOATH
|
|
14
18
|
data = Calculate::Request::Data.new(name: name, timestamp: timestamp.to_i / 30)
|
15
19
|
request = Calculate::Request.new(data: data.to_binary_s)
|
16
20
|
response = Response.read(@card.transmit(request.to_binary_s))
|
17
|
-
|
18
|
-
Calculate::Response.read(response.data)
|
21
|
+
raise RequestFailed, response unless response.success?
|
22
|
+
Calculate::Response.read(response.data).code.to_s
|
19
23
|
end
|
20
24
|
|
21
25
|
def calculate_all(timestamp:)
|
22
26
|
data = CalculateAll::Request::Data.new(timestamp: timestamp.to_i / 30)
|
23
27
|
request = CalculateAll::Request.new(data: data.to_binary_s)
|
24
28
|
response = Response.read(@card.transmit(request.to_binary_s))
|
25
|
-
|
26
|
-
CalculateAll::Response.read(response.data)
|
29
|
+
raise RequestFailed, response unless response.success?
|
30
|
+
CalculateAll::Response.read(response.data)[:codes].map do |code|
|
31
|
+
[code.name, code.code.to_s]
|
32
|
+
end.to_h
|
27
33
|
end
|
28
34
|
|
29
35
|
def delete(name:)
|
@@ -35,14 +41,20 @@ class YubiOATH
|
|
35
41
|
def list
|
36
42
|
request = List::Request.new.to_binary_s
|
37
43
|
response = Response.read(@card.transmit(request))
|
38
|
-
|
39
|
-
List::Response.read(response.data)
|
44
|
+
raise RequestFailed, response unless response.success?
|
45
|
+
List::Response.read(response.data)[:codes].map do |code|
|
46
|
+
[code.name, {
|
47
|
+
type: TYPES.key(code.type),
|
48
|
+
algorithm: ALGORITHMS.key(code.algorithm),
|
49
|
+
}]
|
50
|
+
end.to_h
|
40
51
|
end
|
41
52
|
|
42
53
|
def put(name:, secret:, algorithm:, type:, digits:)
|
43
54
|
data = Put::Request::Data.new(
|
44
55
|
name: name,
|
45
|
-
|
56
|
+
type: TYPES.fetch(type),
|
57
|
+
algorithm: ALGORITHMS.fetch(algorithm),
|
46
58
|
digits: digits,
|
47
59
|
secret: secret,
|
48
60
|
)
|
@@ -59,7 +71,7 @@ class YubiOATH
|
|
59
71
|
def select(aid)
|
60
72
|
request = Select::Request.new(aid: aid).to_binary_s
|
61
73
|
response = Response.read(@card.transmit(request))
|
62
|
-
|
74
|
+
raise RequestFailed, response unless response.success?
|
63
75
|
Select::Response.read(response.data)
|
64
76
|
end
|
65
77
|
end
|
data/lib/yubioath/list.rb
CHANGED
data/lib/yubioath/put.rb
CHANGED
@@ -2,9 +2,6 @@ require 'bindata'
|
|
2
2
|
|
3
3
|
class YubiOATH
|
4
4
|
class Put
|
5
|
-
ALGORITHMS = {'SHA1' => 0x01, 'SHA256' => 0x02}
|
6
|
-
TYPES = {'hotp' => 0x10, 'totp' => 0x20}
|
7
|
-
|
8
5
|
class Request < BinData::Record
|
9
6
|
uint8 :cla, value: 0x00
|
10
7
|
uint8 :ins, value: 0x01
|
@@ -20,8 +17,8 @@ class YubiOATH
|
|
20
17
|
|
21
18
|
uint8 :key_tag, value: 0x73
|
22
19
|
uint8 :key_length, value: -> { secret.length + 2 }
|
23
|
-
|
24
|
-
|
20
|
+
bit4 :type
|
21
|
+
bit4 :algorithm
|
25
22
|
uint8 :digits
|
26
23
|
string :secret
|
27
24
|
end
|
data/lib/yubioath/response.rb
CHANGED
@@ -4,10 +4,10 @@ class YubiOATH
|
|
4
4
|
class Response < BinData::Record
|
5
5
|
count_bytes_remaining :data_length
|
6
6
|
string :data, read_length: -> { data_length - 2 }
|
7
|
-
array :
|
7
|
+
array :status, type: :uint8, initial_length: 2
|
8
8
|
|
9
9
|
def success?
|
10
|
-
|
10
|
+
status == [0x90, 0x00]
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/yubioath.gemspec
CHANGED
@@ -4,23 +4,16 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'yubioath'
|
7
|
-
spec.version = '0.
|
7
|
+
spec.version = '1.0.0'
|
8
8
|
spec.authors = ['James Ottaway']
|
9
9
|
spec.email = ['yubioath@james.ottaway.io']
|
10
10
|
spec.summary = 'Securely manage your 2FA tokens using your Yubikey NEO'
|
11
11
|
spec.homepage = 'https://github.com/jamesottaway/yubioath'
|
12
12
|
spec.license = 'MIT'
|
13
13
|
|
14
|
-
spec.files = `git ls-files -z`.split("\x0")
|
15
|
-
spec.bindir = "exe"
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
15
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
17
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
16
|
spec.require_paths = ['lib']
|
19
17
|
|
20
|
-
spec.add_dependency 'thor', '~> 0.19.1'
|
21
18
|
spec.add_dependency 'bindata', '~> 2.1'
|
22
|
-
spec.add_dependency 'smartcard', '~> 0.5.5'
|
23
|
-
|
24
|
-
spec.add_development_dependency 'bundler', '~> 1.7'
|
25
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
26
19
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yubioath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Ottaway
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: thor
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.19.1
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 0.19.1
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: bindata
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,66 +24,23 @@ dependencies:
|
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
26
|
version: '2.1'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: smartcard
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 0.5.5
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.5.5
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: bundler
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.7'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.7'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rake
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '10.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '10.0'
|
83
27
|
description:
|
84
28
|
email:
|
85
29
|
- yubioath@james.ottaway.io
|
86
|
-
executables:
|
87
|
-
- yubioath
|
30
|
+
executables: []
|
88
31
|
extensions: []
|
89
32
|
extra_rdoc_files: []
|
90
33
|
files:
|
91
34
|
- ".gitignore"
|
35
|
+
- ".rspec"
|
92
36
|
- ".rubocop.yml"
|
37
|
+
- ".rubocop_todo.yml"
|
93
38
|
- ".ruby-version"
|
94
39
|
- Gemfile
|
40
|
+
- LICENSE.markdown
|
95
41
|
- LICENSE.txt
|
96
42
|
- README.md
|
97
43
|
- Rakefile
|
98
|
-
- exe/yubioath
|
99
|
-
- lib/card.rb
|
100
|
-
- lib/cli.rb
|
101
44
|
- lib/yubioath.rb
|
102
45
|
- lib/yubioath/calculate.rb
|
103
46
|
- lib/yubioath/calculate_all.rb
|
data/exe/yubioath
DELETED
data/lib/card.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'smartcard'
|
2
|
-
require 'yubioath'
|
3
|
-
|
4
|
-
class Card
|
5
|
-
def initialize(name:)
|
6
|
-
@name = name
|
7
|
-
end
|
8
|
-
|
9
|
-
def yubioath
|
10
|
-
tap do |card|
|
11
|
-
yield YubiOATH.new(card)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def tap(&block)
|
18
|
-
Context.tap do |context|
|
19
|
-
begin
|
20
|
-
card = context.card(@name)
|
21
|
-
block.call(card)
|
22
|
-
ensure
|
23
|
-
card.disconnect unless card.nil?
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class Context
|
29
|
-
def self.tap(&block)
|
30
|
-
context = Smartcard::PCSC::Context.new
|
31
|
-
block.call(context)
|
32
|
-
ensure
|
33
|
-
context.release
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
data/lib/cli.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'thor'
|
2
|
-
require 'card'
|
3
|
-
|
4
|
-
class CLI < Thor
|
5
|
-
package_name 'yubioath'
|
6
|
-
|
7
|
-
desc 'list', 'list current OTP tokens'
|
8
|
-
def list
|
9
|
-
card.yubioath do |yubioath|
|
10
|
-
all = yubioath.calculate_all(timestamp: Time.now)
|
11
|
-
|
12
|
-
STDOUT.puts
|
13
|
-
STDOUT.puts 'YubiOATH Tokens:'
|
14
|
-
STDOUT.puts '----------------'
|
15
|
-
|
16
|
-
all[:codes].each_with_index do |token, index|
|
17
|
-
STDOUT.puts "#{index+1}. #{token.name}: #{token.code}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
desc 'token NAME', 'get the current OTP value for NAME'
|
23
|
-
def token(name)
|
24
|
-
card.yubioath do |yubioath|
|
25
|
-
token = yubioath.calculate(name: name, timestamp: Time.now)
|
26
|
-
STDOUT.print token.code
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
desc 'add NAME SECRET', 'add a new OTP secret'
|
31
|
-
def add(name, secret)
|
32
|
-
card.yubioath do |yubioath|
|
33
|
-
response = yubioath.put(name: name, secret: secret, algorithm: 'SHA256', type: 'totp', digits: 6)
|
34
|
-
throw unless response.success?
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
desc 'delete NAME', 'remove the OTP token called NAME'
|
39
|
-
def delete(name)
|
40
|
-
card.yubioath do |yubioath|
|
41
|
-
response = yubioath.delete(name: name)
|
42
|
-
throw unless response.success?
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def card
|
49
|
-
@card ||= Card.new(name: 'Yubico Yubikey NEO OTP+CCID')
|
50
|
-
end
|
51
|
-
end
|