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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7386901adaf1c073cfd36ba9935ed9d6f705d36d
4
- data.tar.gz: 1d8cb06257cf05e553908e807973afb860540beb
3
+ metadata.gz: 4f3d7712ca20e86423b1dd55b26c6e8390bd9c08
4
+ data.tar.gz: e81afd263ba2466f972568e4fe199bd9b9237b5f
5
5
  SHA512:
6
- metadata.gz: f749d02c469cf6ba53b8f75b967b9a2e54630f62bafb14ad07d9d8c623113228136f52c6b10e9d63520467e0e11a7444bf6b6bdcfcf5bfd8e7a9c520f9d85f76
7
- data.tar.gz: 91c71ef4a90586d8c8291a236a55ddfc8d8c6a31065eb66efb383888350bf113b7a0f2bc341ee51d36d98ce76b551980565e9dc1bdf1b183efb1f9751b1f67b1
6
+ metadata.gz: 903e5115d614d3cc777f6a2ea46a66561bdb2c7be8bebccc653a695c36449b970ff00dd56eed04bbdc879d18b34fb39ab0ffe36a18ed03798c5db3b45bca37b7
7
+ data.tar.gz: 34215cf74eb5316b6728136df0106a0c74ec5610fc0e7c2bb5b643d7a3b1c66b45217f749c631f8303378c1ec7b3d36b85d894f94b31793381a606f10bf186ff
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -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
- Metrics/LineLength:
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
@@ -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
@@ -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
- Securely manage your 2FA tokens using your Yubikey NEO
3
+ A mostly-complete Ruby implementation of the [YubiOATH applet protocol](https://developers.yubico.com/ykneo-oath/Protocol.html).
4
4
 
5
- ## Installation
5
+ ## Usage
6
+
7
+ The `YubiOATH` class accepts a `card`, which must respond to `#transmit(apdu)`.
6
8
 
7
- Install `yubioath` globally using RubyGems:
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
- $ gem install yubioath
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
- ## Usage
23
+ ### Calculate All
14
24
 
15
- List all available tokens:
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
- $ yubioath list
19
-
20
- YubiOATH Tokens Available:
21
- -----
22
- 1. GitHub - james+github@example.com: 123456
23
- 2. Google - james+google@example.com: 234567
24
- 3. Amazon - james+amazon@example.com: 345678
25
- 4. Heroku - james+heroku@example.com: 456789
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
  ```
@@ -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
- throw unless response.success?
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
- throw unless response.success?
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
- throw unless response.success?
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
- key_algorithm: (Put::ALGORITHMS[algorithm] | Put::TYPES[type]),
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
- throw unless response.success?
74
+ raise RequestFailed, response unless response.success?
63
75
  Select::Response.read(response.data)
64
76
  end
65
77
  end
@@ -13,7 +13,9 @@ class YubiOATH
13
13
  array :codes, read_until: :eof do
14
14
  uint8 :name_tag
15
15
  uint8 :name_length
16
- string :name, read_length: :name_length
16
+ bit4 :type
17
+ bit4 :algorithm
18
+ string :name, read_length: -> { name_length - 1 }
17
19
  end
18
20
  end
19
21
  end
@@ -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
- uint8 :key_algorithm
24
-
20
+ bit4 :type
21
+ bit4 :algorithm
25
22
  uint8 :digits
26
23
  string :secret
27
24
  end
@@ -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 :success, type: :uint8, initial_length: 2
7
+ array :status, type: :uint8, initial_length: 2
8
8
 
9
9
  def success?
10
- success == [0x90, 0x00]
10
+ status == [0x90, 0x00]
11
11
  end
12
12
  end
13
13
  end
@@ -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.1.1'
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.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Ottaway
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-29 00:00:00.000000000 Z
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
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env ruby
2
- $LOAD_PATH.unshift File.expand_path(File.join(__dir__, '..', 'lib'))
3
- require 'cli'
4
- CLI.start
@@ -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