yubioath 0.1.1 → 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 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